opengl教程翻译 #4着色器

背景知识:编程

从这个课程开始,每个咱们执行的效果和技术,都会用到着色器。着色器是当前开发3D图形的流行方式。从某种程度来讲,这是一个退步,由于大多数3d功能函数都提供了固定功能管线,仅仅须要开发人员配置一些参数(好比光线属性,旋转值等),而如今必须都得由开发人员制定(经过着色器),然而,这种可编程性却能带来更多的灵活和创新。数组

可视化的OpenGl可编程管线以下图:
app

 

【顶点处理器】负责各个顶点着色器的执行,以及每一个顶点经过管线(数量取决于用于draw call的参数)。顶点着色器对须要渲染什么图形是一无所知的。另外,顶点处理器中你不能丢弃顶点。每个顶点刚好一次进入顶点过程,经过变换进入管线的下一阶段。编辑器

下一阶段是【几何处理器】。在这一阶段数据是做为完整的图元信息(即它的全部顶点)和相邻顶点提供给着色器的。这使技术必须考虑到除顶点自己以外的更多额外信息。几何着色器还能输出与以前draw call中选择的不一样的拓扑结构。例如,你能够提供给一个点的列表,而后根据这些点生成2个三角形(好比一个正方形,这个技术叫广告牌)。此外,你能够给每一个几何着色器传多个顶点,根据你选择的拓扑产成多种几何图元。函数

管线的下一阶段是【剪裁】。这是一个有明确任务的固定功能--在以前咱们教程中看到的标准盒子中,剪裁图元。它会在近Z面和远Z面之间剪裁它们。也能够申请自定义剪裁面,有不那么作的剪发。在此阶段中,留下来的顶点的位置会被映射到屏幕空间坐标,接着光栅化根据它们的拓扑结构渲染它们到屏幕。例如,若是是三角形,这意味着把三角形内部全部的点找出来。对于每一个点光栅化调用【片断处理器】。在这里你能用纹理采样或其余技术肯定像素的颜色。布局

这三个可编程的阶段(顶点,几何,片断处理器)都是可选的。若是你没有对它们绑定着色器,那么某些默认的函数功能会被执行。测试

着色器管理程序跟C/C++程序的建立方式很是相似。首先你得写一段着色器代码,并使其可被你的程序获取。这步能够经过在源程序代码里简单的包含一个字符串数组文本,或者把一个额外的文本文件加载进来(而后一个字符串数组中)。接着你就能把着色器代码逐个翻译成着色器对象。以后你能够把着色器连接到一个单一的程序,然后把它加载进GPU。连接着色器使驱动有机会根据着色器之间的关系裁剪、优化它们。例如,你可能会有一个顶点着色器发起了法向量,匹配一个片断着色器忽视了法向量。在这种状况下,驱动中的GLSL编译器会移除着色器中法向量相关函数以便可以更快地执行顶点着色器。若是以后连接到另外一程序的着色器又与一个用了法向量的片断着色器匹配,则会产生一个不一样的顶点着色器。优化

代码实践:ui

GLuint ShaderProgram = glCreateProgram();
咱们经过建立一个程序对象开始配置一个这个过程。咱们会把全部着色器一块儿连接进这个对象中。操作系统

GLuint ShaderObj = glCreateShader(ShaderType);
咱们用上面的调用建立了2额着色器对象。一个是GL_VERTEX_SHADER类型的着色器,另外一个是GL_FRAGMENT_SHADER类型的。对于二者来讲,指定着色器源程序和编译着色器的过程是同样的。

const GLchar* p[1];
p[0] = pShaderText;
GLint Lengths[1];
Lengths[0]= strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);
在编译着色器对象以前咱们必需要指定它的源代码。函数glShaderSource把着色器对象当作一个参数以及在指定源程序上提供了灵活性。源程序能够分散在几个字符数组中,而你须要提供一个指针数组指向这些字符数组,同时还须要一个包含每一个对应的字符数组的长度的整型数组。简单起见咱们用只有一个字符串的数组做为整个着色器的源程序,同时对于指针和长度咱们也仅仅用了一个元素。

