帧缓冲区

简介

此处,我们介绍分层着色器写入用户帧缓冲区过程的方式,提供与过去使用 mia_material_x 着色器不同的机制。在某种程度上,这可能会告知给定集成如何采用更简单的流水线方法。因此,在此页面中,我们解释这个新方法为什么更适合产品级使用,同时显示旧方法的缺陷。

使用具有帧缓冲区的分层着色器

对于最小设置,为摄影机指定特别命名的帧缓冲区。只需使用标准名称创建帧缓冲区。以下示例用场景描述语言表示如何指定帧缓冲区:

framebuffer "indirect_diffuse"     
    datatype "rgba_16" 
    filtering true 
    filename "passes.exr"
    compression "rle" 
    premultiplied true 
    user true 
    useopacity true
    attribute string "LPE" "L.+<RD>E"
framebuffer "direct_diffuse"      
    datatype "rgba_16" 
    filtering true 
    filename "passes.exr"
    compression "rle" 
    premultiplied true 
    user true 
    useopacity true
    attribute string "LPE" "L<RD>E"
    ... 
    ... etc repeated for all the buffers
    ...s

以下是帧缓冲区名称及其含义:

direct_diffuse
漫反射直接照明。灯光沿从灯光 (L) 到漫反射 (<RD>) 表面再到眼睛 (E) 的路径前进。
indirect_diffuse
场景的(漫反射)间接照明。灯光可能会照射到任意数目的表面。反射或透射,最后通过漫反射表面到达眼睛。
direct_glossy
光泽直接照明。灯光沿从灯光到光泽反射表面再到眼睛的路径前进。
indirect_glossy
光泽反射不直接来源于灯光。通常,传统的高光和反射过程不完全是直接和间接反射,而是直接灯光循环和光线跟踪反射光线。这意味着可见区域光可以在传统的反射过程(而不是高光)中结束。使用灯光路径表达式模型,我们更加严格地遵循直接和间接灯光路径规范。
direct_specular
镜面反射直接照明(从灯光到镜面反射曲面到眼睛)。以下是来自像镜子那样的表面的直接灯光反射
indirect_specular
镜面反射不会产生直接来自光源,而是来自对象。
diffuse_transmission
半透明效果。直接和间接。
glossy_transmission
折射模糊透射。直接和间接。
specular_transmission
折射不模糊(完美镜像)透射。直接和间接。
front_scatter
“前向”次表面效果。
back_scatter
“后向”次表面效果。
发射
任何“附加光”/自发光/白炽度效果。
mila_adapter 着色器。

示例

在示例场景中使用几个图层,我们会在不透明的机器人前放置一个简单透明球体。在此情况下,透明度在概念上表示薄玻璃球体,而不是实心玻璃球体,从而能够通过一个镜面反射透射组件更好地实现这种透明度。

美景渲染

Image:framebuffer-final-render.jpg
这是一个包含多种材质的示例场景。靠近前方的蓝色球体
使用“透明度”(与镜面反射透射相对)显示其后面的部分场景。

帧缓冲区过程使用简单的传统机制

通常,顶端的对象着色器将值输出到帧缓冲区。由于不透明对象的存在,在整个漫反射和反射过程中,我们可以清晰地看到这一切。但是,若透明对象存在时,渲染将在背景对象上执行着色器。因此,关键的生产问题是背景对象将影响哪个/哪些帧缓冲区过程

在简单的实施中,顶端的着色器将值输出到单独的“透明度”过程,而且值中包括完整的结果(即,顶端对象后面内容的美景过程)。它看起来如下所示:

Image:framebuffer-current-behavior.jpg

此处我们看到了 mia_material_x 的各种输出。我们将表面拆分为间接和直接漫反射、高光反射(直接高光反射或高光)和反射(间接高光),以便这些曲面的外观可以在后期制作中进行调整。但蓝色球体在每个表面中都显示为完全不透明。

具有透明度的对象(如这个蓝色球体)在着色器的透明度输出中只是包含它们后面的对象。此透明度输出不再分割为漫反射、高光等各个部分,而是以整体形式作为一个最终合成项(如美景过程)呈现背景对象。

优点

缺点

总的来说,我们缺少对整个对象的控制;不会“跟随”在透明度中看见的东西,因为可能会对合成阶段中的其他相加过程应用重置权重。透明度是完全独立的,不控制单独的灯光过程。

