Counter-Strike 2:基于物理的环境光渲染分析

Counter-Strike 2(CS2)基于 Valve 最新的 Source 2 分支打造,起源 2 引擎为 CS2 带来了巨大的图形提升,相比于起源 1 的 CS:GO 或 CS:Source

Counter-Strike 2(CS2)基于 Valve 最新的 Source 2 分支打造,起源 2 引擎为 CS2 带来了巨大的图形提升,相比于起源 1 的 CS:GO 或 CS:Source 的 Blinn-Phong 方法,起源 2 的 CS2 是基于物理的,从材质制作 workflow 到 gameplay,CS2 始终依托于基于物理的渲染。本文讨论的是基于物理的环境光渲染。

首先,渲染方程:

%0A%20%20%20L_o%3DL_e%2B%5Cint_%7B%5COmega%7D%5E%7B%7DL_i(l)%20%20f_r%20(l%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl%7D%20)%20%5Cmathrm%7Bd%7Dl%20

抠除自发光:

%0A%20%20%20L_o%3D%5Cint_%7B%5COmega%7D%5E%7B%7DL_i(l)%20%20f_r%20(l%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl%7D%20)%20%5Cmathrm%7Bd%7Dl%20

L_i 是入射光,环境光就是一个点接受来自所有方向的入射光总和,于是我们可以使用一些方法(例如 Cubemap)来求得。起源 2 是一个普通得不能再普通的现代引擎了,做这些工作是再平常不过的了,在 Hammer 里你可以一瞥起源 2 的 IBL 系统。

刚刚说的那一大串似乎没啥卵用,因为无论怎么说渲染方程的那个积分号还在,怎么搞?不过没办法,都实时渲染了,理想是积分,现实是近似(渲染方程本身也不太可能求得解析解)。

那好吧。只能把它拆了,写成个重要性采样的格式:

%0A%20%20%20L_o%3D%5Cint_%7B%5COmega%7D%5E%7B%7DL_i(l)%20%20f_r%20(l%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl%7D%20)%20%5Cmathrm%7Bd%7Dl%20

%0A%3D%5Clim_%7BN%5Cto%20%5Cinfty%7D%20%5Cfrac%7B1%7D%7BN%7D%20%5Csum_%7Bk%3D1%7D%5EN%20%20L_i(l_k)%20%20f_r%20(l_k%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%5C

%20%5Capprox%20%20%5Cfrac%7B1%7D%7BN%7D%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7BL_i(l_k)%20%20f_r%20(l_k%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D

Epic Games 的 Brian Karis 曾在 UE4 基于物理着色的技术分享上给出过具体的数学上的分析,那就是继续展 :

%20%5Cfrac%7B1%7D%7BN%7D%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7BL_i(l_k)%20%20f_r%20(l_k%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D%20

%20%3D%5B%5Cfrac%7B1%7D%7BN%7D%5Csum_%7Bk%3D1%7D%5EN%20L_i(l_k)%5D%5B%5Cfrac%7B1%7D%7BN%7D%20%20%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7B%20f_r%20(l_k%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D%20%5D

第一个求和就是我们之前提到的求 L_i 入射光,这个很好做。

后面那一大坨有点难搞,我们知道 PBR 渲染里 Specular 的 f_r 项比起 Lambertian Diffuse 的复杂,因为存在自变量 normal, view 和 halfvector。我们使用一下 Schlick 近似的菲涅尔表达式:

brdf_%7Bint%7D%20%3D%20%5Cfrac%7B1%7D%7BN%7D%20%20%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7B%20f_r%20(l_k%2Cv)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D%20

%3D%5Cfrac%7BF_0%7D%7BN%7D%20%20%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7B%20%5Cfrac%7B%20f_r%20(l_k%2Cv)%7D%7BF(v%2Ch)%7D(1-(1-v%20%5Ccdot%20h)%5E5)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D%20%2B%20%5Cfrac%7B1%7D%7BN%7D%20%20%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7B%20%5Cfrac%7B%20f_r%20(l_k%2Cv)%7D%7BF(v%2Ch)%7D(1-v%20%5Ccdot%20h)%5E5)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D%20%0A

看起来复杂,但实际上影响到求和结果的只有 n%20%5Ccdot%20v 和粗糙度,并且没有烦人的积分,这就好办多了。

%5Cfrac%7BF_0%7D%7BN%7D%20%20%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7B%20%5Cfrac%7B%20f_r%20(l_k%2Cv)%7D%7BF(v%2Ch)%7D(1-(1-v%20%5Ccdot%20h)%5E5)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D%20%2B%20%5Cfrac%7B1%7D%7BN%7D%20%20%5Csum_%7Bk%3D1%7D%5EN%20%20%5Cfrac%7B%20%5Cfrac%7B%20f_r%20(l_k%2Cv)%7D%7BF(v%2Ch)%7D(1-v%20%5Ccdot%20h)%5E5)%20(%7Bn%7D%5Ccdot%20%7Bl_k%7D%20)%20%7D%7BPDF(l_k%2Cv)%7D%20%0A

%5CRightarrow%20color_%7Bspec%7D%20%5Ccdot%20A(roughness%2C%20n%5Ccdot%20v)%2B%20B(roughness%2C%20n%5Ccdot%20v)

我们可以直接离线计算这两个 input 所对应的 output,把它们放入一张表里,引擎里直接查表不就行了。这里使用一张 LUT 来存储积分结果,R, G 通道分别存储某一粗糙度和 n%5Ccdot%20v 下的 AB 的结果,再做一个乘加运算就能算出最终颜色了。这也是目前几乎所有现代游戏引擎实现基于物理的环境光渲染的做法。

由于 f_r 不唯一,所以 LUT 也不只是长成下图的那个样子,例如 D 项(法线分布)的不同也会产生不同的 LUT。

起源 2 的 BRDF 积分

在 CS2 的资产管理器里,你可以搜寻到这张表。你也可以通过这张 LUT 的特征辨认出起源 2 使用的是 GGX 法线分布。

实际效果就不用放图了,CS2 已经可供所有 Steam 用户免费下载游玩,进去感受下吧。