背景知识
OpenGL的可编程管线处理流程如下:
- 顶点输入:在CPU上输入顶点数据,创建顶点缓冲对象VBO,将其绑定到对应目标上,该VBO可以管理GPU上的内存;通过调用函数可将输入顶点数据复制到GPU的内存中,并利用VBO对这些数据进行管理;
- 顶点着色器:从顶点数据中直接接受输入,对顶点坐标进行变换,使用location指定输入变量,并为它的输入提供额外的layout标志从而将输入连接到顶点数据;
- 图元装配阶段:将顶点着色器输出的所有顶点作为输入,将所有点装配成指定的图元形状(图元,用于提示OpenGL输入数据所表示的渲染类型,如GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP);
- 几何着色器:将图元形式的一系列顶点的几何作为输入,通过产生新顶点构造出新的或其他的图元来生成其他的形状;
- 光栅化阶段:将图元映射为最终屏幕上相应的元素,生成供片段着色器使用的片段(一个片段是OpenGL渲染一个像素所需的所有数据);在片段着色器处理之前会进行裁剪;
- 片段着色器:通常包含3D场景数据(如光照、阴影、光的颜色等),这些数据用于计算最终像素的颜色;
- Alpha测试和混合阶段:检测片段的对应深度值,判断这个像素在其他物体的前面还是后面,决定是否应该丢弃;同时该阶段价检查alpha值并对物体进行混合;
源码详解
|
|
创建着色器程序,将所有的着色器连接到该对象上;
创建着色器对象,shaderType选择GL_VERTEX_SHADER表示顶点着色器,GL_FRAGEMENT_SHADER表示片段着色器;
编译着色器对象前必须定义它的代码源;
检查着色器相关的错误;
将编译好的shader对象绑定到程序对象上;
编译好所有的shader对象并将它们绑定到程序中,之后完成连接。在实现程序连接之后可以通过调用glDetachShader和glDeleteShader解绑并删除着色器,若着色器绑定在程序上,只调用删除函数只会标记该着色器等待删除,只有调用解绑之后才会将其置零并删除;
检查和程序相关的错误;
检查当前的管线状态程序是否可以被执行;
使用回调函数将连接好的着色器程序设置到管线声明中,该着色器程序将在所有的绘制回调函数中一直生效知道你用另一个程序替换掉它或者使用glUseProgram指令将其置为NULL明确禁用它。
告诉编译器的目标GLSL编译器版本是3.3;
在顶点着色器中,使用location指定输入变量,使得可以在CPU上配置顶点属性;使用layout将变量与顶点数据链接,这里位置变量的属性位置为0;
让编译器知道缓冲区中顶点的哪个属性和shader中声明的哪个属性进行映射匹配有两种办法:
- 可以明确设置它(为0),这样就在应用中使用一个写死的编码值(通过调用glVertexAttributePointer调用第一个参数);
- 简单在shader中声明“in vec3 Position”,之后使用glGetAttribLocation检索应用中这个是属性的实时位置;在复杂应用中最好让编译器定义属性的索引标志并实时检索它们,从而可以更加容易从多个来源整合shader着色器;1void main()
可以通过连接多个shader来创建着色器,但是每个着色器阶段(顶点着色器,几何着色器,片段着色器)有且只有一个main函数作为着色器的入口。
这里对输入顶点的位置做固定编码转换,‘gl_Postion’是一个内置的变量,用来保存顶点位置,光栅器会查找这个变量并用在一系列变换之后将它作为屏幕空间的位置。此处需要设置W值为1.0。将物体从三维投影到二维的过程需要经过两个独立的阶段:首先,将所有顶点乘以投影变换矩阵;然后,在顶点到达光栅器之前GPU会自动对位置属性进行‘透视分割’,即使用W值分割gl_Position的所有其他分量元素。
片段着色器定义每个片段(像素)的颜色,输出的颜色通过上面代码中声明的变量完成,在这些变量上设置的值将会被光栅器接受最后写入帧缓存中;
一致变量
uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,一致变量与普通属性的区别:
- 普通变量:包含的数据是顶点具体化的,因此在每个着色器引入时,它们将从顶点缓冲区加载一个新的值;
- 一致变量:uniform是全局的,在整个绘制回调的过程中保持不变,主要用于保存光照参数(光的位置和方向)、变换矩阵、材质对象的handle等数据;
GLUT只有在最大最小化窗口或者从另一个窗口后面重新出现等事件发生时才会重新调用渲染回调函数,若应用启动后没有在窗口布局中做任何变化则渲染回调只会执行一次。若想要多次重复改变一个变量的值,需要通过再注册一个闲置的回调函数,该函数在GLUT没有收到任何窗口事件的时候被调用。1glutIdleFunc(RenderScenceCB);
将渲染回调函数注册为全局闲置回调,若使用一个专用的闲置回调则需要在它后面添加一个glutPostRedisplay(),该函数标记当前的窗口需要被重新绘制,当下一个GLUT的主循环开始时就调用渲染回调函数进行窗口绘制。
我们无法直接获取着色器的内容,因而也无法改变它的变量值,当编译着色器对象时,GLSL编译器会给每一个一致变量分配一个索引,在着色器内部表示中,是通过变量的索引值来获取编译器内部的变量的,该索引可通过glGetUniformLocation函数设置程序对象的句柄参数和变量名参数来获取,若有错误则返回-1.
glUniform{1234}{if},该实例函数可以将浮点数或整型数赋给不同维度的向量作为参数;