OpenGL中的着色器是什么,我们需要它们做什么?[closed]

jobtbby3  于 2022-11-04  发布在  其他
关注(0)|答案(3)|浏览(137)

已关闭。此问题需要更多的focused。当前不接受答案。
**想要改进此问题吗?**更新问题,使其仅关注editing this post的一个问题。

去年关闭了。
去年,机构群体审查了是否重新讨论此问题,并将其关闭:
原始关闭原因未解决
Improve this question
当我试图通过www.learnopengl.com上的openGL wiki和教程时,我的直觉永远无法理解整个概念是如何工作的。有人能用更抽象的方式来解释一下它是如何工作的吗?什么是顶点着色器和片段着色器,我们用它们做什么?

5t7ly7z5

5t7ly7z51#

OpenGL wiki给出了一个很好的定义:
着色器是一个用户定义的程序,设计用于在图形处理器的某个阶段上运行。
"历史课"
过去,显卡是执行一组固定算法的非可编程芯片:

  • 输入:三角形的3D坐标、三角形的颜色、光源
  • 输出:2D图像

所有这些都使用一个固定的参数化算法,通常类似于Phong reflection model。图片来自Wiki:

这样的架构被称为“固定功能流水线”,因为它们只能实现一个算法。
但这对于想要创建许多不同的复杂视觉效果的程序员来说限制太大了。
因此,随着半导体制造技术的进步,GPU设计师能够在每平方毫米上压缩更多的晶体管,供应商开始允许渲染管道的一些部分被编程为编程语言,如类似C的GLSL
然后,这些语言被转换为semi-undocumented instruction sets,运行在内置于这些较新GPU的小型“CPU”上。
一开始,那些着色器语言were not even Turing complete
术语 * 通用GPU(GPGPU)* 指的是现代GPU增强的可编程性,新的语言被创造出来以比OpenGL更好地适应它,特别是OpenCL和CUDA。关于哪种算法更适合GPU而不是CPU计算,请参阅此答案的简短讨论:术语“受CPU限制”和“受I/O限制”是什么意思?

现代着色器管线概述

在OpenGL 4模型中,只有下图中的蓝色阶段是可编程的:

Image source
着色器从前一流水线阶段获取输入(例如顶点位置、颜色和光栅化像素),并自定义下一阶段的输出。
最重要的两个方面是:

  • 顶点着色器:
  • 输入:点在三维空间中的位置
  • 输出:点的2D投影(使用4D矩阵乘法)

以下相关示例更清楚地说明了什么是投影:如何在OpenGL中使用glOrtho()?

  • 片段着色器:
  • 输入:三角形的所有像素的2D位置+(边的颜色或纹理图像)+光照参数
  • 输出:三角形的每个像素的颜色(如果它没有被另一个更接近的三角形遮挡),通常在顶点之间插值

碎片是从先前计算的三角形投影离散化的,参见:

  • 片段着色器如何从顶点着色器输出中确定片段数?
  • https://gamedev.stackexchange.com/questions/8977/what-is-a-fragment-in-3d-graphics-programming/118820#118820

相关问题:What are Vertex and Pixel shaders?
由此我们看到,“着色器”这个名字对当前的架构来说并不是很具描述性。这个名字当然起源于“阴影”,它是由我们现在所说的“片段着色器”处理的。但是GLSL中的“着色器”现在也像顶点着色器一样管理顶点位置,更不用说OpenGL 4. 3 GL_COMPUTE_SHADER了,它允许与渲染完全无关的任意计算,与OpenCL非常相似。
TODO OpenGL是否可以单独使用OpenCL高效地实现,即,使所有阶段都可编程?当然,必须在性能/灵活性之间进行权衡。
第一批带有着色器的GPU甚至使用不同的专用硬件来进行顶点和片段着色,因为它们的工作负载完全不同。然而,当前的架构使用单一类型硬件(基本上是小型CPU)的多个通道来处理所有着色器类型,这节省了一些硬件重复。这种设计被称为Unified Shader Model
x1c4d 1x指令集
改编自this imageSVG source
以下来自Asianometry https://youtu.be/GuV-HyslPxk?t=350的精彩总结还澄清了一些管线实际上是由CPU本身处理的,而不是早期技术中的GPU,主要由NVIDIA领导:

