从这里就接触到了可编程图形渲染管线。
下面介绍使用Vertex Shader (顶点着色器)和 Fragment Shader(像素着色器)的方法。
我们的目标是使用这两个着色器给三角形填充绿色。
添加了一个cpp文件存放Shader文件,MyShaderCode.cpp:
1 const char* vertexShaderCode =
2 " #version 430
"
3 "
"
4 " in layout(location=0) vec2 position;
"
5 "
"
6 "
"
7 "
"
8 " void main()
"
9 " {
"
10 " gl_Position= vec4(position,0.0,1.0);
"
11 " }
"
12 "
"
13 "
";
14
15 const char* fragmentShaderCode =
16 " #version 430
"
17 "
"
18 " out vec4 finalColor;
"
19 "
"
20 "
"
21 " void main()
"
22 " {
"
23 " finalColor = vec4(0.0,1.0,0.0,1.0);
"
24 " }
"
25 "
"
26 "
";
书写Shader使用的是GLSL语言,和C语言语法非常相似。
Vertex Shader
#version 430 表示opengl的版本号,要使用显卡支持的版本,否则会编译出错
vertex Shader第四行的 in 关键字表示这里定义的是一个输入。layout(location=0)表示这里接收的是位置信息。vec2表示这里是一个2维向量。position是自己定义的接受数据的变量名称。
gl_Position是GLSL定义的关键字,使用在Vertex shader中就表示它将接收一个四维向量来表示顶点位置信息。
vec4(position,0.0,1.0)是GLSL的一种特殊语法,用于方便的构造一个四维向量,因为position是二维的,后面再添加两个数字就表示了四维向量。最后一位是1.0,表示没有缩放。
Fragment Shader
第18行使用out关键字,表示这里是一个输出。
而Fragment Shader在渲染管线中只有一个输出,就是每个Fragment(暂时可以理解为像素,实际上不等同于像素)的颜色。
在main中直接将所有的像素颜色都设置成绿色。
MyGlWindow.cpp
1 #include <glglew.h> 2 #include "MyGlWindow.h" 3 4 extern const char* vertexShaderCode; 5 extern const char* fragmentShaderCode; 6 7 GLuint programID; 8 9 void MyGlWindow::sendDataToOpenGL() 10 { 11 GLfloat verts[] = 12 { 13 +0.0f, +0.0f, //0 14 +1.0f, +1.0f, //1 15 -1.0f, +1.0f, //2 16 -1.0f, -1.0f, //3 17 +1.0f, -1.0f, //4 18 }; 19 20 GLuint vertexBufferID; 21 glGenBuffers(1, &vertexBufferID); 22 glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); 23 glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); 24 25 GLushort indices[] = 26 { 27 0,1,2, 28 0,3,4, 29 }; 30 GLuint indexBufferID; 31 glGenBuffers(1, &indexBufferID); 32 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID); 33 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 34 35 glEnableVertexAttribArray(0); 36 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); 37 } 38 39 void MyGlWindow::installShaders() 40 { 41 GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER); 42 GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); 43 44 glShaderSource(vertexShaderID, 1, &vertexShaderCode, 0); 45 glShaderSource(fragmentShaderID, 1, &fragmentShaderCode, 0); 46 47 glCompileShader(vertexShaderID); 48 glCompileShader(fragmentShaderID); 49 50 programID = glCreateProgram(); 51 glAttachShader(programID, vertexShaderID); 52 glAttachShader(programID, fragmentShaderID); 53 54 glLinkProgram(programID); 55 56 glDeleteShader(vertexShaderID); 57 glDeleteShader(fragmentShaderID); 58 59 glUseProgram(programID); 60 } 61 62 void MyGlWindow::initializeGL() 63 { 64 glewInit(); 65 sendDataToOpenGL(); 66 installShaders(); 67 } 68 69 void MyGlWindow::paintGL() 70 { 71 glViewport(0, 0, width(), height()); 72 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); 73 } 74 75 MyGlWindow::~MyGlWindow() 76 { 77 glUseProgram(0); 78 glDeleteProgram(programID); 79 }
MyGlWindow.h
1 #pragma once 2 #include <QtOpenGLqgl.h> 3 class MyGlWindow :public QGLWidget 4 { 5 protected: 6 void sendDataToOpenGL(); 7 void installShaders(); 8 void initializeGL(); 9 void paintGL(); 10 11 public: 12 ~MyGlWindow(); 13 };
对MyGlWindow.cpp文件进行了一个重构,把上节initializeGL()中的内容除了第一句glewInit()之外全部封装进了sendDataToOpenGL()函数,因为这些代码都是向OpenGL函数发送数据的。
为了使用Shader,我们新增加了一个installShaders()函数,所有使用Shader的功能都写在其中。
在initializeGL函数中先调用sendDataToOpengGL(),然后调用installShaders()。
使用Shader的步骤清单
- 创建Shader并分配ID,glCreateShader
- 设置Shader内容, glSourceShader
- 编译Shader, glCompileShader
- 创建Program,glCreateProgram
- 附加Shader,glAttachShader
- 链接Shader, glLinkProgram
- 删除Shader,glDeleteShader
- 使用Program, glUseProgram
- 删除Program,glDeleteProgram
下面就按照这六个步骤逐步解析代码。
1. 创建Shader并分配ID
39 - 40 行使用glCreateShader分别创建了vertex shader和 fragment shader,并分别将ID保存在GLuint对象里(和前面使用GLuint存储Buffer对象类似)
函数的参数是固定的宏
2. 设置Shader内容
42 - 43 行使用glShaderSource函数给Shader提供具体内容。
我们额外增加了一个文件MyShaderCode用来存放Shader的代码,代码具体内容放在两个较长的字符串里。
然后在MyGlWindow.cpp的开头使用extern 声明了vertexShaderCode和fragmentShaderCode字符串变量。
glShaderSource的
- 第一个参数表示Shader的ID
- 第二个参数表示Shader内容的字符串有几个数组,这里只有1个
- 第三个参数表示字符串数组本身,但是需要注意参数的类型是const char **,所以还要在前面增加一个取地址符号
- 第四个参数使用0表示由OpenGL自己计算字符串的长度
3. 编译Shader
45 - 46 行使用glCompileShader分别对两个Shader进行编译
4.创建Program
进行后续操作之前需要创建一个Program,这里使用glCreateProgram()创建了一个Porgram,并将其ID保存在GLuint对象里。
5.附加Shader
glAttachShader有两个参数,第一个参数是Program的ID,第二个参数是Shader的ID
6.链接Shader
虽然通常称为“链接SHader”, 但其实是链接Program, 函数glLinkProgram的参数就是Program的ID
7.删除Shader
Shader使用完成后一定要删除Shader,第56和57行进行了这步操作。在useProgram之前进行也没问题,只要编译和链接了Shader就行。
8.使用Program
glUseProgram的参数是Program的ID
9. 删除Program
使用完成Program之后要删除Program,这里在析构函数里进行是比较合适的,分两步,如77和78行所示。
完成后编译,我们将得到两个绿色的三角形。