Unity学习shader笔记[九]渲染流水

渲染流水线分三个阶段:应用阶段 几何阶段 和 光栅化阶段

  • 应用阶段是渲染数据的准备阶段,由CPU完全控制,开发者拥有绝对的控制权,应用阶段有很多渲染管线可供选择,其中一种是前向渲染。 这个阶段主要进行场景渲染数据的准备
    粗力度的不可见剔除(像unity的occlusion),将见不到的东西不进行渲染数据的准备,设置渲染状态数据(例如漫反射颜色 高光反射颜色
    纹理等信息的准备),将这些数据变成渲染图元输出到几何阶段供给应用。unity在这个阶段提供了可编程渲染管线,高清渲染管线(HDRP)等功能

  • 几何阶段主要由GPU负责,开发者只有部分的编写和配置权,编写的unity的shader主要在这个阶段生效,简单来说这个阶段主要任务是把顶点坐标变换到屏幕空间中,交给光栅器进行处理,这个阶段最后会生成片元信息,每个片元信息包含了一个像素上的深度值
    颜色值 模板缓冲值等信息,这些生成的片元信息供给最后的光栅化阶段进行渲染

  • 光栅化阶段使用几何阶段的信息进行屏幕上的像素绘制,根据片元信息来决定屏幕上的像素颜色构成

应用阶段

  • 把数据加载到显存中,渲染数据(法线 切线 颜色等)首先是在硬盘中 程序运行的时候加载到内存 最后应用阶段将需要的数据加载到显存
  • 设置渲染状态 ,在粗力度的不可见剔除后, 对于每个要渲染的东西,进行最开始的渲染数据的准备,包括材质,顶点,法线,纹理,光源,顶点着色器和片元着色器的选择
  • 调用DrawCall 生成drawcall,drawcall指向渲染状态设置时准备的数据生成的图元列表,本身不携带任何渲染数据信息, drawcall传送到缓冲区后GPU会调用进行渲染

这里需要提及的是Drawcall合并能减少CPU的负担,更加充分的发挥GPU的并行运算能力,因为大的drawcallGPU那边会拆解成很多个相同的并行任务一次跑完,减少CPU负担的原因是CPU调用1000个drawcall和将1000个drawcall合并成一个的效率就和移动1000个文件以及将1000个文件压缩成一个压缩包后移动的效率一样

几何阶段和光栅化阶段一览

Unity学习shader笔记[九]渲染流水_第1张图片
蓝色代表渲染环节的固定实现,开发者没有控制权,绿色代表开发者可以写代码进行细节控制,黄色代表开发者可以进行一些简单的配置,可配置代表可以在unity的顶点片元着色器里面写几句代码进行配置。
顶点数据在应用阶段有CPU生成,所以顶点着色器的参数输入是应用阶段生成的

