Preface
这一节所有的主要内容都在一个 OpenGL 库文件中 <LoadShaders.h> , 只需要用 LoadShader()函数进行加载即可. 但是由于老是出错, 所以自己实现了一下, 也用以提供给有兴趣了解着色器的编译, 连接等原理的人.
因为程序基本都是自己实现的, 所以, 只需要包含最基本的 OpenGL 头文件即可运行.
效果如下:
Background
由于固定管线编程过程中涉及的大量计算 (当然, 上个例子并没有多少计算量) 都是再 CPU 上进行的, 而这些可能影响 CPU 处理其他事情的效率, 所以不妨运用一种编程技法, 将这些繁重的运算交予 GPU 去处理, 进而腾出 CPU 的时间. 于是就引进了我们今天的前提 -- 可编程管线, 其对应的语言为 GLSL(OpenGL Shading Language).
先说一下博文撰写原则, 博文撰写涉及到的背景知识只阐述与本次主题相关的, 至于整合的那种完整的理论原理综述, 如果需要的话会在后面做统一的阐述, 而单篇有主题的文章中不做总结, 不然可能会喧宾夺主.
看完这一篇可能会有几个疑问: 着色器到底干了啥, 它是怎么导致图形效果的, 以及它里面每一个关键字都是干什么的, 等等
上述问题, 可能考虑下一篇讲, 有人会问为什么不先弄清楚上面的再写下面的, 因为我就是那样过来的, 只有空洞的理论和语法, 这种还是先看到效果, 然后追究其原理更合适一点, 这是我的一个安排和考虑, 理解万岁.
Getting ready
觉得还是需要简单说明一下可编程渲染管线的流程(超级精简版):
对于顶点属性数据, 如: 位置, 颜色, 纹理等等, 他们都需要进行某些操作, 比如, 位置可能需要平移, 颜色可能需要渐变等, 而这些一定都是矩阵运算, 而不同的处理就对应着不同的着色器, 嗯, 就是酱紫.
而我们今天只需要用到顶点着色器和片元着色器.
以顶点着色器为例来介绍我们这次需要掌握的 GLSL 语法
vertex Shader (顶点着色器)
将下面的内容写到一个名为 basic.vert 的文件中 (emmm, 最后一行可能要有一个空行, 这个不是语法要求, 这个是该程序所需, 后面会讲到)
- #version 430
- layout (location=0) in vec3 VertexPosition;
- layout (location=1) in vec3 VertexColor;
- out vec3 Color;
- void main()
- {
- Color = VertexColor;
- gl_Position = vec4(VertexPosition,1.0);
- }
顶点着色器: 对于绘制命令传输的每一个顶点, OpenGL 都会调用一个顶点着色器来处理顶点相关的属性数据.
顶点着色器的复杂与否与其他着色器的活跃与否有关, 所以, 它可能会非常简单, 如: 值将数据复制并传递到下一个阶段, 这类似于 pass-through shader, 就是, 走一下形式, 数据简单流过, 无任何操作. 它也可能非常复杂, 如: 执行大量的计算来得到顶点在屏幕上的位置, 或者通过光照的计算来判断顶点的颜色.
注: 一个复杂应用程序可能有多个顶点着色器, 但统一时刻只能有一个顶点着色器起作用
in 从应用程序 (我们习惯将主程序称为应用程序) 中传递进来的数据
out 从该着色器传递出去的新数据(相当于 C++ 程序中的 return 变量)
第一句 #version 430 指的是 opengl 或 GLSL 的版本为 4.3(后面用程序获取本机的版本号)
因为可能会有很多个 in 对象, 而我们知道应用程序中需要获取它的位置, 所以就要有 Location, 即下标, 索引 号, 而 layout 就是一个 GLSL 语言的关键字用于人工确定索引的排布方式. 上一节中, 提到过 Location:(https://www.cnblogs.com/lv-anchoret/p/9221504.html)
vec3 就是一个一维的有三个元素的数值类型(三元组)
gl_Position 就是 OpenGL 系统内部的量, 需要将处理好的量传递给它, 它和 out 量都会传递到下面继续处理.
vertex shader takes the input attributes VertexPosition and VertexColor and passes them along to the fragment shader via the output variabes gl_Position and Color.
而关于主程序代码中, 对着色器的处理与之前固定管线的结合, 我们也并不需要担心太多, 主程序中, 单纯从代码上说它们可能交融的不是很紧密.
我们需要用一些 window 工具包来构建脚本, 跨平台的有 GLFW,GLUT,FLTK,QT 或者 wxWidgets. 我们这里继续选用 GLUT / freeglut
Compiling a shader
the diagram of this recipe
第一步 # 创建一个着色器对象 Create the shader object
vertShader = glCreateShader(GL_VERTEX_SHADER);
函数的参数为着色器类型, 该函数返回一个着色器对象的映射量(GLuint 型), 有时称之为对象句柄(object"handle").
如果创建失败, 将返回一个 0, 所以我们可以根据返回值来检验是否创建成, 如果没有创建成功, 则返回一个正确的信息, 以用于检验程序错误.
第二步 # 拷贝脚本源程序 Copy the source code
- const GLchar* shaderCode = loadShaderAsString("basic.vert");
- const GLchar* codeArray[] = {shaderCode};
- glShaderSource(vertShader, 1, codeArray, NULL);
loadShaderAsString(); 似乎是在 flew 头文件中才有的吧, 我也不是很清楚, 原则上应该是这么整的, 但是出于, 我也不知道它的头文件, 所以就这个也是自己写的.
它的目的就是将以参数为名的文件信息内容读取出来.(因为我也不知道这个东西, 之前一直以为是类型转换之类的, 然后就出现了很多错误)
glShaderSource(); // load the source code into the shader object
因为这个函数支持一次性加载多个源代码, 所以我们把读取的字符串指针放入一个数组中, 即 codeArray
parameter 1: 创建的着色器对象的映射值(即: shader_Obj handle)
para 2: Array 数组里面元素的个数
para 3: Array 数组
para 4: 一个数组, 记录 Array 数组中每个元素的长度. 如果你读取的每个源代码字符串是以 null 结尾的, 那么此处可以填写 NULL
第三步 # 编译着色器 Compile the shader
glCompileShader(vertShader);
第四步 # 验证编译状态 Vertify the compilation status
- GLint result;
- glGetShaderiv(vertShader, GL_COMPILE_STATUS, &result);
- if (GL_FALSE == result)
- {
- fprintf(stderr, "Vertex shader compilation failed!\n");
- GLint logLen;
- glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, &logLen);
- if (logLen> 0)
- {
- char* log = new char[logLen];
- GLsizei written;
- glGetShaderInfoLog(vertShader, logLen, &written, log);
- fprintf(stderr, "Shader log:\n%s", log);
- delete[] log;
- }
- }
- glGetShaderiv(shader_Obj handle, 要查询的状态的枚举量, 用于记录结果的参数);
之后就是逐步验证着色器编译时候的每一个状态, 直到找到错误点为止, 然后提示相应的错误信息
Linking a shader program
the diagram of this recipe:
第一步 # 创建一个着色器程序 Create the program object
GLuint programHandle = glCreateProgram();
第二步 # 关联着色器与着色器程序 attach the shaders to the program object
- glAttachShader(programHandle, vertShader);
- glAttachShader(programHandle, fragShader);
就是将着色器对象句柄与着色器程序句柄关联在一起
第三步 # 链接着色器程序 Link the progra
glLinkProgram(programHandle);
检验, 如果没问题, 则进行下一步
第四步 # Install the program into the OpenGL pipline
glUseProgram(programHandle);
到此, 我们拥有了一个完整的 OpenGL 管线, 然后就可以渲染了.
如果要删除句柄可以如下这么做
glDeleteProgram(programHandle); 如果正在使用该着色器程序, 那么, 将会当它 不再使用的时候删除
glDeleteShader 同上
代码: 做了点小小的整合
顶点着色器: basic.vert
- #version 430
- layout (location=0) in vec3 VertexPosition;
- layout (location=1) in vec3 VertexColor;
- out vec3 Color;
- void main()
- {
- Color = VertexColor;
- gl_Position = vec4(VertexPosition,1.0);
- }
片元着色器: basic.frag
- #version 430
- in vec3 Color;
- out vec4 FragColor;
- void main()
- {
- FragColor = vec4(Color, 1.0);
- }
注意两者后面要空一行空行, 原因在前面说过了
主程序:
与本节无关的代码, 如果有什么问题, 可以参见上一节 笔记 < 1>:https://www.cnblogs.com/lv-anchoret/p/9221504.html
- // 配置代码
- #if _MSC_VER>=1900
- #include "stdio.h"
- _ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
- #ifdef __cplusplus
- extern "C"
- #endif
- FILE* __cdecl __iob_func(unsigned i) {
- return __acrt_iob_func(i);
- }
- #endif /* _MSC_VER>=1900 */
- //code-list
- //using namespace std;
- #include <iostream>
- #include <fstream>
- using namespace std;
- #include <vgl.h>
- GLint vertShader, fragShader;
- GLuint vaoHandle;
- float positionDate[] =
- {
- -0.8f,-0.8f,0.0f,
- 0.8f,-0.8f,0.0f,
- 0.0f,0.8f,0.0f,
- };
- float colorDate[] =
- {
- 1.0f,0.0f,0.0f,
- 0.0f,1.0f,0.0f,
- 0.0f,0.0f,1.0f,
- };
- void init();
- void Display();
- void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName); // 编译着色器
- void _Link_Shader_(); // 链接着色器
- bool readFile(const char*, string&); // 读取文件内容的函数
- int main(int argc, char** argv)
- {
- glutInit(&argc, argv);
- glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
- glutInitWindowSize(1024, 768);
- glutInitWindowPosition(20, 20);
- glutCreateWindow("test");
- if (glewInit())
- {
- cout <<"Error!" << glGetString(glewInit()) << endl;
- exit(EXIT_FAILURE);
- }
- cout << "GL version:" << glGetString(GL_VERSION) << endl; // 查询本机 OpenGL 版本
- init();
- _Compiling_Shader_(vertShader, GL_VERTEX_SHADER,"basic.vert");
- _Compiling_Shader_(fragShader, GL_FRAGMENT_SHADER, "basic.frag");
- _Link_Shader_();
- glutDisplayFunc(Display);
- glutMainLoop();
- }
- void init()
- {
- GLuint vboHandles[2];
- glGenBuffers(2, vboHandles);
- GLuint postionBufferHandle = vboHandles[0];
- GLuint colorBufferHanle = vboHandles[1];
- glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle);
- glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), positionDate, GL_STATIC_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle);
- glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), colorDate, GL_STATIC_DRAW);
- glGenVertexArrays(1, &vaoHandle);
- glBindVertexArray(vaoHandle);
- glEnableVertexAttribArray(0);
- glEnableVertexAttribArray(1);
- glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle);
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
- glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle);
- glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
- }
- void Display()
- {
- glClear(GL_COLOR_BUFFER_BIT);
- glBindVertexArray(vaoHandle);
- glDrawArrays(GL_TRIANGLES, 0, 3);
- glutSwapBuffers();
- }
- bool readFile(const char* filename, string& content)
- {
- ifstream infile;
- infile.open(filename);
- if (!infile.is_open())return false;
- char ch;
- infile>> noskipws;
- while (!infile.eof())
- {
- infile>> ch;
- content += ch;
- }
- infile.close();
- content += '\0';
- return true;
- }
- void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName)
- {
- shaderHandle = glCreateShader(GL_Shader_type);
- // 检查编译情况
- if (0 == shaderHandle)
- {
- fprintf(stderr, "Error creating shader.\n");
- exit(EXIT_FAILURE);
- }
- string ShaderCode;
- if (!readFile(shaderName, ShaderCode))
- {
- cout <<"readFile Error!" << endl;
- exit(EXIT_FAILURE);
- }
- const GLchar* shaderSource = ShaderCode.c_str();
- const GLchar* codeArray[] = { shaderSource };
- glShaderSource(shaderHandle, 1, codeArray, NULL);
- glCompileShader(shaderHandle);
- GLint result;
- glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &result);
- if (GL_FALSE == result)
- {
- fprintf(stderr, "shader compilation failed!\n");
- GLint logLen;
- glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLen);
- if (logLen> 0)
- {
- char* log = new char[logLen];
- GLsizei written;
- glGetShaderInfoLog(shaderHandle, logLen, &written, log);
- fprintf(stderr, "Shader log:\n%s", log);
- delete[] log;
- }
- }
- }
- void _Link_Shader_()
- {
- GLuint programHandle = glCreateProgram();
- if (0 == programHandle)
- {
- fprintf(stderr, "Error creating program object.\n");
- exit(1);
- }
- glAttachShader(programHandle, vertShader);
- glAttachShader(programHandle, fragShader);
- glLinkProgram(programHandle);
- GLint status;
- glGetProgramiv(programHandle, GL_LINK_STATUS, &status);
- if (GL_FALSE == status)
- {
- fprintf(stderr, "Failed to link shader program!\n");
- GLint logLen;
- glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen);
- if (logLen> 0)
- {
- char* log = new char[logLen];
- GLsizei written;
- glGetShaderInfoLog(programHandle, logLen, &written, log);
- fprintf(stderr, "Program log:\n%s", log);
- delete[] log;
- }
- }
- else
- glUseProgram(programHandle);
- }
如果出现_fprintf,_sscanf 或者__iob_func 问题, 请参见: https://www.cnblogs.com/lv-anchoret/p/8729384.html
如果没有 vgl.h 的, 见如下
- #ifndef __VGL_H__
- #define __VGL_H__
- // #define USE_GL3W
- #ifdef USE_GL3W
- #include <GL3/gl3.h>
- #include <GL3/gl3w.h>
- #else
- #define GLEW_STATIC
- #include <GL/glew.h>
- #ifdef _MSC_VER
- # ifdef _DEBUG
- # if (_MSC_VER>= 1600)
- # pragma comment (lib, "glew_static_vs2010_d.lib")
- # else
- # pragma comment (lib, "glew_static_d.lib")
- # endif
- # else
- # if (_MSC_VER>= 1600)
- # pragma comment (lib, "glew_static_vs2010.lib")
- # else
- # pragma comment (lib, "glew_static.lib")
- # endif
- # endif
- #endif
- #endif
- #define FREEGLUT_STATIC
- #include <GL/freeglut.h>
- #ifdef _MSC_VER
- # ifdef _DEBUG
- # if (_MSC_VER>= 1600)
- # pragma comment (lib, "freeglut_static_vs2010_d.lib")
- # else
- # pragma comment (lib, "freeglut_static_d.lib")
- # endif
- # else
- # if (_MSC_VER>= 1600)
- # pragma comment (lib, "freeglut_static_vs2010.lib")
- # else
- # pragma comment (lib, "freeglut_static.lib")
- # endif
- # endif
- #endif
- #define BUFFER_OFFSET(x) ((const void*) (x))
- #endif /* __VGL_H__ */
如果下载的是 9th 版本的 OpenGL 库文件, 那么 vgl 是不完整的
这个程序的运行效果在一开始有, 就放了, 放一个错误提示, 当初因为一个函数解读错误导致的
可以看出来错误定位还是非常精准的
今天就到这里
感谢您的阅读, 生活愉快~
来源: https://www.cnblogs.com/lv-anchoret/p/9231780.html