参考出处:
1. OpenGL管线(用经典管线代说着色器内部)
2. 顶点、 图元、片元、像素的含义
可编程管线中的顶点处理器和片元(又称片段)处理器分别代替了固定管线上的各顶点的操作和片元处理。
在进入各个框之前,我们先大概划清范围,看看我们常用的固定管线功能都包括在哪个部分。顶点处理包括固定管线的顶点坐标变换、光照(也即逐顶点光照)等;图元装配裁剪等包括图元装配、裁剪、透视除法、视口变换等;光栅化包括点线光栅化、多边形填充、纹理(Texture)、雾(Fog)等;逐片断处理包括各种测试(Scissor, Alpha, Stencil, Depth Test)、混合(Blending)等。
固定管线上的各顶点操作主要负责顶点的转换(Transformation)和光照(Lighting),故替代它的顶点处理器必须具备这些功能,这就要求顶点处理器必须根据输入进来的gl_Vertex数据给出正确的输出数据gl_Position,以供给下一阶段正确的运行。如何将gl_Vertex变换为gl_Position,这就是比变换的工作。变换的工作包括三个部分,模型变换、视图变换和投影变换。其中通过将gl_Vertex乘上gl_ModelViewMatrix矩阵可以将其变换到摄像机空间(又称Eye空间或摄像机坐标系),而乘上gl_ModelViewProjectionMatrix矩阵可以将顶点数据变换到齐次裁剪空间,此时即得到了gl_Position,也就是说gl_Position是位于齐次裁剪空间的顶点数据。变换部分可参考OpenGL矩阵变换。
varying vec3 ecPosition;
ecPositon = vec3(gl_ModelViewMatrix * gl_Vertex); //视点
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
顶点处理器中的光照操作一般发生在eye Space,故关于光照的处理都会根据上述代码的ecPosition进行相关操作,处理之后传递到片元处理器中作进一步的处理。
图元装配使用到的顶点数据是就是位于齐次裁剪空间的gl_Position,装配过程依据用户指定的四边形,三角形,直线或点的方式对顶点数据进行组装。
裁剪
裁剪发生在齐次裁剪空间内,即各顶点使用的都是类似于(x, y, z, w)形式的齐次坐标。
拼装好的图元会与视景体(View Volume)和用户自定义的裁剪平面(通过glClipPlane函数设置的)进行比较,若位于视景体和用户自定义的裁剪平面的内部,则不进行裁剪,若全部位于外部,则抛弃全部图元,但若一部分位于其内,一部分位于其外,则裁剪它,这样就产生了新的顶点,这些的点的颜色值以及纹理坐标等值要被插值。
裁剪公式如示:
投影
该处的投影意为透视投影的操作,这里针对于透视视图进行透视除法,即将顶点的齐次坐标除以w,将顶点数据的(x, y, z)规范到[-1, 1]的闭区间内,此时w=1。
透视除法公式:
视口
此处视口意即视口变换,是OpenGL矩阵变换的最后一步变换。是齐次裁剪空间到屏幕窗口的变换,有时也称为是摄像机镜头平面到窗体视口平面的变换。
视口变换可通过函数void Viewport(int x, int y, sizei w, sizei, h)
进行设置,
视口变换公式:
公式中的顶点坐标(x, y, z)的下标’d’表示经过透视除法后的坐标值。公式中”f”和”n”代表函数glDepthRange(GLclampd znear, GLclampd zfar)
中的参数”zfar”和”znear”,类型GLclampd 表示参数值被钳位到了区间[0, 1],显而易见该函数用来设置深度值范围的。若不指定范围,则z坐标的默认范围为[0, 1],且值越小表示场景数据距离视口的距离越近。
消隐
使用上述计算好的窗口坐标来测试各个多边形图元,可以查看它是否远离当前的查看位置。通过调用glEnable可以启用消隐状态,调用glCullFace可以指定要丢弃背面的多边形、正面的多边形或者两者都丢弃。
到目前为止,管线里的数据都是顶点,经过图元装配之后,哪些顶点就是一个点、哪两个顶点是直线段、哪三个或更多顶点是一个三角形或多边形,这些图元信息都已经知道了,但它们还是只是顶点而已:顶点处都还没有“像素点”、直线段端点之间是空的、多边形的边和内部也是空的,光栅化的任务就是构造这些。由于已经经过了视口变换,光栅化是在二维(附带深度值)的屏幕坐标系(Window Space)中进行的。
光栅化有两个任务:
1. 确定图元包含哪些由整数坐标确定的“点”(和屏幕像素对应,现在还不能叫片断,光栅化完成后才能叫片断);
2. 确定这些”点”的Depth值和Color值(从图片顶点的Depth和Color插值得到),这些颜色后来可能被其他如纹理操作修改。
一个片段(fragment,又称片元)由一个窗口坐标、深度以及其他相关属性(如颜色、纹理坐标等)组成,这些属性的值是通过对在图元的顶点上指定的值之间进行插值而确定的。在进行栅格化时,顶点有一个主颜色和一个次颜色,可以通过glShadeModel函数来指定是在顶点之间对这些颜色值进行插值(平滑着色,GL_SMOOTH),还是对整个图元都使用该图元最后一个顶点的颜色值(平面着色,GL_FLAT)。
片元处理器主要有两个工作,根据光栅化后得到的纹理坐标,获取纹理,并传递给输出变量gl_FragColor;另一个作用就是“雾化”操作,即根据片元距离当前视点的位置来控制雾化在最终gl_FragColor变量中比例。
uniform float visibility;
uniform vec4 fogColor;
uniform sampler2D decal;
vec4 textureColor = texture2D(decal, gl_TexCoord[1].st);
float abs_z = abs(mPositon.z);
float fogFactor = ((visibility == 0) ? 0.0 : clamp(abs_z/visibility, 0.0, 1.0));
gl_FragColor = mix(textureColor, fogColor, fogFactor); //fogFactor为计算出的比例因子
片元着色器的目的是计算要应用到片元上的颜色(gl_FragColor )和计算片元的深度值(gl_FragDepth),但一般情况下由OpenGL的栅格化阶段计算的深度值就已经很令人满意了,故一般片元着色器的工作就是计算当前片元的颜色。这也是在上述程序中没有计算gl_FragDepth的原因。
各片元的操作又译为逐片元处理,逐片段处理。
光栅化的输出是一系列片断(Fragments,这些片断又经过片断着色器处理),片断被称为“准像素”,这是因为屏幕坐标系的一个整数坐标上只有一个像素,但可以前后“堆叠”多个片断。这些片断进入逐片断处理(Per-Fragment Operations),首先进行各种测试(下图中共5个),每步测试,不通过的片断将被丢弃从而不能进入后续操作,然后进行一些操作(如混合),最终通过所有处理的片断将被写入FrameBuffer用于最终屏幕显示,过程如下:
该阶段主要供用户设置OpenGL的状态,以控制要在其中绘制图元的帧缓冲区区域。
帧缓冲区(FrameBuffer),包括RGBA缓冲、Depth缓冲,Stencil缓冲和Accum缓冲。
注意帧缓冲区不包括双缓冲,双缓冲的交换发生在帧缓冲之后,然后就是将渲染的内容显示在屏幕了。
双缓冲是一种常用的防止画面撕裂的技术,即调用OpenGL函数进行渲染的结果都写入“back” buffer,待所有渲染完成调用SwapBuffers函数,切换“back” buffer和“front” buffer,并将“front” buffer内容显示在屏幕上,有个细节,显示器刷新频率一般为60或120Hz,SwapBuffers调用时刻可能不是显示器的刷新时刻,这时SwapBuffers将会等待直到显示器刷新才返回(当然,肯定存在避免等待的技术)。