几何阶段

  • 顶点着色器 完全可编程,它通常对mesh的每个顶点进行处理,输入进来的每个顶点都会调用一次顶点着色器,顶点着色器用于实现顶点的由模型空间到其次裁剪空间的变换,变换过程包括了三个步骤,模型空间到unity世界空间 由世界空间转换到观察空间 最后再通过投影变换由观察空间转换到齐次裁剪空间 其中 观察空间是以摄像机的坐标为原点,摄像机的旋转作为轴的朝向的空间; 模型空间是以模型中心为原点的坐标系,在建模的时候确定;齐次裁剪空间是长宽高分别为1的立方体的空间。在变换后, 顶点着色器会执行逐顶点光照,布料模拟,波浪模拟,计算顶点颜色等操作,以及为片元着色器等后续阶段输出所需的数据等。
    顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点直接的关系。Gpu可以利用本身的特性快速处理每一个顶点。
    顶点着色器是unity里面的其中一个主要着色器,因为是对顶点进行处理,执行强度不如片元着色器,一些片元着色器里面觉得耗性能的操作可以考虑放到顶点着色器

  • 曲面细分着色器 可选着色器,编程的重要程度不大,用于细分图元。

  • 几何着色器 可选着色器,编程的重要程度不大,用于执行逐图元的着色操作,或者产生于更多的图元。

  • 裁剪,裁剪过程在齐次裁剪空间进行,可配置,将那些不在摄像机视野内的顶点裁剪掉,剔除某些图元的面片,在shader中的写法是cull on /off/ front/ back等, on和off都明白,front和back其实不是简单的正面背面的意思,关于正反面可以参考 如何理解OpenGL中的backface culling以及图形的正反面? 。简单来说, 一个物体是由很多个顶点组成,每三个顶点之间组成三角面。 如果某个三角面的z轴方向与摄像机的z轴的方向相反,那么它就是正面,反面就是相同的面;三角面的Z轴朝向和其顶点绘制顺序是有关系的 。顺时针顺序的顶点所围成的面的法线方向就是三角面的z轴方向,如果这个顺时针方向是摄像机观察视野中的顺时针,则形成的三角面的z轴是与摄像机的z轴相对的,反之则是相同的。
    这里的裁剪和unity的occulusion这样的CPU阶段的裁剪不同,occulusion的裁剪通常是将完全没出现在视野范围内的物体不进行渲染处理,而这里的裁剪是在occulusion的基础上将一个物体在屏幕外面的部分的图元数据忽略,而只保留其在齐次裁剪空间内的图元数据Unity学习shader笔记[九]渲染流水_第2张图片

  • 屏幕映射 这一阶段不可配置和编程,是GPU固定实现,任务将上一阶段裁剪后的齐次坐标系(NDC)里面的图元数据映射到屏幕坐标系,其中屏幕坐标系是一个二维坐标系,其x轴和y轴的最大值和用于显示画面的分辨率有关系;屏幕坐标系和齐次裁剪坐标系的范围只是在0到1的z轴构成的坐标系称为窗口坐标系
    屏幕映射如下图示例,将齐次坐标下 -1,1的坐标范围转换到(x1,y1),(x2,y2)。可以看到这个过程实际上就是一个缩放的过程。在这个处理中z轴的值不做处理,即之前每个顶点在齐次裁剪坐标系中的z值是不变的。映射后的坐标值值会被传递到下一个阶段Unity学习shader笔记[九]渲染流水_第3张图片
    要注意的一点是,OpenGL和DirectX的屏幕坐标系的坐标是不一样的
    Unity学习shader笔记[九]渲染流水_第4张图片

光栅化阶段

三角形设置:将之前计算到的屏幕上的顶点信息连结成为屏幕上的三角面片
三角形遍历:检查每个像素在上个阶段计算出来的哪个三角面片覆盖,如果某个像素检查到被覆盖了, 就会产生一个片元,片元不能单纯理解为一个像素,片元还包含了像素以及像素上的一些屏幕坐标值,颜色值 深度值 模板值等其他信息,片元里面的几乎所有信息是根据片元所在的三角面片的三个顶点的信息和片元所在的位置插值得到,得到的片元传递给片元着色器,三角形遍历也称扫描变换。
Unity学习shader笔记[九]渲染流水_第5张图片

片元着色器 完全可编程,用于实现逐片元的着色操作,片元着色器是unity另一个主要着色器,这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到起覆盖的片元的纹理坐标,片元着色器的输出是一个或者多个颜色值,一般是一个颜色值,多个颜色值在一些特殊需求的时候可能会用到。

逐片元操作 负责重要操作,如修改颜色,深度缓冲,颜色混合等,可配置,这是在OpenGL中的称呼,相同的这个阶段在DX中被称为输出合并阶段,在这个阶段主要进行两个操作,

  1. 确定每个片元的可见性,为了达到这个目的,会执行比较多的测试工作,包括深度测试,模板测试,透明度测试等
  2. 对每个通过可见性测试的片元进行与颜色缓冲区的颜色值的混合操作,混合之后再写入到颜色缓冲区,混合操作种类涉及到比较多,如直接覆盖。有点像PS里面的上层面板与下层面板的叠加效果,像高光,溶解等。
    逐片元操作的顺序
    在这里插入图片描述

