OpenGL超级宝典学习笔记——新的模式

在传统上,图形硬件设计的目标是快速地执行相同的硬编译的计算指令集。计算的步骤可以被跳过,参数可以被调整,但计算本身却是固定的。所以旧式的GPU设计被称为是“固定功能”的。现在的趋势是朝着通用图形处理器的方向发展。就像CPU一样,GPU也可以用任意的指令序列来执行图形计算。GPU和CPU最大的区别是,GPU的浮点数计算能力更强。

在OpenGL2.0之前是固定函数渲染管线,在OpenGL2.0之后就是可编程函数渲染管线了。

 

走出旧的模式

在替换掉旧的模式之前,我们来回顾一下传统的OpenGL渲染管线是如何运作的。在第一阶段的是基于顶点操作,然后是图元的光栅化产生片段,最后在写到帧缓冲区前,执行片段的纹理,雾和其他的操作。如下图,下面分别讨论基于顶点和基于片段的操作。

OpenGL超级宝典学习笔记——新的模式

 

固定的顶点处理

    基于顶点的阶段以一系列顶点的属性作为输入。这些输入包括物体空间坐标,法线,主颜色,辅助颜色,纹理坐标。最终处理输出的结果为裁剪空间坐标,正面和背面的主颜色和辅助颜色,雾坐标,纹理坐标,以及点的大小。这个处理过程被分为四个阶段。

 

顶点变换

    在传统的固定函数管线中,顶点坐标从物体空间转换到裁剪空间。首先乘以模型视图矩阵转换到视觉空间,然后再乘以投影矩阵变换到裁剪空间。与者两个矩阵相乘是固定的程序,要“跳过”这个过程,就是把这两个矩阵都通过glLoadIdentity设置为单位阵。这样就不会产生影响,输出的结果与输入的结果相同。

    为了光照的处理,每一个顶点的法线也需要进行变换,从物体空间变换到视觉空间。法线乘以倒转置的模型视图矩阵,在此之后可能需要重新调整尺寸或者规格化。光照需要的法线是单位长度的,所以除非你传入的法线是单位向量而且模型视图矩阵不会改变它的长度,否则你需要对其调整尺寸或者规格化。

 

光照

    光照把顶点的颜色、法线和坐标作为它的原始数据输入。它的输出是主颜色和辅助颜色、在某些情况下正面和背面的颜色也不一样。通过材料的属性,光照的属性和一些glEnable/glDisable的开关来控制这个过程。

    光照是高可配置的。我们可以开启多个光源,每一个又可以通过大量的参数来设置器位置,颜色,类型,也可以通过材料属性来模拟不同的表面。

      我们可以关闭光照来跳过这个阶段。但只要光照被开启了固定的方程式就会被使用。详细的参考光照那一节。

 

纹理坐标生成与变换

    在这个阶段我们可以选择让OpenGL为我们生成纹理坐标。有几种生成公式可以选择。我们可以为每个纹理坐标成分选择不同的生成方式。如果关闭纹理自动生成,那么手工附加在顶点上的纹理坐标将其作用。随后纹理坐标会经过纹理矩阵的变换。如果纹理矩阵是单位阵则纹理坐标不会改变。

 

裁剪

    如果经过上面的变换之后,顶点落在了可视区域之外,则将被裁剪掉。被裁减掉的顶点将被丢弃,然后根据图元的类型生成与裁剪边缘相交的顶点。颜色、纹理坐标和其他的顶点属性将通过插值的方式重新赋予到新的顶点上。

OpenGL超级宝典学习笔记——新的模式

 

固定的片段处理

    在这个阶段把片段和关联的数据作为输入。这些关联的数据由插值后的线和三角形,一个或多个纹理坐标,主颜色和辅助颜色,雾坐标组成。每个片段处理后的结果是单一的颜色值,然后被传送到随后的深度测试,alpha混合,模板测试等操作中。这个阶段也被分为四个部分。

 

