《UnityShader入门精要》读书笔记——1.渲染流水线

《UnityShader入门精要》读书笔记——1.渲染流水线

Shader更多地是面向GPU的工作方式,学习它,不光要讲语法,也要讲渲染框架。

 

1、什么是流水线?

一件事情需要N个工序完成,每个工序由专人完成,所有工序并行进行,工序1完成后移交给工序2,继续做新的工序1。

 

2、什么是渲染流水线?

由一个三维场景出发,生成(或者说渲染成)一张二维图像。

分成3个阶段:应用阶段、几何阶段、光栅化阶段。

  • 应用阶段:输出渲染图元,包括:相机位置、模型数据、渲染状态(漫反射颜色、高光颜色、纹理、shader等)、光照信息等,会进行粗粒度的可视化剔除工作
  • 几何阶段:用于处理所有和我们要绘制的几何相关的事情。这一阶段的重要任务就是把顶点坐标变换到屏幕空间中,再交由光栅化阶段处理。通过对渲染图元进行多步操作后,这一阶段会输出屏幕空间的二维顶点坐标、每个顶点对应的深度值、着色等相关信息,并交由下个阶段。
  • 光栅化阶段:这个阶段将会使用上个阶段传递的数据产生屏幕上的像素,并渲染成最终的图像。这个阶段主要任务是决定每个渲染图元中的哪些像素应该被绘制在屏幕上。它需要对上个阶段得到的逐顶点数据(纹理坐标、顶点颜色等)进行插值,然后再进行逐像素处理。

 

 3、CPU和GPU之间的通信

(1)把数据加载到显存中

从硬盘HDD(Hard Disk Drive)加载到内存RAM(Random Access Memory),再从内存加载到VRAM(Video ...)

(2)设置渲染状态

什么是渲染状态,通俗讲定义了场景中网格如何被渲染的。使用哪个着色器、光源属性、材质等。

(3)调用DrawCall

一个命令,通知GPU开始一次渲染。

 

4、GPU流水线

几何阶段和光栅化阶段都是在GPU中进行的,顶点数据->几何阶段->光栅化阶段->屏幕图像。

几何阶段:顶点着色器->曲面细分着色器->几何着色器->裁剪->屏幕映射

光栅化阶段:三角形设置->三角形遍历->片元着色器->逐片元操作

 

4.1、顶点着色器

1.完全可编程的。

2.它的输入来着CPU,它本身无法创建、删除顶点,而且无法获取顶点间的关系。

3.主要工作:坐标变换和逐顶点光照、计算颜色。

  • 坐标变换:把顶点从模型空间转换到齐次裁剪空间。代码:
// 把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(Normalized Device Coordinates,NDC)
o.pos = mul(UNITY_MVP, v.position);

 

4.2、曲面细分着色器

可选的着色器,一般不用,用于细分图元。

 

4.3、几何着色器

可选的着色器,一般不用,用于执行逐图元的着色操作,或者生产更多的图元。

 

4.4、裁剪

1.不可编程

2.主要工作:把不在摄像机视野范围内的物体裁剪掉,我们已知在NDC下的顶点位置,只需把图元裁剪到这个单位立方体内即可,对于部分在、部分不在的图元,会生成新的顶点来描述图元

 

4.5、屏幕映射

1.不可编程

2.主要工作:把图元的x,y坐标转换到屏幕坐标系(Screen Coordinates)下。屏幕坐标系和z坐标一起构成一个叫窗口坐标系(Window Coordinates),一起传递到光栅化阶段。

3.注意:OpenGL屏幕左下角为0,0位置,DirectX屏幕左上角为0,0位置

 

4.6、三角形设置

1.不可编程

2.计算光栅化一个三角网格所需的信息。

 

4.7、三角形遍历

1.不可编程

2.根据上个阶段得到的三角网格信息,对每个被覆盖的像素生成片元。片元的状态是对3个顶点的信息进行插值得到的。

3.注意:片元不是真正意义的像素,包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括:屏幕坐标,深度信息,法线、纹理坐标等。

 

4.8、片元着色器

1.重要的可编程阶段

2.输入是上个阶段对顶点信息插值得到的结果,输出是一个或多个颜色值。

3.这个阶段可以完成很多渲染技术,其中最主要的就是纹理采样。局限性在于,它仅可以影响单个片元,它不可以把自己的任何结果发送给它的邻居们。

 

4.9、逐片元操作

1.不可编程,但高度可配置

2.主要任务:

(1)决定片元的可见性。这涉及了很多测试工作,如:深度测试、模板测试等,如果没通过测试,这个片元就会被舍弃(Poor fragment)。

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