逐片元操作的每个环节

模板测试
模板测试流程大概如下,每个片元会有一个模板值,是下图中第三个框的参考值,然后每个像素的位置会有一个模板缓冲值,每个像素所在的位置的模板缓冲值在程序最开始时候默认是0,参考值和该像素所在位置的模板缓冲值会进行比较,由开发者定义参考值与缓冲值比较偏大还是偏小还是大于等于或者小于等于算通过,不通过则会舍弃片元不进行渲染,由开发者定义通过还是不通过时更新模板缓冲的值,新的参考值就是和已更新的模板缓冲的值进行比较了
Unity学习shader笔记[九]渲染流水_第6张图片
深度测试
这里的每个片元深度值的是在顶点着色器阶段将顶点从模型空间转换到齐次裁剪空间坐标系之后的z值的插值,离摄像机越近的片元深度值越小, OpenGL的齐次裁剪空间坐标的z值取值范围是0到1,DX是-1到1。深度测试的流程和模板测试差不多,深度测试是在模板测试之后进行的,下面是深度测试的过程:
Unity学习shader笔记[九]渲染流水_第7张图片
每个片元会有一个深度值,然后每个像素的位置会有一个深度缓冲值,每个像素所在的位置的深度缓冲值在程序最开始时候默认是0,参考值和该像素所在位置的深度缓冲值会进行比较,由开发者定义参考值与缓冲值比较偏大还是偏小还是大于等于或者小于等于算通过,在大多数情况下,开发者会设置小于等于算通过,和模板测试不同的是,如果一个片元没有通过深度测试,该片元所在像素点的深度缓冲的值是不会被更改的,并且片元通过深度测试,也要看开发者是否将深度写入的开关打开才能决定是否写入深度缓冲,在一般情况下会开启深度写入,但是如果有透明物体的时候,透明物体上面的shader一般不会开启深度写入,因为假设某个像素点透明物体的片元的深度值是0.1,它相同位置的后面的片元的深度值是0.3,如果开启了深度写入,则这个像素点目前的深度是0.1,如果这时突然有个深度是0.2的片元在这个像素点出现,按道理它应该被渲染,因为他的深度夹在0.3和0.1之间,而且0.1深度的片元是半透明的,但是开启了深度写入后第一次比较的0.1已经写入缓冲区,这时深度为0.2的片元是不会通过测试的,也就是会被舍弃掉,透明物体开启深度写入可以在游戏有一些特殊需求的时候考虑,比如某个物体进入了某个半透明的东西的部分就消失了

合并混合
渲染过程是一个物体接着一个物体画到屏幕上,而屏幕上每个像素的颜色信息在被渲染之前被储存在一个名为颜色缓冲的地方。因此,当我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,那么,我们使用这次渲染得到的颜色完全覆盖掉之前的结果还是进行其他处理,就是合并需要解决的。下面是合并混合的过程:
Unity学习shader笔记[九]渲染流水_第8张图片
对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。因为不透明物体的遮挡不需要对被遮挡的物体进行任何的处理,被遮挡的物体是完全看不见了,但对于半透明物体,就需要混合操作来让这个物体看起来是透明的,混合操作可选的类型是比较多的,在项目中根据实际情况由开发者选择决定
不透明物体渲染会造成性能问题的一个原因是合并混合操作引起的性能开销。

混合
只要有关键字Blend,就代表开启混合(除Blend off以外)

