Unity Shader入门精要

Unity Shader入门精要

声明:
本文章的学习内容来源全部出自《UnityShader入门精要》——冯乐乐
该文章只是本人我的学习笔记,里面对《UnityShader入门精要》进行了些许概括
如果想更加具体地了解其内容,建议购买原著进行学习

什么是渲染流水线?

------即Unity中的渲染管线!
由一个三维场景出发,将3D数据(顶点数据,光照数据,材质数据等)生成2D图像的过程。通常由CPU和GPU共同完成!

渲染流水线的三大阶段——应用阶段、几何阶段、光栅化阶段

在这里插入图片描述

1.应用阶段

目的:

通过CPU完成输出每个模型渲染所需的点线面等几何信息(即渲染图元),传递给下一个阶段—几何阶段!其中最重要的就是顶点数据!

应用阶段细分为三个阶段:
  1. 把数据从硬盘加载到显存中。
  2. 设置渲染状态。即定义场景中的网格是怎样被渲染!例如光照属性,材质等。如果我们没有更改渲染状态, 那么所有的网格都将使用同一种渲染状态,即unity中所有的自带几何体都是用的默认材质就可理解为同一种渲染状态。
  3. 由CPU向GPU发送Draw Call 命令,开始执行GPU渲染流水线的过程。
    这个命令仅仅会指向一个需要被渲染的图元 (primitives) 列表,而不会再包含任何材质信息,这是因为我们已经在上一个阶段中完成了。