过去开发的帧缓冲区着色器可以为那些愿意构建更复杂的着色网络的人员解决此问题。事实上,我们已开发和测试了多个版本,但在实际使用中,可能并不支持这些版本的使用。但是,我们相信我们可以通过将帧缓冲区支持合并到该库的分层功能中,简化易用性和性能上的一些缺陷。

使用分层着色器帧缓冲区过程

在分层库中,我们在了解了正在执行哪些基本组件后,提供了输出写入功能。从灯光路径表达式的角度看,基本着色器能够识别眼前的组件交互阶段,通常能够识别该交互的入射光是直接还是间接的。例如,“L<RD>E”可以由 mila_diffuse_reflection 内部的直接灯光识别。间接灯光由“L.+<RD>E”表示。

我们的“透明度”输出为“L *<TS> E”,也可以来自镜面反射透射。也可以单独输出通过透明度从眼睛得到的漫反射、光泽和镜面反射交互。合成器可能会将各种子输出视为准备添加到眼睛直接看到(不是通过透明对象)的各个输出中。

优点

缺点

运动模糊处理帧缓冲区的方式

如果仍对这种样式的透明度行为有疑问,请考虑运动模糊的行为方式:

在以下示例中,不再使蓝色球体稍微透明,而是将其快速移动,以便通过运动模糊的方式使其变得半透明。即使使用较旧的着色器行为也会呈现该结果:

Image:framebuffer-motionblur-rendering.jpg

然后,各个帧缓冲区将具有如下外观,甚至不必使用分层着色器对它们进行渲染

Image:framebuffer-motionblur-behavior.jpg

正如我们所见,现在渲染器已自动得出精确的结果!因为当它将采样收拢到像素中时,结果完全符合预期要求。

在此情况下,“透明度”输出甚至已没有意义 - 对象甚至不具有透明度。它只会呈现由于速度所表现的形式。

因此,使用分层着色器透明度功能,可以达到同一效果。合成更加简单,控制力度更大,最终结果更不易合成瑕疵。

务必要注意,如果使用了光栅化器,则 usopacity 标志必须为 true,因为光栅化器负责将最终采样合成到帧缓冲区像素中。

可能存在的数学限制

有一点需要注意,即这种行为对于灯光子组件数学相加得出的所有输出具有完整的意义。它对于其他许多不同类型的输出没有意义,例如:

分层着色器可以通过在材质根节点 mila_material 上进行特殊的“附加输出”为前者提供支持。

但是,第二个问题在数学上无法求解;这一点将在下一节中进行说明:

增加过程

为何我们只能混合“结果”输出?

mia_material 着色器具有所有类型的输出,通常包括“raw”、“level”和“result”等类型

现在,为什么这些着色器不具有所有这些输出,并将它们很好地混合在一起?

一旦 oass 转换为像素,从数学角度看就无法重新创建混合

假如我们有两种漫反射材质:A 和 B,要混合 80% 的 A 和 20% 的 B。进一步假设这些着色器具有“result”、“raw”和“level”输出,其中

  A.result = A.raw * A.level
  B.result = B.raw * B.level

最终结果的混合:

  final.result = A.result * 80% + B.result * 20%

……但我们不能混合其他输出。例如:

  final.raw    = A.raw   * 80% + B.raw   * 20%
  final.level = A.level * 80% + B.level * 20%

...然后:

  final.raw * final.level = ((A.raw * 80%) + (B.raw * 20%)) *((A.level * 80%) + (B.level * 20%))
  final.raw * final.level = (A.result * 80%) * 80% + (B.result * 20%) * 20% + more multiplying terms ...

  final.result != final.raw * final.level

……因为现在权重应用到“raw”和“level”,然后将它们相乘后将会有效地应用权重的平方……不再匹配各个“result”输出的乘积!

最后,请注意在对像素进行过滤时,会对像素中不同采样的许多最终结果进行权重和混合。这更加重了数学的复杂性。甚至会影响位于对象边缘上的简单过程。通常,使用这样的过程可能会看到对象上变暗的边或其他缺陷。如果我们使用仅添加各种基本数量灯光的过程以创建最终的图片,我们会完全避免此问题。使用“仅相加”过程可避免在合成时发生元素重新合成问题。