图形接口

Opengl和DirectX

UnityShader所使用的是CG语言,CG语言在OpenGl和DirectX之上

渲染流水

普遍的渲染管线的流程:

下图是基本描述

image-20241029143955055

image-20241029161109518

应用阶段:

1.把数据加载到显存中
2,设置渲染状态
3,调用DrawCall

把数据加载到内存中做的事情

数据会从硬盘到内存再到显存,一般加载到显存中的数据都会被从内存中移除

设置渲染状态做的事情

这些状态定义了场景中的网格是如何被渲染的,例如,使用哪个定点着色器,片元着色器,光照,材质等

调用DrawCall

Drawcall是一个命令,CPU发起,GPU接收,不包含任何材质信息,仅仅指向需要被渲染的图源列表
(Unity中存在的DrawCall优化,GPUInstance,这里仅仅是发音比较像,剩余的需要自己去查找)

几何阶段和光栅化阶段

两个阶段能被开发者使用代码控制的不多,具体可见ppt中对应的内容

几何阶段

定点着色器,曲面细分着色器,几何着色器,裁剪,屏幕映射

顶点着色器

处理单位是定点,每个定点都会调用一次定点着色器

空间转换

模型空间-》齐次裁剪空间-》计算顶点颜色
o.vertex = mul(UNITY_MVP,v.vertex); //Unity5.x
o.vertex = UnityObjectToClipPos(v.vertex); //Unity2017 及其以上

裁剪

一个图元与摄像机的关系有三种,内,外,部分内

屏幕映射
任务是将裁剪后的齐次坐标(HDC)转化到屏幕坐标系,屏幕坐标系是一个二维坐标系
(OpenGl左下角是零点,DX左上角是零点)

光栅化阶段

主要操作

三角形设置,三角形遍历,片元着色器,逐片元操作

三角形设置

这个阶段会计算光栅化一个三角网格所需要的信息。上一个阶段输出的都是三角网格的顶点,即我们得到的是三角网格每条边的两个端点。如果要得到正规三角网格对像素的覆盖情况,就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,就需要得到三角形边界的表示方式。

三角形遍历

三角形遍历 阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的情况下,就会产生一个片元。而这样一个找到那些像素被三角网格覆盖的过程就叫做三角形遍历,也被称作扫描变换

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

片元着色器

片元着色器的输入是上一个阶段对顶点信息插值得到的结果,具体来说是根据那些从顶点着色器中输出的数据插值得到的。而其输出是一个或者多个颜色值。

​ 这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到起覆盖的片元的纹理坐标

逐片元操作

逐片元操作是OpenGL中的说法,在DX中这个阶段被称作输出合并阶段

(1)决定每个片元的可见性,涉及很多测试工作,例如深度测试,模板测试。

(2)如果一个片元通过了所有测试后,就需要把这个片元的颜色值和已经储存在颜色缓冲区的色彩进行合并,或者说混合

image-20241029144526686

​ 测试的过程实际上是个比较复杂的过程,而且不同的图形接口(OpenGL和DX)的实现细节也不尽相同。

模板测试

与之相关的是模板缓冲(Stencil Buffer)。模板缓冲和颜色缓冲,深度缓冲几乎是一类东西。如果开启了模板测试,Gpu首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考值进行比较,这个比较函数可以由开发者指定的,例如小于等于舍弃该片元,或者大于等于舍弃该片元。如果这个片元没有通过测试,该片元就会被舍弃。不管一个片元有没有通过模板测试,我们都可以根据模板测试和之后的深度测试结果来修改模板缓冲区,这个操作也是由开发者指定的。模板测试通常用于限制渲染区域,或者渲染阴影,轮廓渲染等

image-20241029144347791
深度测试

如果开启了深度测试,Gpu会把该片元的深度值和已经存在于深度缓冲中的深度值进行比较。这个比较函数也是可由开发者设置的,例如小于时舍弃该片元,或者大于时舍弃该片元。通常这个比较函数是小于等于,即如果这个片元的深度大于等于当前深度缓冲区中的值,那么就舍弃它。这是因为我们总想只显示出离摄像机最近的物体,而那些被其他物体遮挡的就不需要出现在屏幕上。和模板测试不同的是,如果一个片元没有通过深度测试,它就没有权利更改深度缓冲区的值。如果一个片元通过了测试,那么开发者可以指定是否要用这个片元的深度值覆盖所有的深度值

image-20241029144429455
合并混合

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

对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会之间覆盖掉颜色缓冲区中的像素值。但对于半透明物体,就需要混合操作来让这个物体看起来是透明的。

image-20241029145045268

测试总结

各种测试的顺序并不是唯一的,虽然从逻辑上来说这些测试是在片元着色器之后进行的,但对于大多数GPU来说,会尽可能在执行片元着色器之前进行这些测试。因为当你在片元着色器进行了大量的计算及设置,最后测试没通过,可以说是计算成本全都浪费了。作为一个想充分提高速度的GPU,肯定是希望尽可能早的指定哪些片元会被舍弃,对这些片元就不再需要在使用片元着色器来计算他们的颜色。Unity的渲染流水中,深度测试就是在片元着色器之前。

但是,如果将这些测试提前的话,其检验结果可能会与片元着色器中的一些操作冲突。例如片元着色器在进行透明度测试,而这个片元没有通过透明度测试,我们会在着色器中调用API(clip)函数来手动将其舍弃。这就导致GPU无法提前执行各种测试。因此,如果片元着色器中的操作和提前测试发生冲突就会禁用提前测试。这样性能上就会下降,也是透明度测试导致性能下降的原因。

​ 当模型图元经过层层计算及测试后,就会显示到屏幕上。我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到正在光栅化的图元,GPU会使用双重缓冲策略。对场景的渲染是发生在幕后的,即在后置缓冲中,一旦场景已经被渲染到后置缓冲中,GPU就会交换后置缓冲区和前置缓冲的内容,前置缓冲区就是显示在屏幕上的图像。由此,保证我们看到的图像是连续的。

附加知识

1,CPU与GPU如何并行工作?

​ 我们之前看到的是一个流水线式的模式,如果需要CPU和GPU并行工作,就需要使用命令缓冲区(Command Buffer)。

​ 命令缓冲区包含了一个缓冲队列,由Cpu向其中添加命令,而由Gpu从中读取命令,添加和读取过程是相互独立的。命令缓冲区使得Cpu和Gpu可以相互独立工作。当Gpu需要渲染一些对象时,它就可以从命令队列中取出一个命令并执行。

​ 命令缓冲区有很多种类,Draw Call就是一种。其他命令还有改变渲染状态等。

image-20241029150329421

2,什么是固定管线渲染?

固定函数的流水线(Fixed-Function Pipeline),简称固定管线,通常是指在较旧的Gpu上实现的渲染流水线。这种流水线只给开发者提供一些配置操作,但开发者没有对流水线阶段的完全控制权。

​ 在Unity中目前的固定管线shader都会自动编译顶点片元shader。

3,什么是Shader?

Gpu流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在Gpu上运行的;

有一些特定类型的着色器,如顶点着色器,片元着色器等。

依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换及传递数据,用片元着色器来进行逐像素渲染。