补充说明:
(1)所有渲染所需的数据都需要从硬盘 Hard Disk Drive, 印江))中加载到系统内存 RandomAccess Memory, RAM) 。然后 ,网格和纹理等数据又被加载到显卡上的存储空间一显存(VideoRandom Access Memory, VRAM 中。这是因为,显卡对于显存的访问速度更快,而且大多数显卡对于 RAM 没有直接的访 问权利 。
当把数据加载到显存中后, RAM 中的数据就可以移除了。 但对于一些数据来说, CPU 仍然需要访问它们(例如, 我们希望 CPU 可以访问网格数据米进行碰撞检测), 那么我们可能就不希 望这些数据被移除, 因为从硬盘加载到 RAM 的过程是十分耗时的。

(2) Draw Call 本身的含义很简单,就 是CPU调用图像编程接口,如 OpenGL 中的 glDrawelements 命令或者 DirectX 中的 DrawlndexedPrimitive命令,以命令 GPU 进行渲染的操作。

应用阶段的3个主要任务:
  • 准备好场景数据:例如相机位置、视锥体、场景里的模型、光源…
  • 作粗粒度剔除,剔除那些看不见的物体,这样就不需要移交给几何阶段
  • 设置每个模型的渲染状态:包括材质(漫反射颜色、高光反射颜色)、使用的纹理和Shader等。

GPU渲染流水线:

当给定了一个 Draw Call 时, GPU 就会根据渲染状态(例如材质、 纹理、 若色器等)和所有输入的顶点数据来进行计算, 最终输出成屏器上显示的那些漂亮的像素。 而这个计算过程, 就是GPU 流水线。
在这里插入图片描述

2.几何阶段

GPU流水线以顶点数据作为输入,这些顶点数据 是由 应用阶段加载到显存中,再由Draw Call指点的。

模型空间:3D物休的原始坐标,存储在模型中。
世界空间:物体放在场景中,才能构成整个游戏场景。
观察空间:需要以相机为原点的3维空间.
裁剪空间:物体是否在相机的渲染范围内。
屏幕空间:3D物体投影到屏幕上。

目的:

将应用阶段所传递的顶点数据经过坐标转换,顶点着色,裁剪,屏幕映射后,由原来的模型空间下的顶点转化成屏幕空间下的二维顶点,并将相关数据(每个顶点对应的深度值、着色等相关信息)传递给下一阶段—光栅化阶段!

详细执行步骤:
2.1 -顶点着色器(Vertex Shader)

顶点着色器的处理单位是顶点(从CPU来),输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可创建和销毁顶点,也无法得到顶点与顶点之间的关系。

例如,我们无法得知两个顶点是否属于同一个三角网格。

但正是因为这样的相互独立性, GPU可以利用本身的特性并行化处理每一个顶点 ,这意味着这一阶段的处理速度会很快。

顶点着色器主要完成的工作:
  • 坐标变换(改变顶点的位置,实现顶点动画。例如,我们可以通过改变顶点位置来模拟水面流动效果等);
    在这里插入图片描述

  • 需要时还可以进行逐顶点光照计与计算顶点颜色
    计算每个顶点的光照信息,比如明亮,阴影等。图中小球即通过各顶点法向量与光源坐标进行了简单的漫反射计算。当然除了计算光照,其他与顶点颜色相关的操作都可以在这个阶段里进行。值得一提的是,这里仅仅是“计算顶点的光照与颜色信息”,还不是真正的着色,可以理解为“为接下来的着色提供一些信息”。
    在这里插入图片描述

在这里插入图片描述

  • 当然,除了这两个主要任务外,顶点着色器还可以输出后续阶段所需的数据,例如顶点法线的计算。

顶点着色器必须完成的一个工作:把顶点坐标从模型空间转换到齐次裁剪空间,接着通常再由硬件作透视除法,最终得到归一化的设备坐标 (Normalized Device Coordinates, NDC )。

从模型空间转换到齐次裁剪空间经过的具体过程:模型空间–>世界空间–>观察空间–>裁剪空间!

在这里插入图片描述

2.2 -曲面细分着色器(Tessellation Stage)

在这一阶段,程序员可以进行曲面细分操作看起来就像在原有的图元内加入了更多的顶点。对于一些有大量曲面的模型,进行曲面细分可以让曲面更加圆润;
在这里插入图片描述
如果为这些细分的顶点再准备一些位置信息,那么这些细分的顶点将有助于我们展现一个细节更加丰富的模型。这也是**贴图置换(Displacement Mapping)**的基本思路。
在这里插入图片描述

细分流程:
在这里插入图片描述

  • 在进入Tessellation Stage之前,流水线还将经过Hull-Shader Stage,这是一个可编程的阶段,开发者可以指挥GPU如何对顶点进行细分操作,但还不会真正进行细分,就像是指挥流水线上的工人说:“来,帮我把这给这根钢筋中间打上三个标记,好让后面的工人在上面安上旋钮。”

  • Tessellation Stage是真正的细分阶段;尽管开发者无法在这个阶段进行编程,但GPU将会根据Hull-Shader Stage中的标记进行细分;就像是流水线上的工人木讷地照着传过来的钢筋上的标记安装上旋钮。

  • 在离开Tessellation Stage之后,流水线将进入Domain-Shader Stage,这是一个可编程的阶段,开发者可以指挥GPU对这些细分的顶点进行坐标计算;就像是指挥流水线上的工人如何调整上一个流程里工人安装上的旋钮,把钢筋摆成想要的形状。

2.3 -几何着色器(Geometry Shader)

在这个阶段,开发者可以控制GPU对顶点进行增删改操作。几何着色器与顶点着色器都可以对顶点的坐标进行修改,但几何体着色器并行调用硬件困难,并行程度低,效率和顶点着色器有很大的差距;如果不是要做顶点增、删这些仅仅能用几何着色器实现的效果,那么还是用顶点着色器来完成吧。
在这里插入图片描述

2.4 -裁剪(Clipping)

将不在摄像机视野内(即摄像机视锥体内)的顶点进行裁剪,并剔除某些三角图元的面片。即Unity中Camera的裁剪平面距实现的功能。
在这里插入图片描述

2.5 -屏幕映射(Screen Mapping)

把NDC归一化的设备坐标系下的 x和y 坐标转换到屏幕坐标系下的坐标,这个过程实质上就是一个缩放过程。
在这里插入图片描述
在这里插入图片描述

那么输入的z坐标会怎么样呢?屏幕映射不会对输入的z坐标做任何处理。实际上,屏幕坐标系和 z坐标 起构成了 窗口坐标。这些值会一起被传递到光栅化阶段。

屏幕映射之后,几何阶段结束,输出的是:屏幕坐标系下的二维顶点位置,以及和它相关的额外信息:如深度值(z坐标)、法线方向、视角方向…

3.光栅化阶段

目的:

由GPU决定每个渲染图元中哪些像素应该被绘制在屏幕上。它需要对上一个阶段得到的逐顶点数据(二维顶点)(例如纹理坐标,顶点颜色等)进行插值(实际上为了确定顶点与顶点之间的点),然后进行逐像素处理!

详细执行步骤:

光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。

3.1 -三角形设置

几何阶段我们输出的是三角网格的顶点,但是我们需要得到整个三角形网格对像素的覆盖情况,因此我们就必须计算三角形每条边上的像素坐标。
换而言之,根据顶点生成三角形

3.2 -三角形遍历(Triangle Traversal)(扫描变换)

通过三角形遍历确定每个三角形对像素的覆盖情况。如果像素被覆盖,就会生成一个片元 (fragment),反之则无片元生成 。最终输出片元序列!
在这里插入图片描述

三角形遍历段会根据上个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格的三个顶点的顶点信息对整个覆盖区域的像素进行插值。

这一步的输出就是得到一个片元序列。需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了(但不限于)它的屏幕坐标深度信息,以及其他从几何阶段输出的顶点信息(例如法线、纹理坐标等)。

3.3 -片元着色器(Fragment Shader)

片元着色器是可编程着色器阶段,在DirectX中也叫像素着色器 (Pixel Shader),但片元着色器是 个更合适的名字,因为此时的片元并不是一个真正意义上的像素。

前面的三角形遍历不会影响屏幕上的每个像素颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据。真正会对像素产生影响的阶段是下一个流水线阶段------逐片元操作 (Per-Fragment Operations) 。

片元着色器的输入是上个阶段(三角形遍历)对顶点信息插值得到的结果,更具体来说,是根据那些从顶点着色器中输出的数据插值得到的。而它的输出每个片元的着色相关信息
在这里插入图片描述
重点:纹理采样
我们通常会在顶点着色器阶段输出每个顶点的纹理坐标,然后经过三角形遍历阶段队三角网格的 三个顶点对应的纹理坐标进行插值后,就得到了覆盖的片元的纹理坐标了。
在这里插入图片描述
此外,程序员还可以引入更多的信息计算颜色,包括法线贴图、高度图、糙度图等等。虽然片元着色器可以完成很多重要效果,但它仅可以影响单个片元。也就是说,当执行片元着色器时,它不可以将自己的任何结果直接发送给它附近的片元的。

3.4-逐片元操作

渲染流水线的最后一步,这一步在OpenGL中叫逐片元操作,在DirectX中叫输出合并阶段

3.4.1 -两个主要任务:
  • 决定每个片元的可视性。这涉及到很多测试工作,如:深度测试、模板测试等。
  • 如果片元通过了所有测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并(或者说混合)。
3.4.2 -两个最基本的测试

深度测试模板测试的实现过程。能否理解这些测试过程将关乎后面章节中提到的渲染队列,尤其是处理透明效果时出现的问题 。

  • 模板测试通常用千限制渲染的区域。另外,模板测试还有一些更高级的用法 如渲染阴影、轮廓渲染等。
    在这里插入图片描述
  • 深度测试通常用于如何表现渲染物体之间的遮挡关系(即决定被遮挡的片元是否要出现在屏幕上)。透明效果和深度测试以及深度写入的关系非常密切。
    在这里插入图片描述
3.4.3 -为什么需要合并(混合)?

合并有两种主要的方式,一种是直接进行颜色的替换,另一种是根据不透明度进行混合(Blend)

这里所讨论的渲染过程是一个物体接着一个物体画到屏幕的。而每个像素的颜色信息被存储在一个名为颜色缓冲的地方。因此,当我们执行这次渲染时颜色缓冲中往往已经有了上次渲染之后的颜色结果,那么,我们是使用这次渲染得到的颜色完全覆盖掉之前的结果,还是进行其他处理?这就是合并需要解决的问题

  • 对于不透明物体,开发者可以关闭混合 (Blend) 操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。
  • 对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。GPU 会取出源颜色和目标颜色, 将两种颜色进行混合。 源颜色指的是片元着色器得到的颜色值, 而目标颜色则是已经存在于颜色缓冲区中的颜色值。 之后, 就会使用一个混合函数来进行混合操作。 这个混合函数通常和透明通道息息相关, 例如根据透明通道的值进行相加、 相减、 相乘等。 混合很像
    Photoshop 中对图层的操作:每一层图层可以选择混合模式,混合模式决定了该图层和下层图层的混合结果, 而我们看到的图片就是混合后的图片。
    在这里插入图片描述
3.4.5 -注意:上面给出的测试顺序并不是唯一的

虽然从逻辑上来说这些测试是在片元着色器之后进行的,但对于大多数 GPU来说,它们会尽可能在执行片元着色器之前就进行这些测试。

原因分析:
当 GPU 在片元着色器阶段花了很大力气终于计算出片元的颜色后, 却发现这
个片元根本没有通过这些测试, 也就是说这个片元还是被舍弃了, 那之前花费的计算成本全都浪费了!
作为一个想充分提高性能的 GPU, 它会希望尽可能早地知道哪些片元是会被舍弃的,对于这些舍弃的片元就不需要再使用片元着色器来计算它们的颜色。 在Unity 给出的渲染流水线中, 我们也可以发现它给出的深度测试是在片元着色器之前。这种将深度测试提前执行的技术通常也被称为Early-Z技术。
在这里插入图片描述
但是,如果将这些测试提前的话, 其检验结果可能会与片元着色器中的一些操作冲突。 例如,如果我们在片元着色器进行了透明度测试, 而这个片元没有通过透明度测试, 我们会在着色器中调用 API例如 clip 函数) 来手动将其舍弃掉。 这就导致 GPU 无法提前执行各种测试。

因此,现代的 GPU 会判断片元着色器中的操作是否和提前测试发生冲突,如果有冲突,就会禁用提前测试。但是, 这样也会造成性能上的下降, 因为有更多片元需要被处理了。 这也是透明度测试会导致性能下降的原因。

在经过上面的层层测试后,片元颜色就会被送到颜色缓冲区
GPU 会使用 双重缓冲 (Double Buffering) 的策略,这意味着,对场景的渲染是在幕后发生的, 即在后置缓冲 (Back Buffer) 中。 一旦场景已经被渲染到了后置缓冲中, GPU 就会交换后置缓冲区和前置缓冲 (Front Buffer) 中的内容, 而前置缓冲区是之前显示在屏幕上的图像。 由此, 保证了我们看到的图像总是连续的。
(即屏幕上显示前置缓冲(Front Buffer),而渲染好的颜色先被送入后置缓冲(Back Buffer),再替换前置缓冲,以此避免在屏幕上显示正在光栅化的图元。)

关于DrawCall命令

在这里插入图片描述
问题二:为什么DrawCall过多会影响帧率?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值