同一视频还继续提到他们2001年推出的GeForce 3 series是第一款引入某种程度着色器可编程性的产品。

源代码示例

要真正了解着色器及其功能,您必须查看许多示例并学习API。例如,https://github.com/JoeyDeVries/LearnOpenGL是一个很好的源代码。
在现代的OpenGL 4中,即使是hello world三角形程序也使用超级简单的着色器,而不是像glBeginglColor这样的旧的被弃用的即时API。
请考虑以下三角形hello world示例,该示例在单个程序中同时包含着色器版本与即时版本:https://stackoverflow.com/a/36166310/895245
main.c


# include <stdio.h>

# include <stdlib.h>

# define GLEW_STATIC

# include <GL/glew.h>

# include <GLFW/glfw3.h>

# define INFOLOG_LEN 512

static const GLuint WIDTH = 512, HEIGHT = 512;
/* vertex data is passed as input to this shader
 * ourColor is passed as input to the to the fragment shader. */
static const GLchar* vertexShaderSource =
    "#version 330 core\n"
    "layout (location = 0) in vec3 position;\n"
    "layout (location = 1) in vec3 color;\n"
    "out vec3 ourColor;\n"
    "void main() {\n"
    "    gl_Position = vec4(position, 1.0f);\n"
    "    ourColor = color;\n"
    "}\n";
static const GLchar* fragmentShaderSource =
    "#version 330 core\n"
    "in vec3 ourColor;\n"
    "out vec4 color;\n"
    "void main() {\n"
    "    color = vec4(ourColor, 1.0f);\n"
    "}\n";
GLfloat vertices[] = {
/*   Positions            Colors */
     0.5f, -0.5f, 0.0f,   1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,
     0.0f,  0.5f, 0.0f,   0.0f, 0.0f, 1.0f
};

int main(int argc, char**argv) {
    int immediate = (argc > 1) && argv[1][0] == '1';

    /* Used in !immediate only. */
    GLuint vao, vbo;
    GLint shaderProgram;

    glfwInit();
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, __FILE__, NULL, NULL);
    glfwMakeContextCurrent(window);
    glewExperimental = GL_TRUE;
    glewInit();
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glViewport(0, 0, WIDTH, HEIGHT);
    if (immediate) {
        float ratio;
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);
        ratio = width / (float) height;
        glClear(GL_COLOR_BUFFER_BIT);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glBegin(GL_TRIANGLES);
        glColor3f(  1.0f,  0.0f, 0.0f);
        glVertex3f(-0.5f, -0.5f, 0.0f);
        glColor3f(  0.0f,  1.0f, 0.0f);
        glVertex3f( 0.5f, -0.5f, 0.0f);
        glColor3f(  0.0f,  0.0f, 1.0f);
        glVertex3f( 0.0f,  0.5f, 0.0f);
        glEnd();
    } else {
        /* Build and compile shader program. */
        /* Vertex shader */
        GLint vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
        glCompileShader(vertexShader);
        GLint success;
        GLchar infoLog[INFOLOG_LEN];
        glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(vertexShader, INFOLOG_LEN, NULL, infoLog);
            printf("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n%s\n", infoLog);
        }
        /* Fragment shader */
        GLint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
        glCompileShader(fragmentShader);
        glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(fragmentShader, INFOLOG_LEN, NULL, infoLog);
            printf("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n%s\n", infoLog);
        }
        /* Link shaders */
        shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);
        glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
        if (!success) {
            glGetProgramInfoLog(shaderProgram, INFOLOG_LEN, NULL, infoLog);
            printf("ERROR::SHADER::PROGRAM::LINKING_FAILED\n%s\n", infoLog);
        }
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        glGenVertexArrays(1, &vao);
        glGenBuffers(1, &vbo);
        glBindVertexArray(vao);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        /* Position attribute */
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
        /* Color attribute */
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
        glBindVertexArray(0);
        glUseProgram(shaderProgram);
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glBindVertexArray(0);
    }
    glfwSwapBuffers(window);

    /* Main loop. */
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }

    if (!immediate) {
        glDeleteVertexArrays(1, &vao);
        glDeleteBuffers(1, &vbo);
        glDeleteProgram(shaderProgram);
    }
    glfwTerminate();
    return EXIT_SUCCESS;
}

