转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3167

面光源一直是实时渲染的一大难点。原先所有实时渲染中都只敢用点光源(spot光源也是从一个点发射出来的,在这里也算做点光源)。直到这两年,软硬件水平逐步提高了之后,面光源的实时近似才慢慢变得实用起来。

面光源的难处

从ray tracing的角度来说,在计算一个点的光照时,需要根据BRDF沿着反射方向发出多根光线,每一根都需要与光源求交。如果是点光源,那么只要计算一个点和射线求交。如果是个有体积的物体,求交就比较麻烦了,并且需要发射出更多的光线才能逼近真实效果。

在实时渲染中,光照的计算可以分为两部分。第一是来自光源的贡献。点光源可以用常见的光照模型进行计算,最近流行的基于物理的渲染,前提之一就是光源为点光源。对于面光源,这个计算会和光源形状相关,不是一个公式就能解决的。第二是阴影的产生。实时渲染一般用shadow map来产生阴影,这同样也需要假设光源从一个点发出。

常见做法

早期对面光源的尝试中,经常使用的是把一个面光源近似成多个点光源的组合。每个点光源都分别产生shadow map和光照,以此来逼近整个面光源。甚至在一些离线渲染中,都用这个方法来加速面光源的计算。这么做效果尚可,但速度就会随着近似点光源的个数增加而下降。

Cone tracing也可以解决面光源问题。但因此就需要引入全套的svo和cone tracing,无法兼容原有的渲染系统。

UE4的做法

KlayGE最近新增了对面光源的支持,用的是UE4的做法(Real Shading in Unreal Engine 4)。其优点在于完全兼容原有的渲染方法,而且速度很快,几乎和点光源的光照计算量一样,效果也很接近ray tracing的结果。缺点是不是基于物理的,而且只能支持形状有解析表达的面光源。同时shadow map和点光源的一样,都只是从一个点出发,没法自主产生正确的软阴影。

球形光源的近似

当处于被照点的地平线之上的时候,球形光源的irradiance和点光源一样,所以这部分不用做特殊处理。但有两个地方需要修正。第一是点光源的位置。对于球形光源来说,UE4的做法是根据视点V和场景中需要计算光照的点P计算一个反射射线R。从R,球心坐标C和球的半径radius可以计算出射线与球的最近位置。那个点就是经过修正的点光源位置。用伪代码表达,就是:

[latex]\mathbf{CenterToRay} = (\mathbf{L} \cdot \mathbf{R}) \mathbf{R} – \mathbf{L}[/latex]

[latex]\mathbf{ClosestPoint} = \mathbf{L} + \mathbf{CenterToRay} * saturate \left( \frac{radius} {|\mathbf{CenterToRay}|} \right)[/latex]

其中L是P到C的向量。

第二个需要修正的地方是specular系数。球形光源的半径越大,对应的specular高光效果就越低。所以需要做这样的近似:

[latex]\alpha ‘ = saturate \left( \alpha + \frac {radius} {2 * distance} \right)[/latex]

其中alpha是原specular系数,distance是P到C的距离。这个修正过的specular系数可以直接替换掉原有系数,带入specular的计算中。

同时,球形光源的shadow用从球心发出的全方向shadow map来近似。

这里借用几张UE4的图来演示一下实时面光源的效果。

Sphere light source, ray tracing上图为球形光源的ray tracing效果

Sphere light source, new specular上图为球形光源只应用了specular修正的效果,可以看到与ray tracing的差别较大

Specular light source, ours上图为球形光源应用了位置修正和specular修正的效果,与ray tracing的差别小了很多

线光源的近似

线光源在UE4里也被称为tube light。它的假设是光源只有长度,没有半径。在这种情况下,光源的irradiance是:

[latex]\int_{L_0}^{L_1} \frac {\mathbf{n} \cdot \mathbf{L}} {|\mathbf{L}|^3}\, d\mathbf{L}=\frac {\frac {\mathbf{n} \cdot \mathbf{L_0}} {|\mathbf{L_0}|} + \frac {\mathbf{n} \cdot \mathbf{L_1}} {|\mathbf{L_1}|}} {|\mathbf{L_0}||\mathbf{L_1}| + (\mathbf{L_0} \cdot \mathbf{L_1})}[/latex]

把这个值乘到颜色上,同时去掉距离衰减,就能实现线光源的光照。同时,线光源的specular也需要修正,方法同球形光源的。

shadow map在线光源的情况下无法正确表达。要么当作从中心发出的全方向shadow map,要么不支持阴影。

其他做法简介

最近看到的另一个面光源的做法是GPU Pro 5的Physically Based Area Lights。这个方法通过面积积分处理非解析面光源,能做到基于物理的结果,但计算开销较大。对于一些高度不对称的光源形状,这个方法也可能会失败。另外,它在GDC上的ppt(Lighting of Killzone: Shadow Fall)里号称通过IES light profiles的方法来增加光源的真实感,并方便美术调整效果。其实IES light profiles和面光源无关,只是个经过精确校准的projective texture。