Unity学习shader笔记[九]渲染流水_第9张图片
在第一个命令中,两个因数不仅影响到了RGB,也影响到了透明度a,第二个命令与第一个命令的区别在于 第二个命令的前两个参数只操作了RGB,后面两个因数只操作了透明度a
用式子表达就是
在这里插入图片描述
其中,源颜色用S表示,目标颜色用D表示,输出颜色用O表示。
Unity学习shader笔记[九]渲染流水_第10张图片
上面只介绍了一种混合等式加法,这是unity默认的混合等式,可以通过语句设置其他的混合等式,默认是BlendOp Add ,如下图所示的众多关键字中,将其替换Add即可改变接下来的混和计算
等式Unity学习shader笔记[九]渲染流水_第11张图片
在上面表格中可以发现,Min,Max计算中,混合因数是对计算没有影响的

通过混合操作和混合因子命令的组合,我们可以得到一些类似Photoshop 混合模式中的混合效果:

// 正常(Normal),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
// 柔和相加(Soft Additive)
Blend OneMinusDstColor One
//正片叠底(Multiply),即相乘
Blend DstColor Zero
//两倍相乘
Blend DstColor SrcColor
//变暗(Darken)
BlendOp Min
Blend One One
//变亮(Lighten)
BlendOp Max
Blend One One
//滤色(Screen)
Blend OneMinusDstColor One
//等同于
Blend One OneMinusSrcColor
//线性减淡(Linear Dodge)
Blend One One

下图给出了上面不同设置下得到的结果。
Unity学习shader笔记[九]渲染流水_第12张图片


其他

各种测试的顺序并不是唯一的,虽然从逻辑上来说这些测试是在片元着色器之后进行的,但对于大多数GPU来说,会尽可能在执行片元着色器之前进行这些测试。因为当你在片元着色器进行了大量的计算及设置,最后测试没通过,可以说是计算成本全都浪费了。作为一个想充分提高速度的GPU,肯定是希望尽可能早的指定哪些片元会被舍弃,对这些片元就不再需要在使用片元着色器来计算他们的颜色。Unity的渲染流水中,深度测试等就是在片元着色器之前。
如果将这些测试提前的话,其检验结果可能会与片元着色器中的一些操作冲突。例如片元着色器在进行透明度测试,而这个片元没有通过透明度测试,如果片元着色器中进行了透明度测试的话,会与unity的提前测试产生冲突 ,unity就会将深度测试等在片元着色器之前的操作禁用掉。这就导致GPU无法提前执行各种测试。 这样性能上就会下降,也是透明度测试导致性能下降的原因。
当模型图元经过层层计算及测试后,就会显示到屏幕上。我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到正在光栅化的图元,GPU会使用双重缓冲策略。对场景的渲染是发生在幕后的,即在后置缓冲中,一旦场景已经被渲染到后置缓冲中,GPU就会交换后置缓冲区和前置缓冲的内容,前置缓冲区就是显示在屏幕上的图像。由此,保证我们看到的图像是连续的。
注意:这里的流水线名,顺序,在不同资料上看到可能是不一样的。一个原因是由于图像编程接口的实现不尽相同,另一个是GPU底层可能做一些优化等等。

  • CPU与GPU如何并行工作?

    CPU和GPU并行工作,使用命令缓冲区(Command Buffer)。
    命令缓冲区包含了一个缓冲队列,由Cpu向其中添加命令,而由Gpu从中读取命令,添加和读取过程是相互独立的。命令缓冲区使得Cpu和Gpu可以相互独立工作。当Gpu需要渲染一些对象时,它就可以从命令队列中取出一个命令并执行。
    命令缓冲区有很多种类,Draw Call就是一种。其他命令还有改变渲染状态等。

  • 什么是固定管线渲染?

    固定函数的流水线(Fixed-Function Pipeline),简称固定管线,通常是指在较旧的Gpu上实现的渲染流水线。这种流水线只给开发者提供一些配置操作,但开发者没有对流水线阶段的完全控制权。
    在Unity中目前的固定管线shader都会自动编译顶点片元shader。

  • 什么是Shader?

    Gpu流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在Gpu上运行的;有一些特定类型的着色器,如顶点着色器,片元着色器等。
    依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换及传递数据,用片元着色器来进行逐像素渲染。

你可能感兴趣的:(Shader)