改编自Learn OpenGLmy GitHub upstream
编译并在Ubuntu 20.04上运行:

sudo apt install libglew-dev libglfw3-dev
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -lGL -lGLEW -lglfw

# Shader

./main.out

# Immediate

./main.out 1

两者的结果相同:


指令集
由此我们可以看出:

  • 顶点和片段着色器程序被表示为在CPU上运行的常规C程序内的包含GLSL语言(vertexShaderSourcefragmentShaderSource)的C样式字符串
  • 该C程序进行OpenGL调用,其将那些字符串编译成GPU代码,例如:
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
  • 着色器定义其预期输入,C程序通过指向GPU代码的内存指针将其提供给GPU代码。例如,片段着色器将其预期输入定义为顶点位置和颜色的数组:
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec3 color;\n"
"out vec3 ourColor;\n"

并且还将其输出ourColor中的一个定义为颜色阵列,然后该颜色阵列变为片段着色器的输入:

static const GLchar* fragmentShaderSource =
    "#version 330 core\n"
    "in vec3 ourColor;\n"

然后,C程序将包含顶点位置和颜色的数组从CPU提供给GPU

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

然而,在直接非着色器示例中,我们看到进行了显式给予位置和颜色的魔术API调用:

glColor3f(  1.0f,  0.0f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.0f);

因此,我们理解,这代表了一个更加受限的模型,因为位置和颜色不再是存储器中的任意用户定义数组,然后由任意用户提供的程序处理,而是仅仅输入到Phong类模型。
在这两种情况下,渲染输出通常直接进入视频,而不通过CPU传回,尽管可以读取到CPU,例如,如果您想要将其保存到文件:如何使用GLUT/OpenGL渲染文件?

出色的3D图形着色器应用程序

非平凡着色器的一个经典酷应用是动态阴影,即一个对象投射到另一个对象上的阴影,与仅取决于三角形法线和光源之间Angular 的阴影相反,Phong模型中已经涵盖了这一点:

Image source中的一个。

超酷的非3D片段着色器应用程序

https://www.shadertoy.com/是一个“片段着色器的Twitter”,它包含了大量视觉上令人印象深刻的着色器,可以作为一个“零设置”的方式来玩片段着色器Shadertoy运行在WebGL上,这是一个浏览器的OpenGL接口,所以当你点击一个shadertoy时,它会在你的浏览器中渲染着色器代码,和大多数“片段着色器图形应用程序”一样,他们只是有一个固定的简单顶点着色器,在屏幕上画两个三角形,就在摄像头的前面:WebGL/GLSL - How does a ShaderToy work?,因此使用者只会编写片段着色器。
下面是我亲手挑选的一些更科学的例子:

  • 对于某些算法,图像处理可以比在CPU上更快地完成:是否可以从每秒60次的点数据构建热图?


指令集

  • 对于某些功能,绘图速度比在CPU上快:是否可以从每秒60次的点数据构建热图?


指令集

ckocjqey

ckocjqey2#

着色器基本上是根据几个光线方程为你要渲染的对象给予正确的颜色。所以如果你有一个球体,一个光源和一个摄像机,那么摄像机应该会看到一些阴影,一些发光的部分等等,即使球体只有一种颜色。着色器执行光线方程计算来给你这些效果。
顶点着色器将虚拟空间(3D模型)中每个顶点的3D位置转换为该顶点在屏幕上显示的2D坐标。
基本上,片段着色器通过执行灯光计算来为每个像素着色。

zqry0prt

zqry0prt3#

GPU例程以一种简短而简单的方式提供了钩子/回调函数,以便绘制面部纹理。这些钩子就是着色器。

相关问题