渲染管线

本文章为:《Unity Shader入门精要》

github:candycat1992/Unity_Shaders_Book: :book: 书籍《Unity Shader入门精要》源代码 (github.com)

都是随心记录,可能繁杂,无序

渲染管线

输入:一个虚拟摄像机、一些光源、一些shader以及纹理、模型顶点等。

输出:一张人眼可以看见的图像。

通常由CPU和GPU共同完成。

 

概括:通常被分为三个阶段:

应用阶段:

1.搭场景。准备好模型,光源,相机位置、fov

2.删模型:剔除、剪裁看不见的部分

3.准备数据:准备好模型的材质、纹理、shader

4.输出一群渲染图元(一个渲染图元可以是一个点、一根线、一个三角面)

几何阶段:

1.决定需要绘制的图元是什么,怎样绘制、在哪里绘制(通常在GPU上运算)

2.和每个渲染图元打交道,进行逐顶点、逐多边形的操作

3.会输出屏幕空间的二维顶点坐标、每个顶点对应的深度值、着色等相关信息。

光栅化阶段:

会使用上个阶段传递的数据来产生屏幕上的像素,并渲染出最终的图像(通常在GPU上运算)

详细:渲染流水线

应用阶段:

1.CPU把数据加载到显存中(本来是在内存中的,但一般显卡读取不了内存)

2.将内存中的数据清除

3.设置渲染状态

(什么是渲染状态呢? 一个通俗的解释就是, 这些状态定义了场景中的网格是怎样被渲染的。

例如,使用哪个顶点着色器/片元着色器、光源属性、材质等。如果我们没有更改渲染状态,那么所有的网格都将使用同一种渲染状态。)

4.调用DrawCall(Draw Call 就是一个命令, 它的发起方是CPU, 接收方是GPU 。这个命令仅仅会指向一个需要被渲染的图元列表,而不会再包含任何材质信息)


5.GPU流水线:

几何阶段

顶点着色器:是完全可编程的,它通常用千实现顶点的空间变换 顶点着色等功能;

曲面细分着色器:是一个可选的着色器,它用于细分图元;

几何着色器:同样是一个可选的着色器,它可以被用于执行逐图元的着色操作,或者被用于产生更多的图元;

裁切:这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些 角图元的面片。这个阶段是可配置的。例如,我们可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面;

屏幕映射:这阶段是不可配置和编程的,它负责把每个胆元的坐标转换到屏幕坐标系中。

 

光栅化阶段:

三角形设置和三角形遍历也是固定函数,听他名字也大概知道是啥;

片元着色器:另一个着色器,用于实现逐片元的着色操作;

逐片元操作:负责执行很多重要的操作,例如修改颜色、深度缓冲、进行混合等,它不是可编程的,但具有很高的可配置性。

 

根据上面的,继续深入

顶点着色器

输入来自CPU;

处理单位是顶点;

也就是说,输入进来的每个顶点都会调用一次顶点着色器;

顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如,我们无法得知两个顶点是否属于同一个三角网格。

坐标变换:顾名思义,就是对顶点的坐标(即位置)进行某种变换。顶点着色器可以在这一步中改变顶点的位置,这在顶点动画中是非常有用的。

例如,我们可以通过改变顶点位置来模拟水面、布料等。

但需要注意的是无论我们在顶点着色器中怎样改变顶点的位置,一个最基本的顶点着色器必须完成的一个工作是,把顶点坐标从模型空间转换到齐次裁剪空间。

类似上面这句代码的功能,就是把顶点坐标转换到齐次裁剪坐标系下。

 

裁切

那些不在摄像机视野范围的物体不需要被处理。

一个图元和摄像机视野的关系有:完全在视野内、部分在视野内、完全在视野外。

完全在视野内的:图元就继续传递给下一个流水线阶段,

完全在视野外的:图元不会继续向下传递,因为它们不需要被渲染。

而那些部分在视野内的图元:需要进行一个处理,这就是裁剪。

例如 ,一条线段的一个顶点在视野内 ,而另一个顶点不在视野内,那么在视野外部的顶点应该使用一个新的顶点来代替,这个新的顶点位于这条线段和视野边界的交点处

屏幕映射

这一步输入的坐标仍然是三维坐标系下的坐标(范围在单位立方体内)。

把每个图元的x和y坐标转换到屏幕坐标系

屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系。

也是因此,屏幕映射不会对z轴做任何处理

实际上,屏幕坐标系和 坐标 起构成了 个坐标系,叫窗口坐标系。这些值会一起被传递到光栅化阶段。

三角形遍历

三角形遍历阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元。

而这样一个找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也被称为扫描变换。

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

这一步的输出就是得到一个片元序列

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

可以说就是一个像素对应了一个list,一个list就是一个片元,里面存的是大量三角形遍历加插值之后的信息。

片元着色器

前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。

每个片元就负责存储这样一系列数据。真正会对像素产生影响的阶段是下一个流水线阶段一—逐片元操作

 

片元着色器的输入是上一个阶段对顶点信息插值得到的结果,还有贴图;

更具体来说,是根据那些从顶点着色器中输出的数据插值得到的,还有每个顶点对应的纹理坐标及其插值;

