• Learn OpenGL 03 着色器


    GLSL

    着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。

    一个典型的着色器有下面的结构:

    1. #version version_number
    2. in type in_variable_name;
    3. in type in_variable_name;
    4. out type out_variable_name;
    5. uniform type uniform_name;
    6. int main()
    7. {
    8. // 处理输入并进行一些图形操作
    9. ...
    10. // 输出处理过的结果到输出变量
    11. out_variable_name = weird_stuff_we_processed;
    12. }

    数据类型 

    GLSL中包含C等其它语言大部分的默认基础数据类型:intfloatdoubleuintbool。GLSL也有两种容器类型,分别是向量(Vector)和矩阵(Matrix)

    向量

    GLSL中的向量是一个可以包含有2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):

    类型含义
    vecn包含n个float分量的默认向量
    bvecn包含n个bool分量的向量
    ivecn包含n个int分量的向量
    uvecn包含n个unsigned int分量的向量
    dvecn包含n个double分量的向量

    大多数时候我们使用vecn,因为float足够满足大多数要求了。

    向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:

    1. vec2 someVec;
    2. vec4 differentVec = someVec.xyxx;
    3. vec3 anotherVec = differentVec.zyw;
    4. vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

    你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:

    1. vec2 vect = vec2(0.5, 0.7);
    2. vec4 result = vec4(vect, 0.0, 0.0);
    3. vec4 otherResult = vec4(result.xyz, 1.0);

    输入与输出

    顶点着色器从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用location这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。

    片段着色器,它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。

    如果我们打算从一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。

    Uniform

    Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

    x修改Uniform的值:

    首先需要找到着色器中uniform属性的索引/位置值。当我们得到uniform的索引/位置值后,我们就可以更新它的值了。这次我们不去给像素传递单独一个颜色,而是让它随着时间改变颜色:

    如果glGetUniformLocation返回-1就代表没有找到这个位置值。最后,我们可以通过glUniform4f函数设置uniform值。注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个uniform之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置uniform的。

    1. float timeValue = glfwGetTime();
    2. float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
    3. int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
    4. glUseProgram(shaderProgram);
    5. glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f)

    因为OpenGL在其核心是一个C库,所以它不支持类型重载,在函数参数不同的时候就要为其定义新的函数;glUniform是一个典型例子。

    uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具,它也是一个在程序和着色器间数据交互的很好工具

    更多属性!

    顶点数据中新加了颜色属性

    1. float vertices[] = {
    2. // 位置 // 颜色
    3. 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
    4. -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
    5. 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部
    6. };

    主要的变化就是要重新配置顶点属性指针

    glVertexAttribPointer函数的前几个参数比较明了。这次我们配置属性位置值为1的顶点属性。颜色值有3个float那么大,我们不去标准化这些值。

    由于我们现在有了两个顶点属性,我们不得不重新计算步长值。为获得数据队列中下一个属性值(比如位置向量的下个x分量)我们必须向右移动6个float,其中3个是位置值,另外3个是颜色值。这使我们的步长值为6乘以float的字节数(=24字节)。
    同样,这次我们必须指定一个偏移量。对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0。颜色属性紧随位置数据之后,所以偏移量就是3 * sizeof(float),用字节来计算就是12字节。

    1. // 位置属性
    2. glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    3. glEnableVertexAttribArray(0);
    4. // 颜色属性
    5. glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
    6. glEnableVertexAttribArray(1);

    创建自己的着色器类并且从文件读取

    创建一个Shader.h和Shader.cpp两个文件

    Shader.h文件主要存放Shader类和其中的函数声明,方便随时查看其中函数的功能

    Shader.cpp文件主要存放函数的定义

    要从文件读取着色器必须使用使用C++文件流

    CPU不能直接读取硬盘中的内容,必须先移动到内存中,也就是要经过硬盘-->FileBuffer-->StringBuffer,之后因为OpenGL不操作String,操作的是char数组,所以我们要一个String的缓存把StringBuffer里的东西变成char数组。

    Shader.h

    1. #pragma once
    2. #include
    3. class Shader
    4. {
    5. public:
    6. Shader(const char* vertexPath, const char* fragmentPath); // 构造器读取并构建着色器
    7. std::string vertexString;
    8. std::string fragmentString;
    9. const char* vertexSource;
    10. const char* fragmentSource;
    11. unsigned int ID; //Shader Program ID
    12. void use(); // 使用/激活程序
    13. // uniform工具函数
    14. void setBool(const std::string& name, bool value) const;
    15. void setInt(const std::string& name, int value) const;
    16. void setFloat(const std::string& name, float value) const;
    17. private:
    18. void checkCompileErrors(unsigned int ID, std::string type);//检查着色器或者程序是否编译错误
    19. };

    Shader.cpp

    1. #include "Shader.h"
    2. #include <iostream>
    3. #include <fstream>
    4. #include <SStream>
    5. #include <glad/glad.h>
    6. #include <GLFW/glfw3.h>
    7. #include <iostream>
    8. using namespace std;
    9. Shader::Shader(const char* vertexPath, const char* fragmentPath)
    10. {
    11. //从文件路径中获取顶点/片段着色器
    12. ifstream vertexFile;
    13. ifstream fragmentFile;
    14. stringstream vertexSStream;
    15. stringstream fragmentSStream;
    16. //打开文件
    17. vertexFile.open(vertexPath);//与硬盘文件建立链接
    18. fragmentFile.open(fragmentPath);
    19. //保证ifstream对象可以抛出异常:
    20. vertexFile.exceptions(ifstream::failbit || ifstream::badbit);//检查是否打开失败或者读取到了坏文件
    21. fragmentFile.exceptions(ifstream::failbit || ifstream::badbit);
    22. try
    23. {
    24. if (!vertexFile.is_open() || !fragmentFile.is_open())
    25. {
    26. throw exception("open file error");
    27. }
    28. //读取文件缓冲内容到数据流
    29. vertexSStream << vertexFile.rdbuf();//硬盘-->FileBuffer-->StringBuffer
    30. fragmentSStream << fragmentFile.rdbuf();
    31. //转换数据流到string
    32. vertexString = vertexSStream.str();
    33. fragmentString = fragmentSStream.str();
    34. vertexSource = vertexString.c_str();
    35. fragmentSource = fragmentString.c_str();
    36. // 编译着色器
    37. unsigned int vertex, fragment;
    38. // 顶点着色器
    39. vertex = glCreateShader(GL_VERTEX_SHADER);
    40. glShaderSource(vertex, 1, &vertexSource, NULL);
    41. glCompileShader(vertex);
    42. checkCompileErrors(vertex, "VERTEX");
    43. // 片段着色器
    44. fragment = glCreateShader(GL_FRAGMENT_SHADER);
    45. glShaderSource(fragment, 1, &fragmentSource, NULL);
    46. glCompileShader(fragment);
    47. checkCompileErrors(fragment, "FRAGMENT");
    48. // 着色器程序
    49. ID = glCreateProgram();
    50. glAttachShader(ID, vertex);
    51. glAttachShader(ID, fragment);
    52. glLinkProgram(ID);
    53. checkCompileErrors(ID, "PROGRAM");
    54. // 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
    55. glDeleteShader(vertex);
    56. glDeleteShader(fragment);
    57. }
    58. catch (const std::exception& ex)
    59. {
    60. printf(ex.what());
    61. }
    62. }
    63. void Shader::use()
    64. {
    65. glUseProgram(ID);
    66. }
    67. void Shader::checkCompileErrors(unsigned int ID, std::string type) {
    68. int sucess;
    69. char infolog[512];
    70. if (type != "PROGRAM")
    71. {
    72. glGetShaderiv(ID, GL_COMPILE_STATUS, &sucess);
    73. if (!sucess)
    74. {
    75. glGetShaderInfoLog(ID, 512, NULL, infolog);
    76. cout << "shader compile error:" << infolog << endl;
    77. }
    78. }
    79. else
    80. {
    81. glGetProgramiv(ID, GL_LINK_STATUS, &sucess);
    82. if (!sucess)
    83. {
    84. glGetProgramInfoLog(ID, 512, NULL, infolog);
    85. cout << "program linking error:" << infolog << endl;
    86. }
    87. cout << "编译成功!" << endl;
    88. }
    89. }

    main.cpp

    只需要加上两句代码就可以了,这里的两个txt文件需要自己创建一个,之后就可以循环使用了

    1. Shader* myshader = new Shader("vertexSource.txt", "fragmentSource.txt");
    2. [...]
    3. myshader->use();

    练习

    1.修改顶点着色器让三角形上下颠倒:参考解答

    将顶点着色器输出的y坐标取反即可

    1. #version 330 core
    2. layout(location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
    3. layout(location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
    4. out vec4 vertexColor;
    5. void main(){
    6. gl_Position = vec4(aPos.x, -aPos.y, aPos.z, 1.0);
    7. vertexColor = vec4(aColor,1.0);
    8. }

    2.使用uniform定义一个水平偏移量,在顶点着色器中使用这个偏移量把三角形移动到屏幕右侧:参考解答

    顶点着色器的x坐标加上一个随时间增加的偏移量即可

    1. //main.cpp
    2. myshader->setFloat("offset", 0.01+glfwGetTime());
    3. #version 330 core
    4. layout(location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
    5. layout(location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
    6. out vec4 vertexColor;
    7. uniform float offset;
    8. void main(){
    9. gl_Position = vec4(aPos.x+offset, aPos.y, aPos.z, 1.0);
    10. vertexColor = vec4(aColor,1.0);
    11. }

    3.使用out关键字把顶点位置输出到片段着色器,并将片段的颜色设置为与顶点位置相等(来看看连顶点位置值都在三角形中被插值的结果)。做完这些后,尝试回答下面的问题:为什么在三角形的左下角是黑的?:参考解答

    三角形的左下角是黑的,是因为左下角顶点(-0.5,-0.5,0),因为颜色值是0到1,负值会被截断到0,就是黑色。插值以后靠近左下角的地方就是黑色的

    vertex 

    1. #version 330 core
    2. layout(location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
    3. layout(location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
    4. out vec4 vertexColor;
    5. out vec3 pos;
    6. uniform float offset;
    7. void main(){
    8. gl_Position = vec4(aPos.x+offset, aPos.y, aPos.z, 1.0);
    9. vertexColor = vec4(aColor,1.0);
    10. pos = aPos;
    11. }

    fragment

    1. #version 330 core
    2. in vec4 vertexColor;
    3. out vec4 FragColor;
    4. in vec3 pos;
    5. void main(){
    6. FragColor = vec4(pos,1);
    7. }

  • 相关阅读:
    【nodejs】如何从Windows中完全删除node.js
    【定时器】企业微信群定时发送消息简单实现
    HappyGBS GB28181信令服务 - 文档
    NLP项目实战02:英文文本识别
    ACID ,你还在为了八股文而背吗
    区间素数筛
    接口自动化测试实践指导(上):接口自动化需要做哪些准备工作
    拼图游戏-第13届蓝桥杯Scratch选拔赛真题精选
    【html】面试问题总结
    指派问题——匈牙利法
  • 原文地址:https://blog.csdn.net/Mr_Dongzheng/article/details/136572931