RenderPipline整理


梳理一下整个渲染管线

前言:网上资料太多也会产生混淆,自己整理一下,但毕竟没看过龙书虎书手写过引擎,所以感觉还是会有不少错误的,这里先挖个坑,以后有变化在改。

首先分为这四个大阶段

其次这是里面的一些细节及与硬件交互传输过程

  • 每个阶段本身也可能是一条管线,如图中的几何阶段所示。此外,还可以对有的阶段进行全部或者部分的并行化处理,如图中的光栅化阶段。应用程序阶段虽然是一个单独的过程,但是依然可以对之进行管线化或者并行化处理。
  • 最慢的管线阶段决定绘制速度,即图像的更新速度,这种速度一般用FPS来表示,也就是每秒绘制的图像数量,或者用Hz来表示。
    (引自毛星云总结,侵删)

1应用阶段(CPU主导)

  • CPU把从硬盘(HDD/SSD)中需要的数据(点线面位置ID等)拿出来放进内存(RAM)

  • 将处理方式(应当使用的着色器,合批方式,哪张RT,render path)丢给进VRAM
    (此时碰撞计算还没有算,所以还没被RAM清除)

  • 将RAM中的数据进行处理(剔除等)
    1.面的方向由什么决定?

由不同坐标系决定的,一个顺时针一个逆时针,顶点ID是没变化的(双面和背面剔除在clipping阶段)

2.Draw call 和draw pass的区别?

pass在call内,就跟shader一样,如果一个shader里有两个pass,这两个叫一个dc,一个dc类似一个rt

3.这边往VRAM里传的时候有一个粗粒度剔除(culling),与裁切(clip)的区别?

culling是通过AABB把视椎外且不与视椎相交的object剔除掉(所以其实这个时候就已经有视椎了,应该是通过Near、Far、Fov等数据设置的)

4.输出到显存中的数据?

(图为command buffer,也就是CPU向GPU传输处理方式的过程)

2几何阶段(GPU主导)

这里需要注意一下:GPU是每个顶点/片元都会算一次。所以一些诸如PI等常量计算放在CPU会省一些,因为只计算一次

2.1模型/视图变换(可以归到VertexShading里面,总之是第一步)

这里是model space – world space – view space

模型空间(model space/object space/local space)

模型变换(从模型空间到世界空间的过程)

世界空间(world space)

观察变换

观察空间(view space/camera space)

(unity的观察空间用的是右手坐标系,剩下都是左手)

2.2顶点着色器

模型转换和相机转换(单独拿到上面去了)

此外,还有坐标变换(如动画),逐顶点色彩处理(光照,纹理采样等,一般为了效果会在ps阶段处理)

也有顶点动画

2.3HS+TS+DS=曲面细分

  • 外壳着色器(Hull-Shader)
    将所需的顶点数据传递到细分阶段。需要一些输入数据,例如细分规则等

  • 镶嵌器(Tessellation Stage)不可编程
    不可编程,负责根据HS中的标记进行细分

  • 域着色器(Domain-Shader )
    决定最终位置

2.4几何着色器(Geometry Shader)可选阶段

https://zhuanlan.zhihu.com/p/76775024

几何着色器的主要亮点就是可以创建或销毁几何图元

控制GPU对顶点进行增删操作(仅能几何着色器)。和顶点着色器一样,都可以对顶i但坐标进行修改,但几何着色器并行调用硬件困难,并行程度低,和顶点着色器有很大差距

2.5投影(Projection)

这里是view space – clip space

观察空间(view space/camera space)

投影变换:透视/正交投影矩阵。运算后可以通过+-W来与xyz比较来判断变换后的点是否在视椎体内(透视投影是非线性变换)

裁剪空间(clip space/齐次裁剪空间/齐次空间)


正交投影矩阵
透视投影矩阵

透视投影最后一行的1用来区分左右手坐标系

是这样的:上面左侧的矩阵是直接将物体映射到NDC之后的矩阵,r和l代表NDC的最小最大值。而闫佬提到的z值变化,是viewSpace.Z和NDZ.Z的对比。该结果由far和near影响

