ping-code

导航

OpenGL入门——着色器

前面几节简单使用了一下着色器

 

现在详细解释一下着色器和着色器语言(GLSL)

 

1. 着色器

着色器是运行在GPU上的小程序,它们之间不能互相通信,唯一的沟通只有输入和输出。

 

2. GLSL

着色器的开头是声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数里面处理所有的输入变量,将结果输出到输出变量。uniform是一种从CPU向GPU发送数据的方式。但是它和输入变量又不一样,因为它是全局的,每个着色器程序使用的都是同一个uniform值。

#version version_number
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

int main()
{
  // 处理输入并进行一些图形操作
  ...
  // 输出处理过的结果到输出变量
  out_variable_name = weird_stuff_we_processed;
}

 

3. 数据类型

GLSL的基础数据类型包含:intfloatdoubleuintbool

容器类型包含:向量和矩阵

  1)向量:vecn(包含n个float分量)、bvecn(包含n个bool分量)、ivecn(包含n个int分量)、dvecn(包含n个double分量)、uvecn(包含n个uint分量);

  2)矩阵:glm::matn(n维矩阵,现在还没怎么看到,后面看到再更新)

 

4. 输入和输出

虽然每个着色器是独立的小程序,但它们也是整体的一部分,所以需要用输入和输出进行数据交流和传递。只要输出变量与下一个着色器的输入匹配上了(类型、名称完全一致即可匹配上),它就会传递下去。

注意:

4.1 顶点着色器使用layout指定输入变量,如layout(location=0),这样就可以之间在CPU上配置顶点属性了,如glVertexAttribPointer的第一个参数就是layout标识。

#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0

out vec4 vertexColor; // 为片段着色器指定一个颜色输出

void main()
{
    gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
    vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}

4.2 片段着色器是要生成对应像素的最终颜色,所以须有一个vec4的颜色输出变量,否则最后物体会被渲染为黑色或白色

#version 330 core
out vec4 FragColor;

in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)

void main()
{
    FragColor = vertexColor;
}

OpenGL入门——第一个三角形 - 一只小瓶子 - 博客园 (cnblogs.com)中的着色器源码替换为上面两个,那么绘制效果会是一个暗红色的三角形。

 

5. Uniform

uniform是一种从CPU传向GPU的发送方式,它是全局的,且在每个着色器中都是独一无二的,可以被任意着色器在任意阶段访问。

#version 330 core
out vec4 FragColor;

uniform vec4 ourColor; // 在程序代码中设定这个变量

void main()
{
    FragColor = ourColor;
}

然后在CPU程序中设置这个uniform值

int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//找到着色器中uniform属性的索引/位置值,返回-1就代表没有找到这个位置值
glUniform4f(vertexColorLocation, 0.0f, 1.0, 0.0f, 1.0f);//设置uniform值,类型为vec4

OpenGL入门——第一个三角形 - 一只小瓶子 - 博客园 (cnblogs.com)中的片段着色器源码替换为上面那个,那么绘制效果会是一个绿色的三角形。

 如果在绘制循环里动态修改这个uniform值,那么渲染效果就是一个颜色会变化的三角形。

 

    while (!glfwWindowShouldClose(window))
    {
        float timeValue = glfwGetTime();
        float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
        int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//找到着色器中uniform属性的索引/位置值,返回-1就代表没有找到这个位置值

        processInput(window);


        //清空屏幕
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);


        ///绘制物体
        glUseProgram(shaderProgram);//激活程序对象
        glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);//设置uniform值,必须在激活程序对象之后才能更新

        glBindVertexArray(VAO);
        
        glDrawArrays(GL_TRIANGLES, 0, 3);//使用VAO绘制:绘制图元为三角形,起始索引0,绘制顶点数量3

        //glBindVertexArray(0);//只绘制一个物体,不需要重复绑定和解绑VAO
        
        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲)
        glfwPollEvents();//检查有没有触发什么事件
    }

注意:更新一个uniform之前必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置uniform的。

 

6. 更多属性

上面4.1说到顶点着色器使用layout指定顶点属性位置,注意就可以在CPU中设置顶点属性

顶点着色器

//vertex shader source
#version 330 core
layout(location = 0) in vec3 position;     //位置X,Y,Z
layout(location = 1) in vec3 color;        //颜色R,G,B

out vec3 vertexColor;        //输出顶点颜色

void main()
{
    gl_Position = vec4(position, 1.0);    //顶点坐标
    vertexColor = color;                //从顶点数据那里得到的输入颜色
}

片段着色器

//fragment shader source
#version 330 core
out vec4 fragColor;        //像素的最终颜色
in vec3 vertexColor;    //从顶点着色器传来的输入变量(名称、类型必须相同)

void main()
{
    fragColor = vec4(vertexColor, 1.0);
}

程序中使用glVertexAttribPointer根据第一个参数layout值设置对应顶点属性

    float vertices[] = {
        // 位置              // 颜色
         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    // 顶部
    };

    //生成VAO对象,缓冲ID为VAO
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);//绑定VAO,从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO,供之后使用

    //生成VBO对象,缓冲ID为VBO
    unsigned int VBO;
    glGenBuffers(1, &VBO);//第一个参数GLsizei是要生成的缓冲对象的数量,第二个GLuint是要输入用来存储缓冲对象名称的数组

    //绑定到目标对象,VBO变成了一个顶点缓冲类型
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//第一个就是缓冲对象的类型,第二个参数就是要绑定的缓冲对象的名称
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//数据传入缓冲内存中,GL_STATIC_DRAW:数据不会或几乎不会改变; GL_DYNAMIC_DRAW:数据会被改变很多; GL_DYNAMIC_DRAW:数据会被改变很多

    //设置顶点属性指针,如何解析顶点数据
    /*
    第一个参数指定我们要配置的顶点属性,顶点着色器中使用layout(location = 0)定义
    第二个参数指定顶点属性的大小
    第三个参数指定数据的类型
    第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间
    第五个参数步长(Stride),它告诉我们在连续的顶点属性组之间的间隔
    最后一个参数的类型是void*,数据在缓冲中起始位置的偏移量(Offset)
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);//启用顶点属性layout(location = 0),顶点属性默认是禁用的
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);//启用顶点属性layout(location = 1),顶点属性默认是禁用的
    glBindBuffer(GL_ARRAY_BUFFER, 0);//设置完属性,解绑VBO

    glBindVertexArray(0);//配置完VBO及其属性,解绑VAO

渲染效果

 

posted on 2023-09-06 22:04  一只小瓶子  阅读(357)  评论(0编辑  收藏  举报