纹理应用和纹理环境

    纹理应用是最重要的片段操作。在这里你把所有片段的纹理坐标和它的主颜色作为输入,输出是一个新的主颜色。这个过程将受启用那个纹理单元,纹理单元绑定的是什么图片,以及纹理环境。

    对于每个被启用的纹理单位,绑定到这个单位的1D、2D、3D或立方体贴图纹理作为纹理查找的来源。基于单元上的纹理格式和指定的纹理函数,查找出来的纹理结果将替换或者与片段的主颜色进行混合。

    有许多的可配置的参数影响着纹理的查找,例如纹理的环绕模式,边界颜色,过滤器,mipmap,深度纹理等。

 

颜色求和

    颜色求和阶段有两个输入:主颜色和辅助颜色,输出是单一的颜色。如果启用了颜色求和或者启用了光照,那么主颜色和辅助颜色的红、绿、蓝成分将会相加,然后截取在范围[0,1]。如果颜色求和未被启用,那么主颜色及其alpha值将直接输出,辅助颜色值和alpha值将不会被使用。

 

雾应用

如果开启了雾。那么片段的颜色将会和雾的颜色进行混合。雾颜色取决于雾因子以及三个硬编码的公式:线性的,指数的,二次指数的。这些公式根据当前雾坐标和雾因子来计算出雾颜色。

 

反走样应用

    最后,如果片段所属的图元开启了反走样例如glEnable(GL_LINE_SMOOTH)等。那么会有一段关联的数据是覆盖值。这个值通常是1.0。但在光滑的点,线,面,这个覆盖值是0.0到1.0之间,片段的alpah值和这些覆盖值相乘,然后进行混合来产生光滑的边缘。

 

新的管道

    自OpenGL2.0之后就引入了新的方式来替换就到固定函数管道——着色器。着色器其实就是一段自定义的程序来替换掉固定函数管道的各阶段。

下图是用新的可编程的着色器替换掉硬编码的固定阶段。

OpenGL超级宝典学习笔记——新的模式

 

可编程的顶点着色器

    现在顶点以及顶点的属性将作为顶点着色器的输入。顶点着色器产生纹理坐标,颜色,点的大小,雾坐标,然后传到裁剪器中。一个顶点着色器替换掉了固定的顶点变换,光照,纹理坐标处理。

 

替换顶点变换

    顶点的变换将不再是固定的函数,要做什么完全取决于你。你可以什么都不做不输出任何东西(也不会画任何东西),如果要画物体,那么最小的操作是输出裁剪坐标。通常情况下,模拟固定的函数管道的话,我们会把顶点乘以投影矩阵和模型视图矩阵。但这些不是必须的,如果你输入的已经是计算好的裁剪坐标,那么我们就不需要执行这些操作,把输入位置拷贝到输出位置就行了。我们也可以在这里把笛卡尔坐标转换成极坐标,然后进行计算。

 

替换光照

    如果你并不关心顶点的颜色属性(例如你只需要这些顶点做遮挡查询)那么你可以不进行光照计算的步骤,不输出任何颜色,在此之后都不使用这些数据(PS:如果后面使用到了这些数据,则行为是未定义的,因为是垃圾数据)。 如果颜色不需要光照计算,我们可以直接把输入的颜色拷贝到输出的颜色。

    在这里也可以有无穷多种方式进行颜色的变换,这取决于你的程序。

 

替换纹理坐标的处理

    如果不需要生成纹理坐标,则不用再顶点着色器对纹理坐标进行编程。 如果不需要进行任何纹理坐标的变换,那可以把输入的纹理坐标拷贝到输出的部分。这个过程可以尽量的精简,不浪费资源去做无用功。例如 你的GPU支持8个纹理坐标,但再后面的管道中,你只用到其中三个,但我们就没必要输出另外的五个。

经过上面的介绍,大概了解了,顶点着色器的输入和输出,以及其中的处理环节。顶点着色器与固定函数管道相比,提供了极大的灵活性,通过良好的着色器编程,我们可以节省资源,提高性能,并得到我们想要的效果。

 

固定功能胶水

 

    在顶点着色器和片段着色器之间,还有一组固定的功能阶段,来连接着两个着色器。在执行顶点着色器之后,需要对其输出进行裁剪,在裁剪的过程中,去移除在可视区域之外的顶点,并添加一些顶点。然后进行透视除法,把坐标变成规格化坐标,然后进行视口变换和深度范围的变换,最后产出屏幕空间的坐标。然后进行光栅化。

    光栅化是一个固定的阶段,把处理后的图元上的顶点光栅化成片段。无论点,线或多边形图元都会在这个阶段产出片段,并插入合适的颜色和纹理坐标到片段中。