GPU将顶点从摄像机观察空间转换到裁剪空间(也叫其次裁剪空间)为之后剔除过程以及投射到二维平面做准备

其次坐标的扩充的方法很简单,对于一个顶点(x,y,z),我们只需要给它增加一个w=1的分量即可,即(x,y,z,1)。

2.6裁剪(clipping)/ 齐次空间(Homogeneous Space)

这里是clip space – screen space

裁剪空间(clip space/齐次裁剪空间/齐次空间)

屏幕映射:透视除法/齐次除法,实际上就是x/w,y/w,z/w(变换到三维空间)OpenGL管这一步得到的坐标叫NDC(归一化设备坐标),在xyz属于[-1,1]的cube内,DX的z范围是[0,1]

屏幕空间(screen space)


当前物体在裁剪空间(clip space/齐次裁剪空间)齐次空间裁剪指的是在齐次除法前进行剔除

GPU把看不见的顶点剔除掉,使他们不被渲染。判断只需要满足

GPU对裁剪空间中的顶点执行齐次出发(xyz分别÷w)得到NDC,经过齐次除法周,透视裁剪空间会变成一个xyz都在[-1,1]区间的立方体(CVA)内。对于正交裁剪空间,把w去掉就行(unity里w分量是透视矫正插值)此时坐标还是三维的,z分量是深度缓冲(z-buffer),可以做一些有关于顶点到摄像机距离的计算。同时背面剔除也是这里剔除的

Tips:

  • NDC(标准化设备坐标)?在经过透视投影矩阵变化后,顶点中的w分量变成了一个衡量顶点到摄像机之间距离的参数。而正交投影矩阵的变化应该是最让人舒服的,它直接把空间变化为了一个x、y、z三个坐标都在[-1,1]区间内,w=1的立方体。
  • NDC&CVV?
    CVV全称:Canonical View Volume。(空间)

NDC全称:Normalized Device Coordinates。(设备坐标)

经过透视投影和透视除法之后,视锥体变为CVV,此时采用的坐标系叫做NDC。

2.7屏幕映射(Screen Mapping)

当前物体在屏幕空间(screen space)这里还有z,z是深度(后面要用到例如光栅化时候),在unity中w用于透视矫正差值


裁剪后,GPU需要把顶点映射到屏幕空间,这是一个从三维映射到二维的过程。

正常来说,对于任意一个点 p_local,转换到屏幕空间需要

p_proj = p_local * WorldMatrix * ViewMatrix * ProjectionMatrix

所以HPosition也就是屏幕空间坐标

3光栅化阶段(Rasterization Stage)

至此GPU完成了渲染的一半工作,我们只得到了顶点,但还不能成为像素显示再屏幕上

3.1图元组装(Primitive Assembly)

也叫三角形设置(Triangle Setup),根据每个顶点的ID将每三个点组成一个面

3.2三角形遍历(Triangle Traversal)

检验屏幕像素是否被三角形网络所覆盖,被覆盖区域将生成一个片元。具体覆盖生成逻辑有很多种。

tips:片元不是真正意义上的像素,而是包含了很多种状态的集合(如屏幕坐标、深度、法线、纹理等)这些状态用于最终计算出每个像素的颜色

这个阶段涉及到了抗锯齿。

除此以外,GPU还将对覆盖区域的每个像素的深度进行插值计算(透明度混合)。因为对于屏幕上的一个像素来说,它可能有着多个三角形的重叠,所以这一步对于后面计算遮挡、半透明等效果有着重要的作用。

3.3片元着色器(Fragment shader/Pixel Shader)

Fragment 这个词,这里翻译成图元,另外的文章可能翻译成面片,或者是片段。3D模型(2D可以看成是3D的特列),不管怎么复杂,也可以一直分解,最终分解到一个个三角形,或者是一个个四边形。这种最终分解出来,不可以再拆分的最基础的部分,就叫图元。OpenGL ES 去掉了四边行,只留下三角形作为最基础的图元。三角形有个优点,是三点永远在同一平面上,可以避免很多特殊情况。

三角形由一个个顶点组成,不同的三角形可能共用相同的顶点。先处理顶点,再将顶点组装起来,变成一个个图元,再处理图元。组装之后图元中每个顶点的信息都有了,之后再通过插值的方式,来处理每个图元中内部的像素点。

