目录
- 抽象理解渲染过程
- 详细介绍各个渲染步骤
- 1. 应用阶段
- 1.1设置场景数据
- 1.2 Culling 粗粒度剔除
- 1.3 渲染设置
- 1.4 传递几何信息
- 1.5 调用DrawCall
- 2. 几何阶段
- 2.1 几何着色器
- 2.2 可选着色器
- 2.3 投影变换 projection
- 2.4 裁切 Clipping
- 2.5 屏幕映射 ScreenMapping
- 3. 光栅化阶段
- 3.1 三角形设置
- 3.2 三角形遍历
- 4. 逐片元操作
- 4.1 片元着色
- 4.2 测试
- 4.3 混合
- 4.4 目标缓冲区
- 空间变换
- MVP
- 裁剪空间
- NDC
- 屏幕空间
- 一些理解
- 一些易混淆的点
- 一些名词
- refer
流水线的这种东西,其阶段划分每个人,每个教程都有不同的理解,这篇会结合我看过的几篇文章,写我自己的流程划分
抽象理解渲染过程
总的流程更像是这样:CPU布置工作、准备材料并下达命令(应用阶段),GPU只负责计算(GPU流水线)
- 应用阶段
1.布置场景数据(场景 物体 光源 摄像机等),决定摄像头中有哪些内容需要渲染
2.传递渲染状态:即设置各个物体的参数,材质,shader等
3.粗粒度剔除(culling):剔除摄像机外的内容,优化性能
4.将几何信息传递给几何阶段
5.发令 drawcall - 几何阶段
1 几何着色器:进行MVP变换
2 可选着色器:网格顶点的增加
3 投影变换 projection:透视除法
4 裁切 Cliping:优化性能
5 屏幕映射 ScreenMapping:适应不同屏幕 - 光栅化阶段:
总的来说就是计算每个图元覆盖了哪些像素
1.三角形设置
2.三角形遍历 - 逐片元操作:
1.片元着色
2.测试&颜色混合
3.目标缓冲区
详细介绍各个渲染步骤
1. 应用阶段
1.1设置场景数据
一般就是地编,布置物体,设置灯光,设置摄像机
1.2 Culling 粗粒度剔除
粗粒度剔除:包含视锥体剔除(Frustum Culling)和遮挡剔除( Occlusion Culling),减少无用的计算,优化性能(这实际上是帮助后面的MVP变换和clip提前减轻负担,是引擎对于渲染的优化)
请注意:应用阶段的剔除操作是以物体为对象的
-
视锥体剔除 (Frustum Culling) 禁用相机视野外的对象渲染,不禁用视野中被遮挡的任何物体的渲染(剔除在视锥体外的物体运算量较低,初步剔除)
-
遮挡剔除:遮挡剔除 (Occlusion Culling) 功能是在对象因被其他物体完全遮挡,当前在相机中无法看到时,禁用对象渲染。该功能一般不会在三维计算机图形中自动开启,因为在透明物体渲染中会出错
(更进一步的,一些光源对象也可以剔除,如与视锥体不相交的聚光,距离非常远的点光源)
1.3 渲染设置
- 材质参数设置:用哪些贴图,属性参数怎样设置
- 着色器设置:决定使用哪个shader,或者决定使用GPU instance批处理一些对象(没有使用过Unity中的批处理,具体操作还需要理解)
- 渲染顺序:决定使用哪种排序来渲染对象,Unity一般就是Render Queue,有些特殊的情况可以自定义,如按照与摄像机距离顺序,特殊的顺序可以在特殊情况下优化性能。
- 渲染目标:RenderBuffer 或 RenderTexture,有时需要同时输出,如SSSSS的皮肤渲染
- 设置渲染模式:forwardBase前向渲染,延迟渲染等
1.4 传递几何信息
将准备好的场景信息存入显存(我不确定是整个场景的数据还是当前剔除后的数据,需要进一步查证,如果是剔除后的数据,那显存岂不是每一帧都要来回的写入?)
- 几何信息:位置、法线、uv坐标、(颜色)发送给几何着色器 (注意这些信息全部是模型空间的信息!)
(有一个问题,几何信息是以物体/图元为单位的,还是没单位,所有顶点全视为单个顶点,后续再组装起来?)(思考3min后的解答:当然是以对象为单位的,渲染是以对象为单位的啊,一个对象渲染完成后,下一个对象还会进行几何阶段,顶点自然也就分开了) - MVP矩阵信息(M矩阵就是这个物体在世界中的位置了)
- 贴图(先存入显存,后续再由纹理坐标读取?)
1.5 调用DrawCall
当一切都准备好之后,发送最后一句drawcall命令
drawcall由CPU发起,GPU接收执行,其指向一个需要被渲染的图元列表(图元列表?是不是就是一个对象?)
应用阶段准备好了一切计算需要的材料,只需要开始计算即可得到结果,drawcall就是发起命令
2. 几何阶段
2.1 几何着色器
- MVP变换
- (顶点着色)
2.2 可选着色器
顶点着色器是不能生成更多的顶点的,其只能进行变换
这一步是可以有也可以没有的,都还没学,慢慢补吧
- 几何着色器:以图元为单位(点线面)来生成更多的图元
- 曲面细分着色器:使用当前的顶点按照算法生成更多的顶点,分成细致的网格和图元
2.3 投影变换 projection
Unity会自动完成
- P变换,两种,正交和透视
2.4 裁切 Clipping
裁切选择在 几何和可选着色器 之后进行,是为了防止出错
(出错情况举例:截了一半的物体,vert里要缩小它,那后续不就露出来那被截断的部分了么)
- CVV视锥体裁切
- 正背面剔除 (所以说clipping和culling的划分很混乱)
2.5 屏幕映射 ScreenMapping
Unity会自动完成
- 另外需要考虑不同的图形API的坐标系
在几何阶段完成后,最终我们会获得这样一个坐标系:以屏幕左下角为原点(OpenGL屏幕原点在左下,DirectX在左上)
而几何阶段最终传递的信息为:
- 顶点信息:位置(在屏幕空间中的xy坐标),深度值,法线方向,uv坐标,颜色(如果有) 等
- 其他信息:摄像机位置信息,光线信息等
3. 光栅化阶段
3.1 三角形设置
在这个过程中进行的操作是:前一步传入的数据全部是顶点,三角形设置则将所有的顶点组装成图元,(一般是三角形,也有线段和折线)
不必纠结这个过程究竟怎么实现的,因为这是渲染器要做的事情
3.2 三角形遍历
在上一步我们获得了各个三角形(而不是顶点),现在我们知道了各个三角形的边界是如何的了,三角形遍历要做的事情就是:根据三角形的边界,映射到片元中:
- 这个过程中最重要的是,将顶点信息插值,赋给每一个片元
(我们本身只有顶点的数据(如:深度,法线,UV坐标等等),那每一个片元中的信息就是根据所在图元的几个顶点按照规则插值出来的,这样我们就获取了每一个片元计算所需要的数据,剩下的就是计算的问题,也就是片元着色器) - 同时如果有顶点着色,片元颜色的插值也会在这一步进行(片元着色器就没有着色的计算了直接输出即可):
- 抗锯齿:由于光栅化是硬件自动进行的,无法使用着色器进行编程,所以这一步能够进行只有硬件的抗锯齿算法:SSAA,MSAA
(其余的抗锯齿是在后处理阶段软件进行的FXAA,TXAA) - 详细的抗锯齿可以看本人的抗锯齿文章
4. 逐片元操作
4.1 片元着色
- 正常情况下这一步就是进行fragment shader了,正常来讲也是整个流水线最耗时的部分,包括读取纹理,光照计算等等
- 但是一般现代GPU会把这个过程放到测试之后,原因接着看:
4.2 测试
包括:模板测试、深度测试、透明度测试
大家都懂哈,不多说了
详细说一下测试和着色的顺序
- 经典情况下是先着色再进行测试
- 但是呢这回有一个问题,性能开销太大,辛辛苦苦着色完了,还被抛弃掉了;因此现代的渲染顺序一般都会先进行测试,再进行着色,包括Unity的Early-Z技术等,这会大大提高性能,尤其是场景遮挡比较复杂时
- 当然也有其缺点,这个问题出现在透明物体上,比如:透明物体在前,如果先进行了测试,那么先渲染的物体(后面的物体)被前面透明物体遮挡住的部分就不会进行着色,也就是空白的,这样在后面透明物体进行着色后,跟缓冲区的颜色进行混合时就会出错,因为缓冲区根本没有颜色缓冲数据。
- 所以当面临透明物体的渲染时,就需要考虑一下测试和着色的顺序了。
4.3 混合
- 前面着色后的数据会被存入颜色缓冲,后面物体着色后如果在同一个像素,就需要进行混合(或者替换),这个混合/替换法则是可以配置的。
4.4 目标缓冲区
渲染完一帧之后,我们获得了完整的一张图像(颜色缓冲中),最后要进行的就是把它输出到什么位置:
- 一般就是交换前置后置缓冲
- 或者输出到渲染纹理,进行后处理操作等
空间变换
MVP
模型空间 → \to → 世界空间 → \to → 观察空间 → \to → 裁剪空间
- 这个过程中的三个箭头对应MVP三个矩阵
- MVP在几何着色器中执行(UnityModelToClipPos)
M:
- 从模型的空间变换到世界空间
- 物体的移动,真正的落实在这个M矩阵上
V:
- 从世界到观察
- 原点变换到摄像机位置
- 某种意义上,摄像机看到的前进其实是世界在后退
P:
- 不是真正的投影,而是为投影做准备
- 为什么这么说:需要进行透视除法才算真正完成投影,而透视除法需要先计算好对应的 w分量,P矩阵就在计算w(根据距离摄像机的距离计算得)
裁剪空间
进行MVP变换后便得到了裁剪空间,在这个空间中,我们自然是要进行裁剪,裁剪的范围是可以设定的(就是视锥体的范围)
- 裁剪的方法:对于xyz任意一个坐标大于其w分量的顶点,进行剔除(其实在应用阶段已经进行了粗粒度剔除),如果是半截在边界外的图元,会截断图元,并在边界生成新的顶点补足这个图元
最终的裁剪结果就是下面这样:
从这时开始,之前的空间都变为有限的空间
NDC
在裁剪过后,进行透视除法,并标准化坐标,得到NDC
透视除法是一个开销很大的过程,其需要对每一个顶点的分量都除以w分量,因此需要在裁剪过后再进行透视除法,以减少工作量
(下面这个图有错误,第二个不是裁剪空间,裁剪空间应当是锥形的)
屏幕空间
从NDC到屏幕空间的变化称为视口变换
一些理解
1.为什么要使用模型空间?所有模型顶点直接用世界坐标原点不就好了?省的计算M矩阵
- 答:
(1)缓存传递:不能这么做的最大问题在于:如果使用世界坐标,每次模型运动时,每一个顶点的数据都需要重新发送给缓存,这是不能接受的;如果当使用模型坐标,要移动模型,我们无需改变已经在缓冲区的顶点坐标数据,只需改变M矩阵,即可将模型移到我们指定的位置。
(2)计算量上:使用世界坐标看似减少计算,实际上如果要进行位移计算,其需要矩阵的加减乘积运算结合,是非常复杂的;另外其实M变换和MV变换的计算量是一致的(MV可以预计算,记为一个矩阵)
(4)简洁性上:将模型起始于原点位置进行顶点的设置,所有的顶点坐标都是相对于 (0,0,0),简洁明了
2. 透视除法为什么安排在最后?
答:因为透视除法会破坏其几何结构,顶点着色器和下面的可选着色器都需要在裁剪坐标系下进行,此时的几何关系是正确的,最后完成所有的几何操作之后,映射之前,才会进行投影变换)
一些易混淆的点
剔除和裁切(culling & clipping):很多将culling和clipping都叫做剔除,不管是中英文都会混着叫
earlyZ和剔除:
一些名词
OverDraw:因为在大部分情况下,离相机最远的对象最先渲染,离相机近的对象覆盖先前的物体(该步骤称之为“重复渲染 (overdraw)”)
DrawCall:
refer
《入门精要》
百人计划
LearnOpenGL
各个空间
空间变换
NDC和裁剪
知乎
csdn