目录概述1 应用阶段(可操控优化)1.1 粗粒度剔除1.2 设置渲染顺序1.3 把数据加载到显存1.4 设置渲染状态(SetPassCall,告诉GPU使用哪个Shader,设置Blend,Cull,ZWrite等)和DrawCall(使用哪些模型数据和贴图渲染)2 几何阶段2.1 顶点着色器(完全可编程)2.2 裁剪,(GPU硬件固定操作,可通自定义裁剪操作来控制)2.3 屏幕映射,GPU完成2.4 补充一下3 光栅化阶段3.1 三角形设置3.2三角形遍历3.3 Early-Z技术,把深度测试提前做4 像素处理阶段4.1 片元着色器(完全可编程)4.2 逐片元操作(输出合并阶段),可配置5 结尾概述整体上流水线分为四个阶段:1应用阶段,2几何阶段,3光栅化阶段,4像素处理阶段当然也可以分成三个阶段把3和4统一叫光栅化阶段。这不重要,重要是理解每个阶段的细节处理。1 应用阶段(可操控优化)此阶段在CPU上运行的,完全可控。主要分为4个阶段:1.1 粗粒度剔除包括视锥体剔除(AABB包围盒和视锥体碰撞检测),遮挡剔除(Occlusion Culling),层级规则剔除(Culling Mask)1.2 设置渲染顺序根据摄像机距离和渲染队列决定顺序,队列小于2500不透,按照由近到远渲染,大于2500透明按照由远到近渲染1.3 把数据加载到显存根据设置好的顺序依次提交打包数据Batch,数据包括,模型信息(顶点坐标,法线,UV,顶点色,切线,索引表等),变换矩阵(世界转换矩阵,根据摄像机位置的VP矩阵,FOV参数等),灯光材质(灯光信息,材质,Shader)等。1.4 设置渲染状态(SetPassCall,告诉GPU使用哪个Shader,设置Blend,Cull,ZWrite等)和DrawCall(使用哪些模型数据和贴图渲染)2 几何阶段2.1 顶点着色器(完全可编程)主要任务是坐标变换,通过MVP矩阵把模型空间顶点坐标转换到齐次裁剪空间。当然还有其他任务,顶点着色(例如逐顶点光照颜色的计算),为光栅化阶段输出其他数据等。转换过程如下图,其中红色框中为顶点着色器的处理过程齐次裁剪空间要重点说明一下,如图所示,左面是观察空间坐标系,右面是其次裁剪空间,此时顶点坐标做了齐次处理(增加了w=1分量),主要原因是,透视空间中,数值是无法正常做线性插值的,需要增加维度方便透视投影和裁剪(通过升维的方式简化裁剪过程,想到这个方法的真是天才,我怀疑他是高纬度的生物),经过裁剪矩阵(投影矩阵)转换后,w有了新的意义。可通过w判断顶点是否在视锥体内。顶点着色的输出数据为齐次裁剪空间下的坐标,主要目的就是为了后面的裁剪和屏幕映射做准备。这个计算公式可以看到,xyz做了不同的缩放,w分量编程了Pview的-z,也就是观察空间下的z轴距离,后面再说SV_POSITION的时候还会提到。曲面细分着色器,可选着色器,主要用于细分图元。几何着色器也是可选的,用于逐图元操作或产生更多图元,这里不是重点,暂且不谈。2.2 裁剪,(GPU硬件固定操作,可通自定义裁剪操作来控制)通过透视投影矩阵转换到其次裁剪空间如果该顶点在视锥体中必须满足-w<=(x,y,z)<=w,然后做透视除法(齐次除法),可以得到归一化设备坐标NDCNDC空间下的,在dx中-1<=x,y<=1,0<=z<=1,在OpenGL中-1<=x,y,z<=1,从而判断是否是在可视范围内,完全在NDC外的剔除,部分在NDC内的,生成新的图元。2.3 屏幕映射,GPU完成在NDC的基础上进一步处理,把顶点坐标转换到屏幕坐标系。此时图元坐标还是3维的,xyz范围是[-1,1],所以映射到[0,1],乘以屏幕分辨率就是屏幕坐标了在OpenGL中屏幕的原点在左下角,DX在左上角,以GL为例转换公式为(结合了其次除法),转换屏幕坐标公式如下:screen.x = clip.x * pixeWidth / 2*clip.w + pixeWidth/2screen.y = clip.y * pixeHeight / 2*clip.w + pixeHeight/2此时的z和w分量作什么呢,通常做法会把clip.z/clip.w的值储存在zbuffer里(看硬件驱动),w分量可用在透视校正插值。2.4 补充一下整个几何阶段输出的核心数据SV_POSITION,也不再是齐次裁剪空间的坐标了,因为做了齐次除法和屏幕映射。这里我很少见人提到过,所以还是重点提一下吧。pos.x =clip.x * pixeWidth / 2*clip.w + pixeWidth/2pos.y = clip.y * pixeHeight / 2*clip.w + pixeHeight/2pos.z = clip.z/clip.w(dx中是[0,1]gl中[-1,1])pos.w = 1 ? 这里不对,1没有意义啊SV_POSITION.w的值,就是经过插值后的ClipSpace的 w 值。在Unity中,这个值等价观察空间下的 z 坐标值,也就是世界空间中,到摄影机的z周距离。这个距离值可以非常方便地实现一些雾效的效果,或者用于在Cluster based Forward中计算 z 轴上的采样位置。OpenGL中,在VertexShader下类似于 SV_POSTION 的语义为 gl_POSITION。在Fragement Shader下类似于SV_POSTION 为 gl_FragCoord。gl_FragCoord.xyz 值和 SV_POSTION.xyz 的语义是一样的。而 gl_FragCoord.w的值,等于 ClipSpace 的 w 值的倒数,这点和 SV_POSTION.w 是不同的。不过在Unity中,为我们做了自动的转换,如果我们在 PixelShader中使用 SV_POSTION.w 值的时候,在 OpenGL下,会自动转换成 1/gl_FragCoord.w,还是非常方便的。此段为引用别人写的,原文链接所以此在片元着色器里,可以直接用SV_POSITION语义的变量除以屏幕分辨率获得屏幕UV。3 光栅化阶段光栅化啥意思,简单点说就是根据三角形的三个顶点计算出覆盖的像素。主要分为两个子阶段:三角形设置(Triangle Setup),三角形遍历(Triangle Traversal)。3.1 三角形设置由固定硬件完成,主要任务是计算三角形网格信息,如根据上一步得到的顶点信息,获得三角形顶点坐标和边界线用数学表达式。3.2三角形遍历检查每一个采样点被三角形覆盖的像素,并使用三角形的三个顶点对覆盖的像素进行插值,为覆盖的像素生成片元(fragment),片元的信息包括屏幕坐标、深度信息、顶点着色器输出的顶点信息如法线,uv等。3.3 Early-Z技术,把深度测试提前做正常的深度测试是在像素阶段的逐片元操作里,目的是把不需要处理的片元提前舍弃掉,提升性能,目前很多硬件都支持,以下情况会使其失效1.开启Alpha Test,由于Alpha Test需要在像素着色器后面的Alpha Test阶段比较,所以无法在像素着色器之前就决定该像素是否被剔除,可以用Alpha Blend代替,或者双Pass优化,一个Pass做discard/clip,深度读写,一个Pass做颜色处理不做深度写入2.关闭了深度测试,深度测试是Early-Z的前提。3.开启了Alpha Blend 启用了Alpha混合的像素很多需要与frame buffer做混合,无法执行深度测试,也就无法利用Early-Z技术,但并不影响其他Pass使用Early-Z。4.开启Tex Kill:即在shader代码中有像素摒弃指令(DX的discard,OpenGL的clip),1中的AlphaTest也是使用了clip函数。4 像素处理阶段4.1 片元着色器(完全可编程)输入的从顶点信息插值的结果,通过对这些信息的处理,为这个片元计算颜色,常用的技术是纹理采样,通过顶点插值来的uv,采样纹理的颜色。4.2 逐片元操作(输出合并阶段),可配置主要任务1.通过一些列测试决定们每个片元的可见性如:透明度测试->模板测试->深度测试2.把通过测试的片元的颜色和颜色缓冲区的颜色进行混合,混合后写入缓冲区。和颜色缓冲区一样,还有模板缓冲区(当前模板之和缓冲区值比较决定片元是否舍弃和写入),深度缓冲区(和模板缓冲区类似),系统中所有的缓冲区组成了帧缓冲区(frame buffer)至此屏幕就显示了帧缓冲区里的颜色了,为了使画面连续,使用双重缓冲技术。4.3 拿到帧缓冲之后其实还有一个很重要的操作,就是做屏幕后处理,例如Bloom,DOF等5 结尾本文大量篇幅在几何阶段,这个阶段相对难理解的,也是最为重要的,他们整个流水线做了基础的准备工作,有了这些准备后面的流程才能方便的计算,后续还有其他细节想起来再补充吧。
目录概述1 应用阶段(可操控优化)1.1 粗粒度剔除1.2 设置渲染顺序1.3 把数据加载到显存1.4 设置渲染状态(SetPassCall,告诉GPU使用哪个Shader,设置Blend,Cull,ZWrite等)和DrawCall(使用哪些模型数据和贴图渲染)2 几何阶段2.1 顶点着色器(完全可编程)2.2 裁剪,(GPU硬件固定操作,可通自定义裁剪操作来控制)2.3 屏幕映射,GPU完成2.4 补充一下3 光栅化阶段3.1 三角形设置3.2三角形遍历3.3 Early-Z技术,把深度测试提前做4 像素处理阶段4.1 片元着色器(完全可编程)4.2 逐片元操作(输出合并阶段),可配置5 结尾概述整体上流水线分为四个阶段:1应用阶段,2几何阶段,3光栅化阶段,4像素处理阶段当然也可以分成三个阶段把3和4统一叫光栅化阶段。这不重要,重要是理解每个阶段的细节处理。1 应用阶段(可操控优化)此阶段在CPU上运行的,完全可控。主要分为4个阶段:1.1 粗粒度剔除包括视锥体剔除(AABB包围盒和视锥体碰撞检测),遮挡剔除(Occlusion Culling),层级规则剔除(Culling Mask)1.2 设置渲染顺序根据摄像机距离和渲染队列决定顺序,队列小于2500不透,按照由近到远渲染,大于2500透明按照由远到近渲染1.3 把数据加载到显存根据设置好的顺序依次提交打包数据Batch,数据包括,模型信息(顶点坐标,法线,UV,顶点色,切线,索引表等),变换矩阵(世界转换矩阵,根据摄像机位置的VP矩阵,FOV参数等),灯光材质(灯光信息,材质,Shader)等。1.4 设置渲染状态(SetPassCall,告诉GPU使用哪个Shader,设置Blend,Cull,ZWrite等)和DrawCall(使用哪些模型数据和贴图渲染)2 几何阶段2.1 顶点着色器(完全可编程)主要任务是坐标变换,通过MVP矩阵把模型空间顶点坐标转换到齐次裁剪空间。当然还有其他任务,顶点着色(例如逐顶点光照颜色的计算),为光栅化阶段输出其他数据等。转换过程如下图,其中红色框中为顶点着色器的处理过程齐次裁剪空间要重点说明一下,如图所示,左面是观察空间坐标系,右面是其次裁剪空间,此时顶点坐标做了齐次处理(增加了w=1分量),主要原因是,透视空间中,数值是无法正常做线性插值的,需要增加维度方便透视投影和裁剪(通过升维的方式简化裁剪过程,想到这个方法的真是天才,我怀疑他是高纬度的生物),经过裁剪矩阵(投影矩阵)转换后,w有了新的意义。可通过w判断顶点是否在视锥体内。顶点着色的输出数据为齐次裁剪空间下的坐标,主要目的就是为了后面的裁剪和屏幕映射做准备。这个计算公式可以看到,xyz做了不同的缩放,w分量编程了Pview的-z,也就是观察空间下的z轴距离,后面再说SV_POSITION的时候还会提到。曲面细分着色器,可选着色器,主要用于细分图元。几何着色器也是可选的,用于逐图元操作或产生更多图元,这里不是重点,暂且不谈。2.2 裁剪,(GPU硬件固定操作,可通自定义裁剪操作来控制)通过透视投影矩阵转换到其次裁剪空间如果该顶点在视锥体中必须满足-w<=(x,y,z)<=w,然后做透视除法(齐次除法),可以得到归一化设备坐标NDCNDC空间下的,在dx中-1<=x,y<=1,0<=z<=1,在OpenGL中-1<=x,y,z<=1,从而判断是否是在可视范围内,完全在NDC外的剔除,部分在NDC内的,生成新的图元。2.3 屏幕映射,GPU完成在NDC的基础上进一步处理,把顶点坐标转换到屏幕坐标系。此时图元坐标还是3维的,xyz范围是[-1,1],所以映射到[0,1],乘以屏幕分辨率就是屏幕坐标了在OpenGL中屏幕的原点在左下角,DX在左上角,以GL为例转换公式为(结合了其次除法),转换屏幕坐标公式如下:screen.x = clip.x * pixeWidth / 2*clip.w + pixeWidth/2screen.y = clip.y * pixeHeight / 2*clip.w + pixeHeight/2此时的z和w分量作什么呢,通常做法会把clip.z/clip.w的值储存在zbuffer里(看硬件驱动),w分量可用在透视校正插值。2.4 补充一下整个几何阶段输出的核心数据SV_POSITION,也不再是齐次裁剪空间的坐标了,因为做了齐次除法和屏幕映射。这里我很少见人提到过,所以还是重点提一下吧。pos.x =clip.x * pixeWidth / 2*clip.w + pixeWidth/2pos.y = clip.y * pixeHeight / 2*clip.w + pixeHeight/2pos.z = clip.z/clip.w(dx中是[0,1]gl中[-1,1])pos.w = 1 ? 这里不对,1没有意义啊SV_POSITION.w的值,就是经过插值后的ClipSpace的 w 值。在Unity中,这个值等价观察空间下的 z 坐标值,也就是世界空间中,到摄影机的z周距离。这个距离值可以非常方便地实现一些雾效的效果,或者用于在Cluster based Forward中计算 z 轴上的采样位置。OpenGL中,在VertexShader下类似于 SV_POSTION 的语义为 gl_POSITION。在Fragement Shader下类似于SV_POSTION 为 gl_FragCoord。gl_FragCoord.xyz 值和 SV_POSTION.xyz 的语义是一样的。而 gl_FragCoord.w的值,等于 ClipSpace 的 w 值的倒数,这点和 SV_POSTION.w 是不同的。不过在Unity中,为我们做了自动的转换,如果我们在 PixelShader中使用 SV_POSTION.w 值的时候,在 OpenGL下,会自动转换成 1/gl_FragCoord.w,还是非常方便的。此段为引用别人写的,原文链接所以此在片元着色器里,可以直接用SV_POSITION语义的变量除以屏幕分辨率获得屏幕UV。3 光栅化阶段光栅化啥意思,简单点说就是根据三角形的三个顶点计算出覆盖的像素。主要分为两个子阶段:三角形设置(Triangle Setup),三角形遍历(Triangle Traversal)。3.1 三角形设置由固定硬件完成,主要任务是计算三角形网格信息,如根据上一步得到的顶点信息,获得三角形顶点坐标和边界线用数学表达式。3.2三角形遍历检查每一个采样点被三角形覆盖的像素,并使用三角形的三个顶点对覆盖的像素进行插值,为覆盖的像素生成片元(fragment),片元的信息包括屏幕坐标、深度信息、顶点着色器输出的顶点信息如法线,uv等。3.3 Early-Z技术,把深度测试提前做正常的深度测试是在像素阶段的逐片元操作里,目的是把不需要处理的片元提前舍弃掉,提升性能,目前很多硬件都支持,以下情况会使其失效1.开启Alpha Test,由于Alpha Test需要在像素着色器后面的Alpha Test阶段比较,所以无法在像素着色器之前就决定该像素是否被剔除,可以用Alpha Blend代替,或者双Pass优化,一个Pass做discard/clip,深度读写,一个Pass做颜色处理不做深度写入2.关闭了深度测试,深度测试是Early-Z的前提。3.开启了Alpha Blend 启用了Alpha混合的像素很多需要与frame buffer做混合,无法执行深度测试,也就无法利用Early-Z技术,但并不影响其他Pass使用Early-Z。4.开启Tex Kill:即在shader代码中有像素摒弃指令(DX的discard,OpenGL的clip),1中的AlphaTest也是使用了clip函数。4 像素处理阶段4.1 片元着色器(完全可编程)输入的从顶点信息插值的结果,通过对这些信息的处理,为这个片元计算颜色,常用的技术是纹理采样,通过顶点插值来的uv,采样纹理的颜色。4.2 逐片元操作(输出合并阶段),可配置主要任务1.通过一些列测试决定们每个片元的可见性如:透明度测试->模板测试->深度测试2.把通过测试的片元的颜色和颜色缓冲区的颜色进行混合,混合后写入缓冲区。和颜色缓冲区一样,还有模板缓冲区(当前模板之和缓冲区值比较决定片元是否舍弃和写入),深度缓冲区(和模板缓冲区类似),系统中所有的缓冲区组成了帧缓冲区(frame buffer)至此屏幕就显示了帧缓冲区里的颜色了,为了使画面连续,使用双重缓冲技术。4.3 拿到帧缓冲之后其实还有一个很重要的操作,就是做屏幕后处理,例如Bloom,DOF等5 结尾本文大量篇幅在几何阶段,这个阶段相对难理解的,也是最为重要的,他们整个流水线做了基础的准备工作,有了这些准备后面的流程才能方便的计算,后续还有其他细节想起来再补充吧。