片元->模板测试->深度测试->混合->颜色缓冲区。

  • 模板测试:GPU首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和参考值进行比较,比较函数开发者指定。不管一个片元有没有通过模板测试,我们都可以根据模板测试和下面深度测试的结果来修改模板缓冲区,这个修改由开发者指定,开发者可以根据不同的结果修改操作,如失败缓冲区不变,成功缓冲区对应位置的值加1等。
  • 深度测试:通过了模板测试,就会进行深度测试,如果片元没通过测试,无法修改深度缓冲区的值。通过测试的片元,开发者通过指定开启/关闭深度写入来决定是否该片元的深度值覆盖掉缓冲区中的深度值。
  • 混合:对于不透明的物体,开发者可以关闭混合(Blend)操作,这样片元着色器计算得到的颜色值会直接覆盖颜色缓冲区中的像素值。但对于半透明物体,就需要这个混合操作。

当片元着色器阶段花了很大力气计算片元颜色后,却因为测试没通过被舍弃时,那之前花费的计算成本全都浪费了。Unity给出的渲染流水线中,深度测试在片元着色器之前,这种技术叫做 Early-Z技术。但透明度测试和Early-Z技术是冲突的,就会自动关闭Early-Z技术,从而导致更多片元需要被处理,这也是透明度测试会导致性能下降的一个原因。

 

5、一些容易困惑的地方

5.1、什么是OpenGL/DirectX?

直接访问GPU是一件非常麻烦的事,需要跟各种寄存器、显存打交道,图像编程接口在这些硬件的基础上实现的一层抽象,OpenGL/DirectX就是图像应用编程接口,它们架起了上层应用程序和底层GPU的沟通桥梁。

 

5.2、什么是HLSL、GLSL、Cg?

编写着色器的语言(Shading Language),DirectX的HLSL(High Level ...)、OpenGL的GLSL(OpenGL ...)以及NVIDIA的Cg(C for Graphic)。

GLSL的优点在于跨平台性,因硬件的不同编译实现也不尽相同。

HLSL微软控制的着色器编译,编译结果是一样的,但支持平台有限。

Cg是真正意义的跨平台,根据不同平台,编译成相应的中间语言。Cg语言的跨平台很大原因取决于跟微软的合作,所以跟HLSL语法很像,Cg可以无缝移植成HLSL,缺点可能无法完全发挥OpenGL的新特性。Unity Shader跟它们的关系后期再做介绍。

 

5.3、什么是DrawCall?

因为流水线的设计,CPU和GPU需要可以并行工作,解决办法就是使用了一个命令缓冲区(Command Buffer)。CPU通过图像编程接口添加命令,GPU读取命令,添加、读取互相独立。命令缓冲区中的命令有很多种类,而Draw Call是其中一种,其他命令还有改变渲染状态等。

Draw Call仅仅会指向一个需要被渲染的图元列表,而不会再包含任何材质信息——这是因为上个阶段中完成了【见 3、CPU与GPU之间通信】。当给定了一个DrawCall时,GPU就会根据渲染状态(例如材质、纹理、着色器等)和所有输入的顶点数据来进行计算,最终输出成屏幕上的像素点。

为什么DrawCall多了会影响帧率?

每次调用DrawCall前,CPU需要向GPU发送很多内容,包括数据、状态、命令等。在这一阶段,CPU需要完成很多工作,例如检查渲染状态等。而一旦CPU完成了这些准备工作,GPU就可以开始本次的渲染。

GPU的渲染能力很强,渲染200个还是2000个三角网格通常没有什么区别,所以渲染速度往往快于CPU提交命令的速度。如果DrawCall太多,CPU就会把大量时间花费在提交DrawCall上,造成CPU过载。

如何减少DrawCall?

批处理:合并DrawCall,批处理技术更加适合于静态的物体,例如不会移动的大地、石头等,只需要合并一次即可。当然也可以对动态物体进行批处理,但由于物体是不断移动的,每帧都需要重新合并并再发送GPU,对空间和时间都会造成一定的影响。

注意事项:尽量使用大量很小的网格,如果要用,考虑合批。避免使用过多材质,不同材质无法合批。

 

5.4、什么是固定管线渲染?

 固定函数的流水线(Fixed-Function Pipeline),也简称为固定管线,通常指较旧的GPU上实现的渲染流水线。只给开发者提供了配置操作,没有完全控制权。随着GPU朝着更高灵活性和可控性发展,渐渐被淘汰。

3D API 最后支持固定管线的版本 第一个支持可编程管线的版本
OpenGL 1.5 2.0
OpenGL ES 1.1 2.0
DirectX 7.0 8.0

 

 

 

 

 

5.5、什么是Shader?

  •  GPU流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码在GPU上运行的(对于固定管线,着色器有时等同于一些特定的渲染设置)。
  • 有一些特定类型的着色器,如顶点着色器、片元着色器等。
  • 依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递参数,用片元着色来进行逐像素的渲染。

Unity Shader:可以方便编写着色器的同时,又可以方便的设置渲染状态。

posted @ 2020-08-26 21:14  何卫  阅读(194)  评论(0编辑  收藏  举报