而它的输出是一个或者多个颜色值。

像这里就是对14个片元进行了运算,但

它有局限,就是仅可以影响单个片元,它不可以将它的任何结果发给它的邻居们,但

它可以访问到导数信息(其他知识点)

 

逐片元操作

该步骤是渲染流水线的最后一步:合并,对每一个片元进行一些操作

那么问题来了,要合并哪些数据?又要进行哪些操作呢?

1.决定每个片元的可见性。这涉及到很多测试工作,例如深度测试、模板测试等;

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

 

决定可见性:

这个阶段首先需要解决每个片元的可见性问题。这需要进行一系列测试

这就好比考试,一个片元只有通过了所有的考试,才能最终获得和 GPU 谈判的资格,这个资格指的是它可以和颜色缓冲区进行合并

如果它没有通过其中的某 个测试,那么对不起,之前为了产生这个片元所做的所有工作都是白费的,因为这个片元会被舍弃掉。

但其实这也只是一部分,不同的图形接口(OpenGL/DX),它的实现细节也会不一样

模板测试:

就是一些用户自定义的函数,将该片元导入到某个函数中,和某个参考值进行比较,

通过了模板测试的片元便会被加入到模板缓冲区

上述的某个参考值可能就是来自于模板缓冲中的默认值,比如一些不透明的阴影。

模板测试通常用于限制渲染的区域。另外,模板测试还有一些更高级的用法,如渲染阴影、轮廓渲染等。

深度测试

如果开启了深度测试, GPU会把该片元的深度值和已经存在于深度缓冲区中的深度值进行比较。

并把深度值小的留下来。

就是从相机的远处开始渲染,一次次循环、比较,最后得到离相机最近的物体。

另一点就是透明物体的深度测试,如玻璃、阴影,这也会有不同的操作。

合并

为什么需要合并?

我们要知道,这里所讨论的渲染过程是一个物体接着一个物体画到屏幕的。

而每个像素的颜色信息被存储在一个名为颜色缓冲的地方。

因此,当我们执行这次渲染时颜色缓冲中往往已经有了上次渲染之后的颜色结果,

那么,我们是使用这次渲染得到的颜色完全覆盖掉之前的结果,还是进行其他处理?这就是合并需要解决的问题。

 

对于不透明物体,可以选择直接覆盖上一帧渲染的内容

对于透明物体,可以选择留着上一帧渲染出来的颜色,并合并当前不透明颜色。

至于怎么混合,就是把两个颜色传进一个函数,然后像PS中对图层的混合模式效果一样

优化

在片元着色器阶段,长方形渲都渲了出来,但还是被舍弃了大半。

因此在Unity给出的渲染流水线中, 我们也可以发现它给出的深度测试是在片元着色器之前。这种将深度测试提前执行的技术通常也被称为Early-Z技术。

但测试提前也会有各种各样的问题出现,例如片元会有透明度测试,完全透明的片元就不会去渲染,但该片元可能是通过了Early-Z测试

于是就会出现完全透明的一块区域,尽管他后面有不透明的物体。

双重缓冲:

之前不是说了颜色缓冲区就是屏幕上显示是颜色吗,

而颜色缓冲是一个像素一个像素显示上去的,就像是正在渲染的图像,是一点点变得清晰起来的

因此,为了避免我们看到那些正在进行光栅化的图元

就有了双重缓冲技术,即两个颜色缓冲区

对场景的渲染是在幕后发生的,即后置缓冲,

而显示在屏幕上的图像,就是前置缓冲。

由此, 保证了我们看到的图像总是连续的。

总结

  • 虽然写了很多,但真实的过程绝对是比这复杂的
  • 其他地方和这里的叫法、顺序可能不同,可能是图形接口(OpenGL/DX)不同,也可能是GPU技术迭代过了,例如深度测试等
  •  

案例

1.模型空间->世界坐标(鼻子的原点是父物体原点,需要改成世界坐标原点)

 

2.世界坐标->观察空间(鼻子的原点是世界坐标原点,需要改成摄像机原点,摄像机的坐标就是观察空间的原点)

(ps,观察坐标用的右手坐标系,其余均为左手)

 

3.观察空间->裁切空间(鼻子的原点是摄像机原点,需要改成视锥原点,同时会有近大远小的处理,剔除看不见的地方,裁切等操作,同时在矩阵变换后,w的值会被赋予特别的含义,即是否在视锥体内)

(此时会运行顶点着色器,去拿材质,去做顶点动画等等等等)

 

4.裁切空间->屏幕空间(把椎体变成2*2的正方体,由于w被赋予了特别的含义,此处直接把某点的xyz除以一个w就可以得到该点在2*2立方体内的坐标了)

(然后就是三角形遍历,拿到一堆片元)

(然后就是把每个片元放到片元着色器里运行一次)

(然后就是根据该点的xy坐标,把该点投射到屏幕坐标上,此时也会做模板测试,至于z,就用来做深度测试了)

posted @ 2023-08-16 14:41  被迫吃冰淇淋的小学生  阅读(20)  评论(0)    收藏  举报