OpenGL超级宝典学习笔记——新的模式

在高度分挌化的物体中,可以一个小三角形会被映射为一个片段。在绝大多数情况下,片段数总是比顶点数多的。但凡事都有例外。光栅化也负责制造有宽度的线和点。它也会应用线和点的点画(stipple)模式。它还负责为光滑的点,线和多边形产生边缘的覆盖值,这些覆盖在后面片段着色器的alpha混合中会用到。如果需要,光栅化器会裁剪正面或者反面的多边形,应用多边形偏移。

    除了点,线,面,光栅化也负责产生位图和像素矩形(使用glDrawPixels绘制的)的片段。

 

片段着色器

      与固定功能管线相同的,雾坐标,纹理坐标和颜色可用于片段着色器。最后输出单一的颜色与先前的固定函数管线的雾阶段输出相同。在片段着色器,我们可以选择自己的方法来处理这些片段。

 

替代纹理

    纹理查找是片段着色器中一项重要的功能。绝大部分与固定函数的方式相同,在纹理着色器外,设置纹理的状态和纹理环境,以及纹理的图片。主要的不同是,你可以在着色器中决定何时、是否执行纹理查找,以及纹理坐标如何使用。

    第1个纹理坐标不一定用于索引第1个纹理图片。你可以在不同的纹理中使用同一个纹理坐标,也可以在同一个纹理中使用不同的纹理坐标。甚至,你可以在运行时去计算纹理坐标。这种灵活性在固定函数的方式是不可能实现的。

    在之前的固定函数的方式中,纹理环境的设置中包含了一项决定如何进行片段的颜色和纹理查找结果混合。如(glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV, GL_DECAL))。这个在片段着色器中被忽略了。如何进行混合完全取决于你的片段着色器的操作。当然片段着色器也可以不做事情,只是简单的把输入的主颜色拷贝到输出的颜色中。

 

替代颜色求和

    这个阶段的处理非常简单,仅仅是把主颜色和辅助颜色相加。如果我们不使用辅助颜色,就直接忽略它。

 

替代雾

    雾的应用也是简单的。首先我们根据公式用雾坐标和雾浓度的常量来计算雾因子。在固定的函数管线中,有几个固定编码的方程式,线性的,指数的,二次指数的方程。但在着色器中你可以编写自己的方程式来计算雾因子。如果我们不使用雾,那我们就不需要在着色器中添加任何关于雾的指令。

OpenGL着色器语言的简单的例子

顶点着色器:

void main(void)

{

    //输入的顶点执行变换,变换到裁剪坐标

    vec4 clipCoord = gl_ModelViewProjectionMatrix * gl_Vertex;

    //复制到输出的位置

    gl_Position = clipCoord;

    //复制主颜色

    gl_FrontColor = gl_Color;

//规格化坐标

    vec3 ndc = clipCoord.xyz / clipCoord.w;

    // 输出前把[-1,1]映射到[0,1]

    gl_SecondaryColor = (ndc * 0.5) + 0.5;

}

片段着色器

void main(void)

{

    //混合主颜色和辅助颜色

    gl_FragColor = mix(gl_Color, vec4(vec3(gl_SecondaryColor), 1.0), 0.5);

}

上面的着色器简单的模拟了一些固定函数管道的一些功能。其中以gl_为前缀的是GLSL内置的变量。其中gl_Vertex代表输入的顶点,gl_ModelViewProjectionMatrix是模型视图矩阵和投影矩阵的结合,我们用gl_ModelViewProjectionMatrix * gl_Vertex; 就可以得到裁剪的坐标了。 gl_Position是输出的顶点的位置。 gl_Color在顶点着色器和片段着色器中有着不同的含义。在顶点着色器中gl_Color代表着用户的输入,即通过glColor等方式的输入。而在片段着色器中的gl_Color代表经过插值的片段的颜色。

在编写完上面两个着色器,并编译连接应用后,每个顶点及其附属的属性都会经过顶点着色器的处理器,然后裁剪,光栅化成片段,然后片段再经过片段着色器的处理。

后面再详细介绍GLSL语言。。。

相关推荐