glCompileShader(ShaderObj);
编译着色器却是至关容易...

GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar InfoLog[1024];
glGetShaderInfoLog(ShaderObj, sizeof(InfoLog), NULL, InfoLog);
fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
}
然而,如预想的那样,你经常会获取一些编译错误。上面这一片代码获取编译状态和把编译中遇到的错误呈现出来。

glAttachShader(ShaderProgram, ShaderObj);
最终,咱们把编译好的着色器链接到程序对象上。这很是像在makefile中为连接过程指定对象列表。因为咱们在这没有makefile因此咱们模拟它的编程行为。只有被链接的对象参与到连接过程。

glLinkProgram(ShaderProgram);
在编译了全部着色器对象以及把它们链接到程序后,最终咱们能够连接它了。注意连接了程序后你能够调glDetachShader和glDeleteShader丢弃中间着色器对象。OpenGL驱动在大多数对象产生时维持着一个引用计数。若是一个着色器被建立后立刻又删除,那么驱动会丢弃它,可是若是它被链接到一个程序了,调用glDeleteShader只会标记为删除,你须要调用glDetachShader使引用计数降到0,它才会被移除。

glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (Success == 0) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
}
注意咱们检查程序相关错误(好比链接错误)跟着色器程序错误有所不一样。咱们用glGetProgramiv代替glGetShderiv,用glGetProgramInfoLog代替glGetShaderInfoLog.

glValidateProgram(ShaderProgram);
你能够问问你本身为何咱们在程序成功连接后还须要验证它。差异在于连接检查的是着色器间的错误,而上面的调用检查在当前管线状态中程序是否能运行。在一个更复杂的应用中,多种着色器以及各类状态在发送变化,最好在每个draw call前验证一下。在咱们的简单app中咱们就检查了一次。固然了,你可能只是但愿在开发过程当中多作这种检查,而在最终的产品中要避免这种开销。

glUseProgram(ShaderProgram);
最后,用上面这个调用把已连接到程序中的着色器设置到管线状态中。如今这个程序会一直为全部draw calls起做用,知道你用另外一个替换它或者调用glUseProgram(NULL)明确地禁止它使用(同时开启固定功能管线)。若是你建立了一个只包含一个类型的着色器程序,其余阶段操做系统会用默认固定管线。
咱们已经完成了涉及到OpenGL着色器管理的调用的实践。剩下的课程涉及到顶点着色器和片断着色器的内容(源码在'pVS'和'pFS'中)。

#version 330
这告诉编译器咱们在针对GLSL的3.3版本。若是编辑器不支持它会发出一个错误。

layout (location = 0) in vec3 Position;
这段声明出如今顶点着色器中。它声明了一个3元素浮点向量的特定顶点属性,在着色器中被认为是一个“位置”。“特定顶点”意味着每次在GPU中调用着色器,都会从缓冲中提供一个新的顶点属性。声明的第一小节,layout(location = 0),创建了属性名和缓冲中属性数据的绑定。在诸如咱们顶点包含不少属性(位置、法向量、纹理坐标等等)的时候,这是必须的。咱们必须让编译器知道缓冲中的哪个顶点属性被映射成着色器程序中的声明属性。这时有两个方法。第一是像咱们目前作的那样,明确地设置它的索引(为0)。在这种状况下咱们在应用中写死一段代码(像咱们以前在调用glVertexAttributePointer时第一个参数作的那样);第二是不要它了(只在着色器中简单的声明'in vec3 Position')接着用glGetAttribLocation在运行时询问location值。在这种状况下咱们须要给glVertexAttributePointer一个返回值,而不是写死一段代码值。在这里咱们选择了简单的方式,可是对于更加复杂的应用最好让编译器在运行时判断、询问属性索引。当遇到不少没有布局好的代码时,整合起来会更简单。

