渲染原理

渲染是什么?

渲染是CPU将数据交给GPU计算的过程。

数据有哪些?

数据类型:

  • 固定类型数据。顶点float3,uv float2(4),等。
  • 固定用途数据。PBR Albedo(固有色)等。
  • 自定义数据。

  • 模型参数:顶点,三角面,法线,切线,变换矩阵,UV,顶点色
  • 材质参数:(颜色,纹理,金属的,粗糙度等)参数是动态的,可以是无限的。
  • 引擎内置参数:框架底层为每个shader声明的参数,框架底层为参数赋值(例如,深度缓冲,阴影缓冲)[通过变体等多种方式,可以理解为固定参数]
  • 全局参数:[固定参数]
  • shader: 计算公式。

我们可以这样理解: GPU 通过 shader 计算公式,带入参数(模型参数 => 固定参数,上文列出,无论shader是否使用,都会传送到GPU材质参数 => 动态参数,任意个数和类型),计算得出一张图片(RenderTarget)。

所以,只要shader和参数匹配,理论上我们可以通过添加无限的材质参数,来实现任意效果。

我们所担忧的地方?

因为性能关系,我们希望shader尽可能复用:
只要材质参数相同,即参数个数相同,类型相同(因为材质参数和shader必须匹配,所以shader相同,个数和类型必定相同),并且参数的值相同,这样我们就能进行批处理,与模型参数无关(这是由GPU流水线,DX OPENGL,或者硬件架构决定的,具体由什么决定我不清楚),大大提升性能。

一个比喻性理解,可能不正确:一次渲染batch,就是用shader和材质参数一起搭建起一个渲染管道,shader是水管,材质参数就是连接头,搭建好以后,调用drawcall,模型参数顶点面片就是像水流流进管道,输出结果。如果多个模型shader和材质参数相同,那么不用重新搭建管道,可以一起输入多个模型,这就是批处理。

但这导致一个问题,多个模型使用相同的参数,那么看起来都一样(一样的金属光泽,一样的粗造度),太过单一。

那能不能即保留多样性,同时又进行批处理呢?

  • 如果想进行批处理:材质参数必须相同。
  • 如果想每个模型表现不同:shader 必须获得不同参数值。

我们由多种方法来达到这种效果(^_^):

  1. 将每个实例的参数封装到模型参数:模型参数支持8个UV,每个都是float4类型(uv012通常已经被使用),顶点色float4,通过这个方法将参数传递到shader。例如:使用顶点色R通道表示顶点受风力影响的强度。
  2. 使用额外的缓冲区,可以是一张全局Texture,也可以是已经渲染完成Pass的结果,这样后续Pass就可以直接使用。
    • 例如:
    • 深度贴图 实现阴影
    • 通过定义一个全局贴图来实现战争迷雾
    • 一个额外的深度贴图,实现半透明彩色阴影
  3. 全局参数:可以实多维数组使用模型坐标做Key设置好参数,shader中通过模型坐标取即可。

任何不通过材质参数将参数传递到shader的方法都可以。

从这个角度理解PBR?

纹理贴图是动态参数,材质上还有各种各样的参数和贴图计算,得到最终结果。
简单理解为rt = tx * p1 + tx * p2 + tx * pn
贴图和参数都是任意的,所以完全无非控制最终结果,只能靠美术慢慢调。
所以PBR就是将问题 由动态参数,转变 为半固定参数。
贴图不能想顶点,法线一样固定数据大小和格式,因为贴图可以用分辨率控制效果精度。
所以从贴图的数据属性来定义了一组标准贴图,就是PBR。
属性:

  • Albedo(固有色):物体在无光照情况下材质原本具有的颜色
  • Metalness(金属性/金属度):使用黑白灰颜色来定义材质类似金属的程度(白色为完全金属,黑色为非金属)
  • Roughness(粗糙度):使用黑白颜色来定义材质的粗糙度(白色为完全粗糙,黑色为光滑)

其实这些属性也可以向顶点色一样设计为每顶点数据。变成固定参数。“mesh.Albedo,mesh.Metalness”,将这些都写进mesh里去。但是后来随着性能提高和对效果的要求,顶点色进化成了纹理采样。技术没有倒着走的道理。 这里只是想表明。将数据传递到渲染流水线的方式多种多样。

通过定义标准,我们就有了有规则的参数。艺术家产出素材时,不必为每个特殊效果额外制作贴图。通过增加shader参数,通过不同算法,就可以得到不同效果。

有了固定属性的贴图,所以产生了符合现实世界的通用型standard材质shader。
有了通用性的ToonShader。
相同的模型和贴图。通过更换shader就可以转换风格。
当然,如果要求的效果越特殊,最终还是会回到手绘贴图的老路上。