[Translate]Reverse Engineering the rendering of The Witcher 3 II | [译]巫师3渲染逆向工程 2

原文参见 Reverse engineering the rendering of The Witcher 3: Index

这是第二篇,翻译原文9-12节。由于文章太长,而且废话较多,这里先做个简单的摘要吧

摘要

9-中介绍GBuffer的组成,讲了一些特殊的操作,比如Best Fit Normal,比如降饱和度。另外神奇的是巫师3并没有使用PBR,毕竟是2015年的游戏。

10-中介绍了远景雨幕,其实是一个圆柱上通过噪声滚动做出的

11-介绍了闪电的做法,是一个树状的mesh渲染出的,可以做粗细远近变化,并且加了一些随机效果

12-介绍了天空的渲染,包括大气散射,太阳和星空的渲染,星空是旋转的天空盒,并做了随机闪烁

9 GBuffer

这是我系列的第9部分
这部分我会揭示巫师3的gbuffer的一些细节
假定你了解延迟渲染的基本知识。简单回顾:不立即计算最后光照和着色,而是分成两个阶段:第一阶段(几何pass)填充gbuffer表面数据(颜色,法线,高光颜色等等),第二阶段(光照pass)混合所有并计算光照

延迟渲染很流行,因为它允许在全屏幕空间计算光照,结合tile-based等技术极大地提高了性能

简单地说,gbuffer是包含一系列几何属性贴图的集合,很重要的一点是设计它的组成。比如这个:Crysis3的渲染技术.

之后我们来看看巫师的一帧

gbuffer有三张R8G8B8A8_UNORM格式的rendertarget和一张D24_UNORM_S8_UNIT格式的深度+stentil缓冲

RT0-Albedo
RT0-A 不知道是啥
RT1-法线

RT1-Alpha 像反射度
RT2 像高光颜色
深度缓冲
Stencil

当然这不是gbuffer的全部,光照pass使用反射探针和其它buffer,但不是这篇的主题。在开始之前,我们先讨论一些泛泛的观察

整体观察

  1. 唯一清理的缓冲是深度/蒙版

如果你用帧分析器分析会有点惊讶,除了深度缓冲,其它没有调用clear指令。所以实际上RenderTarget1看上去这样,注意远处模糊的像素。

这是简单实用的优化,因为ClearRenderTargetView调用是有开销的,只有需要时才用

2. 反向Z

很多文章讨论过浮点深度缓冲的精确度,巫师3用了反向z,这是开放世界和较大渲染距离的自然选项。

在DX中这不复杂
a) 用0清理深度缓冲,而不是1远处是0而不是1
b) 计算投影矩阵时,将远近裁切值交换
c) 将深度测试从Less改为Greater
OpenGL中有些复杂,不过是值得的

Pixel Shader

我想展示pixel shader如何给gbuffer传递数据,我们至少存了颜色,法线,高光。但可能不像你想得容易
问题是pixel shader有很多变种,使用的贴图和参数不一样。比如这个桶。

看看他们的贴图:

我们有颜色,法线和高光颜色,很常见。但开始之前,一些碎语:几何体带有位置,UV,法线和tangent属性vertex shader输出uv,归一化的TBN。对于复杂的材质,比如有两张颜色贴图的。vertex shader会输出其他的,这里一个简单的例子:

这个shader有很多步,我分别描述一下。首先是cbuffer的数值

颜色

我们先从难的考试,它不简单是采样贴图颜色,采样之后做了一步降饱和

对于大多数的物体,它就是返回的贴图本色,但适当的材质cbuffer数值,cb4_v1.x如果是1,会导致蒙版为0,会使用lerp混合颜色。

但有一些反例
我发现最高的降饱和系数时4,降饱和颜色取决于材质,可以是(0.2,0.3,0.4),但没有严格规范。我迫不及待重现了,下面是结果,当降饱和颜色为(0.25,0.3,0.45)时

desaturationFactor = 1
desaturationFactor = 2
desaturationFactor = 3
desaturationFactor = 4

我很确定这仅仅应用了材质属性,不是颜色的最终部分。15-20行是最后的几步

v0.z是vertex shader出来的,结果是0.记住它,因为vo.z后面会多次用到。
看上去一些系数和代码会让颜色变暗,但如果v0.z是0的话,颜色就不会变,

关于RT0.a,如你所见,材质材质cbuffer,但是因为没有debug信息,我们也不知道是什么,或许是透明度?

法线

首先解压法线,然后正常做,没什么特别的

看看28-33行,

大概可以写成

我不确定这是合适的写法,如果你知道这是什么数学计算,请告诉我。(译者注:像是一个reflect操作。。。但是为啥对模型反面这么干我是头一回见)
我们看到pixel shader使用了SV_IsFrontFace

对于线和点,始终为True。特殊情况是三角面的线(线框模式),和实体模式一致。可以用geometry shader设置并被pixel shader读取

我想自己检查下,的确他只在线框模式可见。我相信目的是线框模式下正确计算法线。下面是比较(译者注:也有可能是开启双面渲染的材质,背面有特殊的计算法线方式,trick地修正光照)

没用trick的场景颜色
用了这个trick的场景颜色
法线0-1,没使用上述trick
法线0-1,使用了trick

你注意到rendertarget地格式是R8G8B8A8_UNORM了嘛?它意味着每个组件有256种可能,但它够用吗?

用有限地资源储存高质量地法线是一个已知问题,但是幸运的是我们有很多材料可以学习

也许你注意到这个技术在这里使用了,我要说整个几何pass种确实有一个附加贴图:

巫师3使用了Best Fit Normal的技术,我不会详细解释,它是2009-2010时Crytek开发的,随着CryEngine开源

BFN造成了法线贴图地颗粒感在缩放法线获得最佳后,我们把它从[-1,1]区间映射到[0,1]区间

高光

我们从34行开始,采样高光贴图

如你所见他也有类似的变暗过滤,计算最大值,然后计算变暗值,然后和本色lerp。

Reflectivity ?

我也不知道合适的名字是什么,我不知道他如何影响光照。它仅仅是法线图的alpha通道

汇编:

和我们的老朋友v0.z打招呼,类似颜色和高光

好了这是第一个pixel shader的变种

pixel shader – 颜色+法线变种

我给你展示另一个变种,这次是颜色和法线。没有高光贴图

和前面的区别:

a) 1,19行,插值参数v0.z乘以了cbuffer系数cb4[0].x,被用于19行插值颜色,其他地方还是用的v0.z
b) 54-55行,o2.w设定条件是cb4[7].x > 0.0我们已经知道这是计算明度的地方

c) 34-42行,完全不同的计算高光的方法
没有高光贴图