void main()
你能经过把各类着色器对象连接到一块儿来建立你的着色器。然而,只能有一个主函数做为每一个着色器(顶点、集合、片断)的入口。例如,你能够用几个函数建立一个轻量级的库,带着你提供的着色器连接它,但这些都不是“main”函数。

gl_Position = vec4(0.5 * Position.x, 0.5 * Position.y, Position.z, 1.0);
这里咱们把传来的顶点位置作一个写死的变换。咱们让x和y值减半,留着y值不变。 'gl_Position'是一个内置变量用以提供包含同类(x,y,z,w份量)顶点位置。光栅化会寻找这个变量,而后把它用做在屏幕空间中的坐标(在一些转换以后)。把x和y缩减到一半意味着咱们只能看到以前课程中四分之一大小的三角形。获取从3D到2D的投影其实是由2个独立的阶段中完成的。首先,你须要把你全部的顶点乘以投影矩阵(咱们再其余课程中会开发),而后GPU会在点属性到达光栅器以前对它们自动执行“透视分离”。这意味着它经过w份量选项把gl_Position的其余选项分离开。在这个课程中咱们还未在顶点着色器中作任何投影变换,但却不能阻止透视分离运行。不论如何,gl_Position这个从顶点着色器输出的值都会被HW用w份量分割。咱们须要记住这点,不然咱们拿不到咱们期待的结果。为了规避透视分离的影响,咱们能够设w为1.0。1.0分离不会影响位置向量中的其余份量,而依然留在咱们的标准盒子里。
若是一切正常工做,3个值为 (-0.5, -0.5), (0.5, -0.5) 和 (0.0, 0.5)的顶点将会到达光栅器。由于全部的顶点恰好都在标准盒子里,因此裁剪就不用作任何事了。 这些值被映射成屏幕空间坐标,而后光栅器开始跑遍全部三角形内部的点。对于每个点,片断着色器都会执行。下面的代码来自片断着色器。

out vec4 FragColor;
片断着色器的工做经常是肯定片断(像素)的颜色。此外,片断着色器能够一并丢弃像素,或者改变它的Z值(会影响到随后Z测试的结果)。输出颜色是经过声明上面的变量来作的。4个份量表明R,G,B,A。你设置进变量的值,会被光栅器接收到,最终写入帧缓冲。

FragColor = vec4(1.0, 0.0, 0.0, 1.0);
在以前几个课程中没有片断着色器,因此全部东西都默认用白色来绘制。在这里咱们设置片断颜色为红色。

 

译者总结:

1.OpenGL渲染管线的顺序是:
顶点着色器--几何着色器--剪裁--光栅化(包含片断着色器)

2.建立并使用一个着色器的完整过程是:
1.建立一个着色器运行程序对象; GLuint ShaderProgram = glCreateProgram();
2.写一段相似于C/C++的着色器代码:
3.建立一个着色器对象(能够是顶点或片断),把代码赋予到对象上; glShaderSource(ShaderObj, 1, p, Lengths);
4.编译着色器对象; glCompileShader(ShaderObj);
5.把着色器对象连接到着色器运行程序对象中; glAttachShader(ShaderProgram, ShaderObj);
6.把着色器设置到管线状态中。 glUseProgram(ShaderProgram);

3.错误检测:
一种是用于捕获着色器程序的语法错误,如glGetShaderiv、glGetShaderInfoLog;
另外一种用于捕获运行程序的逻辑错误,如glGetProgramiv、glGetProgramInfoLog,这种相似于assert。

4.顶点着色器的做用:输入顶点数据,输入几何图元。经过设置顶点着色器中的指令,实现投影变换、透视分离等。 几何着色器的做用:输出拓扑结构。 片断着色器的做用:输入几何图元,肯定像素颜色。

相关文章
相关标签/搜索