LOADING

加载过慢请开启缓存 浏览器默认开启

Gbuffer传递更多种类数据的小花招

为什么二维的八面体法线仍占据 RGB 三个通道?从位打包与 materialFlags 出发,看看 URP 如何利用有限的 GBuffer 存储空间。

一、引言

最近在看关于Gbuffer阶段的法线八面体映射编码,它是一种将三维向量编码为二维数据的技巧,正常来说应该只需要两个通道就可以,可实际你去看Gbuffer2的颜色会发现:

Gbuffer传递更多种类数据的小花招示意图 1

这不是红绿蓝全占了嘛!!说好的两个通道呢?于是就有了现在的文章。

二、用多通道承载高精度的单通道信息

嗯、答案就是用多通道承载高精度的单通道信息,它用了更底层的方法,巧妙的让原本Gbuffer传递的信息精度更上一层楼。我们常说的某某bit的数据指的其实是二进制的位数,一个二进制只有0和1两种状态,我们常用的RT一般是每通道8Bit的,也就是八位数的二进制,可以存 2^8也就是256个数字,unity给法线的Gbuffer也是这样的RT:R8G8B8A8_SRGB,这对法线来说还是精度太低了,既然数字本质是二进制,我们存的也是二进制,那么我们为什么为得拘泥于一个颜色通道存一个信息呢,URP的解决方式是这样的:假设我们有一个编码后的法线float2(X,Y),那么我们可以这样:X写成二进制是X:XXX(为了方便举例,这里用超低的位数),Y的二进制是Y:YYY,这样XY分别是3bit的法线,在写入RT的时候我们可以这样:R:XX G : XY B : YY这样虽然每个通道的信息精度都不足以承载原先的法线信息(2bit),但是我们用三个通道将两个法线信息都完整的承载下来了。

Gbuffer传递更多种类数据的小花招示意图 2

实际情况是:我们将存储的法线两个通道分别是12bit的信息,一共24bit,法线用的Gbuffer是RGB888,一共可以储存24bit的信息,这样即使但通道的信息精度不到12bit,我们也可以直接储存12bit的法线了。 负责编码和解码的是unity这两个函数:

half3(PackFloat2To888(remappedOctNormalWS));
half2(Unpack888ToFloat2(pn))

三、高精度单通道传递多个种类的信息

众所周知,延迟渲染一共就4个Gbuffer,通道不多,光是颜色或者向量信息就得占23个通道,如果想要传递各种各样的开关怎么办呢?嗯就是高精度单通道传递多个种类的信息,这种方式的典型就是Gbuffer0的materialFlags。 开关嘛,充其量就是个0或1,占一个bit就够了,而一个贴图的A通道可是有足足8bit的位置,够存8个开关了。 我们具体看看materialFlags存了什么:

Gbuffer传递更多种类数据的小花招示意图 3

分别对应: - ReceiveShadowsOff:关闭接收阴影 - SpecularHighlightsOff关闭高光反射 - SubtractiveMixedLighting减法混合光照模式 - SpecularSetup使用 Specular 工作流 对其的定义在这里:

Gbuffer传递更多种类数据的小花招示意图 4

这里每 * 2代表向前挪了一位,一共使用了四位。Unity 文档将剩下的第4至7位保留给未来用途;如果修改管线来存放自定义开关,需要同时检查自己的 URP 版本、编码和解码逻辑,不能直接当作始终可用的空位。

四、碎碎念

真行啊,每次去翻URP的延迟渲染都能学会点新东西,之前一直不理解materialFlags干嘛的,现在发现是真的重要,例如之后想要把自己的ToonShader的角色和场景分开渲染的话就可以用这种方式了。希望能帮到一些人吧。

参考:Unity:URP 中延迟渲染路径中的 G 缓冲区布局。