Vertex Shader,用来处理顶点,每个顶点执行一次。 Fragment Shader,用来处理图元,每个图元中的像素执行一次。(这里先忽略像素裁剪)

同一个渲染过程中,Vertex Shader 和 Fragment Shader 执行的次数并不是一样的。Fragment Shader会执行更多次。(所以FS会更精细一些)

3.4逐片元操作(Per-Fragment Operations)/合并阶段(Output-Merger)

从两个名字中我们大致可以推测出GPU在这个阶段要做的事情:对每个片元进行操作,将它们的颜色以某种形式合并,得到最终在屏幕上像素显示的颜色。主要的工作有两个:对片元进行测试(Test)并进行合并(Merge)。

测试步骤决定了片元最终会不会被显示出来。在OpenGL中,主要的测试有:裁剪测试(Scissor Test)、透明度测试(Alpha Test)、模板测试(Stencil Test)以及深度测试(Depth Test)。这个阶段是高度可配置的,所以渲染顺序不唯一,例如earlyZ

* **裁剪测试(Scissor Test)**

在裁剪测试中,允许程序员开设一个裁剪框,只有在裁剪框内的片元才会被显示出来,在裁剪框外的片元皆被剔除。

* **透明度测试(Alpha Test)**

在透明度测试中,允许程序员对片元的透明度值进行检测,仅仅允许透明度值达到设置的阈值后才可以会绘制。在OpenGL3.1后这个API被删除了,但你可以在片元着色器中实现类似的效果。

* **模板测试(Stencil Test)**

模板测试是一个相对复杂的测试。在模板测试中,GPU将读取片元的模板值与模板缓冲区的模板值进行比较,如何比较可以由程序员决定,如果比较不通过,这个片元将被舍弃。

譬如图中两个有重叠区域的小球;令左侧小球模板值为3,右侧小球在非重合处模板值为2,重合处模板值为3。现进行模板测试,令左侧小球始终被绘制为红色,令右侧小球满足:模板值与缓冲区相等的绘制为绿色,否则绘制为蓝色。于是我们发现,右侧小球重合处的片元通过了模板测试,被成功绘制为绿色。

* **深度测试(Depth Test)**

深度测试是一个十分重要的测试。在深度测试中,GPU将读取片元的深度值(就是我们前面留下来的坐标z分量)与缓冲区的深度值进行比较,比较方式同样是可以配置的。用通俗的说法解释,深度测试允许程序员设置如何渲染物体之间的遮挡关系。

  • 颜色混合(Color Blending)
    Alpha Test

Depth Buffer Test

Stencil Test

Blending

  • 目标缓冲区
    FrameBuffer

RenderTexture

Tip:framebuffer是位于memory上,带宽压力和功耗自然高

补充

  • 对于vs和ps的修饰符

    • attribute,为每个顶点的属性数据,每个顶点都不一样。顶点属性也只能在Vertex Shader 中定义,为用户向顶点处理模块传输的数据。
    • uniform,为状态数据,整个渲染过程都是不变的。uniform 这个单词意思就为始终如一。状态数据,两种Shader 中都可以使用,但需要声明一致,不然会链接错误。
    • varying,是 Vertex Shader 向 Fragment Shader 传输的数据,相当于两个shader 之间的数据通道。要通信,两个 Shader 就需要有一致的约定,表现成语言,就变成一致的数据声明。Vertex Shader 只管向 varying 写入顶点处理后的结果。Fragment shader 只管从 varying 中读取已经是经过插值后的结果。
      从语法的设计角度来,可能将 attribute 和 varying,换成关键字 in 和 out 会更好些。
  • unity的渲染逻辑

  • 数据传递
    硬盘作为存储设备存储数据,然后被传到内存中,内存负责存储使用的数据,然后数据会在CPU和GPU之间来回传输。

一部分在CPU一部分在GPU,大部分工作是保持同步进行的。

  • EarlyZ

  • 不同GPU架构对于pipline的处理

文章作者: Neilyodog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Neilyodog !
评论
评论