物理光源:Linearly Transformed Cosines

物理光源:Linearly Transformed Cosines_第1张图片

1. 介绍

1️⃣首先,确认LTC技术的目标:解决多边形面光源的光照积分问题,特别是高光部分的求解多边形面光源的积分和我们之前考虑的问题都不一样:

  • 常见光源(点光源、SpotLight等):简单,积分域就是一个点。
  • 环境贴图:复杂一点,积分域是半球域——一般来说,我们都是使用IBL技术类,进行简化。

我们考虑更加物理的光源:多边形光源。这个时候,积分域 S S S 就很复杂了,求解更加复杂:
I = ∫ S ( L ( w l ) ⋅ f ( p , w i , w o ) ⋅ cos ⁡ ( θ p ) ) d w i I=\int_S(L(w_l)\cdot f(p,w_i,w_o)\cdot \cos(\theta_p))dw_i I=S(L(wl)f(p,wi,wo)cos(θp))dwi

注意:我们考虑的是多边形光源,这意味着这个光源的所有顶点都是在一个平面上,不能有凹凸!

最朴素的思想就是:使用蒙特卡罗方法。但这个思路在性能上是无法接受的。

2️⃣作者就发现了一种线性变换球面分布Linearly Transformed Spherical Distributions )的思想。这种思想就是对于任意一个球面分布函数,一定可以通过一个线性变换矩阵将其变化到另外一个球面分布函数。这个时候希望来了,由于 c o s + ( θ i ) cos^+(\theta_i) cos+(θi)是一个球面分布函数(余弦分布函数), f ( p , w i , w o ) f(p,w_i,w_o) f(p,wi,wo) 也是一个球面分布函数。

那么具体这个算法到底是什么呢?

c o s + cos^+ cos+clamp余弦

3️⃣根据论文,我们拿来进行简化计算的球面分布函数,实际上是 c o s + ( θ i ) ⋅ 1 π cos^+(\theta_i) \cdot \frac{1}{\pi} cos+(θi)π1。而原来的复杂的球面分布函数,实际上是 f ( p , w i , w o ) ⋅ cos ⁡ θ i f(p,w_i,w_o)\cdot \cos{\theta_i} f(p,wi,wo)cosθi

2. 算法分析

2.1 思路

1️⃣根据第一节,我们有两个认识:

  1. 在多边形光源上进行实时光照积分是困难的。( S S S 是多边形积分域)
    I = ∫ S ( L ( w l ) ⋅ f ( p , w i , w o ) ⋅ cos ⁡ ( θ p ) ) d w i I=\int_S(L(w_l)\cdot f(p,w_i,w_o)\cdot \cos(\theta_p))dw_i I=S(L(wl)f(p,wi,wo)cos(θp))dwi
  2. 根据LTCS思想(数学思想,所以我不懂,只是给出结论):存在一个变换矩阵 M M M,可以把简单的球面分布函数 c o s + ( θ i ) ⋅ 1 π cos^+(\theta_i)\cdot \frac{1}{\pi} cos+(θi)π1,变换成复杂的球面分布函数 f ( p , w i , w o ) ⋅ cos ⁡ θ i f(p,w_i,w_o)\cdot \cos{\theta_i} f(p,wi,wo)cosθi
    f ( p , w i , w o ) ⋅ cos ⁡ θ i ≈ M ∗ c o s + ( θ i ) π f(p,w_i,w_o)\cdot \cos{\theta_i}\approx M* \frac{cos^+(\theta_i)}{\pi} f(p,wi,wo)cosθiMπcos+(θi)
    注意:这里的*不是普通的矩阵乘法,这里的线性变换是指把我们的入射向量乘以矩阵M

矩阵 M M M : 三阶矩阵。根据论文,它主要包括如下的性质:
物理光源:Linearly Transformed Cosines_第2张图片

2️⃣但是,还是看不出来有什么用,所以我们需要知道LTCS的另外一个性质:
∫ S D ( w ) d w = ∫ S t D t ( w t ) d w t \int_SD(w)dw=\int_{S_t}D_t(w_t)dw_t SD(w)dw=StDt(wt)dwt
所以,我们会有:
∫ S f ( p , w i , w o ) ⋅ cos ⁡ θ i d w i = ∫ S t c o s + ( θ i ) π d w t \int_S f(p,w_i,w_o)\cdot \cos{\theta_i} dw_i=\int_{S_t}\frac{cos^+(\theta_i)}{\pi} dw_t Sf(p,wi,wo)cosθidwi=Stπcos+(θi)dwt
而积分域: S = M ∗ S t S=M*S_t S=MSt。积分域进行变换,实际上就是对这个多边形域的每个顶点,应用这个矩阵 M M M

3️⃣对 c o s cos cos 进行积分是很简单的,哪怕积分域 S t S_t St 比原积分域 S S S 复杂很多,也不会有太大影响——实际上,积分域 S t S_t St 也不会比 S S S 复杂,毕竟,我们并没有增加删除顶点,而只是对顶点的位置进行变换(不考虑裁剪: S S S 是五边形, S t S_t St 也会是五边形)。而且似乎对 c o s cos cos 进行积分有近似方法,这个后续进行分析。

4️⃣所以我们的最终的目标就是:放弃在多边形积分域 S S S中,使用复杂的 D ( w ) = f ( p , w i , w o ) ⋅ cos ⁡ θ i D(w)=f(p,w_i,w_o)\cdot \cos{\theta_i} D(w)=f(p,wi,wo)cosθi进行光照积分,而是使用简单的 D t ( w t ) = c o s + ( θ i ) π D_t(w_t)=\frac{cos^+(\theta_i)}{\pi} Dt(wt)=πcos+(θi)进行积分计算。
I = ∫ S t ( L ( w t ) ⋅ D t ( w t ) ) d w t I=\int_{S_t}(L(w_t)\cdot D_t(w_t))dw_t I=St(L(wt)Dt(wt))dwt

但目前还是有两个问题需要解决:

  1. 我们需要确定积分域 S t S_t St,所以我们需要知道 M − 1 M^{-1} M1
  2. 光照积分中可不只有球面分布函数 D t ( w t ) D_t(w_t) Dt(wt),还有入射光 L ( w t ) L(w_t) L(wt)

我们依次来解决!

2.2 预计算矩阵M

物理光源:Linearly Transformed Cosines_第3张图片

1️⃣根据原论文,我们可以知道,变换前后的球面分布函数有如下关系:
D ( w ) = D t ( w t ) δ w t δ w = D o ( M − 1 w ∣ ∣ M − 1 w ∣ ∣ ) ∣ M − 1 ∣ ∣ ∣ M − 1 w ∣ ∣ 3 D(w)=D_t(w_t)\frac{\delta w_t}{\delta w}=D_o(\frac{M^{-1}w}{||M^{-1}w||})\frac{|M^{-1}|}{||M^{-1}w||^3} D(w)=Dt(wt)δwδwt=Do(M1wM1w)M1w3M1
其中, D ( w ) = f ( p , w , w o ) ⋅ cos ⁡ θ i D(w)=f(p,w,w_o)\cdot \cos{\theta_i} D(w)=f(p,w,wo)cosθi D o ( w t ) = cos ⁡ θ t π D_o(w_t)=\frac{\cos{\theta_t}}{\pi} Do(wt)=πcosθt w t = M ∗ w w_t=M*w wt=Mw M M M 就是我们需要的转换矩阵。根据原论文,这个矩阵 M M M 实际上有如下固定形式:
物理光源:Linearly Transformed Cosines_第4张图片
2️⃣所以,我们的问题从求得一个矩阵,变成了求得 ( a , b , c , d ) (a,b,c,d) (a,b,c,d)的值,那么,怎么求呢?

IBL方法类似,我们在BRDF中不考虑菲涅尔项,而且假设入射方向等于法线方向,那么 f ( p , w i , w o ) ⋅ cos ⁡ θ i f(p,w_i,w_o)\cdot \cos{\theta_i} f(p,wi,wo)cosθi 项就只依赖于视线方向粗糙度。那么我们就可以计算得到一个张类似于BRDF LUT的二维纹理,其横纵坐标是视线法线夹角粗糙度

3️⃣咦?我们似乎还是不会求 M M M?根据大佬的博客:对于任意一个 f ( p , w i , w o ) ⋅ cos ⁡ θ i f(p,w_i,w_o)\cdot \cos{\theta_i} f(p,wi,wo)cosθi 一定找的到一个M变换矩阵把他变换到余弦分布,因此其实只需要遍历所有的矩阵M总能找到一个误差足够小的矩阵 M M M,不过穷举法不可能,我们采用一种单纯形法用于寻找 M M M。单纯形法的具体过程有点类似梯度下降的思路,我们随机初始化一个矩阵 M M M,然后经过计算,我们会得到一个“梯度”也就是我们该往哪个方向去修正我们的矩阵 M M M,循环往复,直到 M M M 的误差在接受范围内。

具体过程来说:

  1. 对于LUT的每个坐标,也就是一对具体的视线法线夹角粗糙度,我们可以随意取一个入射方向 w w w,求得 N 0 = f ( p , w , w o ) ⋅ cos ⁡ θ i N_0=f(p,w,w_o) \cdot \cos{\theta_i} N0=f(p,w,wo)cosθi
  2. 初始化一个矩阵 M M M,代入公式 D o ( M − 1 w ∣ ∣ M − 1 w ∣ ∣ ) ∣ M − 1 ∣ ∣ ∣ M − 1 w ∣ ∣ 3 D_o(\frac{M^{-1}w}{||M^{-1}w||})\frac{|M^{-1}|}{||M^{-1}w||^3} Do(M1wM1w)M1w3M1求值,得到 N 1 N_1 N1
  3. ∣ N 0 − N 1 ∣ |N_0-N_1| N0N1L3范数,作为误差 e e e
  4. 不断更新 M M M ,知道误差 e e e 足够小(怎么更新,暂未研究)。

4️⃣我们进行预计算,最终得到一张横纵坐标是视线法线夹角粗糙度,存储了转换矩阵的逆 M − 1 M^{-1} M1LUT

M矩阵预计算code

2.3 恒定多边形光源

1️⃣最简单的情况就是:多边形光源的辐照度是恒定的,那么这个时候,入射光 L L L就可以直接提出来。
I = L ⋅ ∫ S t 1 ∣ ∣ s − p ∣ ∣ 2 ⋅ cos ⁡ θ t π d w t I=L\cdot\int_{S_t}\frac{1}{||s-p||^2} \cdot \frac{\cos{\theta_t}}{\pi} dw_t I=LStsp21πcosθtdwt
其中, s s s 是渲染点, p p p是多边形光源上的点(变化后的,位于 S t S_t St上)。而且,根据Daniel的方法,上诉积分具有解析解:
∫ S t 1 ∣ ∣ s − p ∣ ∣ 2 ⋅ cos ⁡ θ t π d w t = 1 2 π ∑ j = 1 e n p ⋅ Γ j ∣ ∣ Γ j ∣ ∣ Υ j = I D − − − − − − − − − − − − − − − − − − − − Γ j = Y j × Y j + 1 \int_{S_t}\frac{1}{||s-p||^2} \cdot \frac{\cos{\theta_t}}{\pi} dw_t=\frac{1}{2\pi}\sum_{j=1}^{e}{n_p\cdot\frac{\Gamma_j}{||\Gamma_j||}}\Upsilon_j=I_D \\ --------------------\\ \Gamma_j=Y_j \times Y_{j+1} Stsp21πcosθtdwt=2π1j=1enpΓjΓjΥj=IDΓj=Yj×Yj+1
其中, e e e 是多边形光源的边数, n p n_p np 为渲染点 p p p 法线, Y j Y_j Yj 是着色点到多边形顶点的向量(这些顶点都是经过变换的,位于 S t S_t St), Υ j \Upsilon_j Υj为向量之间的夹角( Y j Y_j Yj Y j + 1 Y_{j+1} Yj+1之间的夹角 )。

1 ∣ ∣ s − p ∣ ∣ 2 \frac{1}{||s-p||^2} sp21来自于光线衰减

2️⃣所以,我们最终只需进行如下计算即可:
L ( p , w o ) = L ⋅ I D L(p,w_o)=L\cdot I_D L(p,wo)=LID

2.4 纹理光源


1️⃣纹理多边形光源的辐照度明显是不均匀的,所以我们不能直接将入射光提取出来,但我们可以利用下面这个常用技巧:
I = ∫ S ( L ( w l ) D ( w l ) ) d w l ≈ I D ⋅ I L − − − − − − − I D = ∫ S D ( w l ) d w l − − − − − − − I L = ∫ S ( L ( w l ) D ( w l ) ) d w l ∫ S D ( w l ) d w l I=\int_S{(L(w_l)D(w_l))}dw_l\approx I_D\cdot I_L \\ -------\\ I_D=\int_SD(w_l)dw_l \\ -------\\ I_L=\frac{\int_S{(L(w_l)D(w_l))}dw_l}{\int_SD(w_l)dw_l} I=S(L(wl)D(wl))dwlIDILID=SD(wl)dwlIL=SD(wl)dwlS(L(wl)D(wl))dwl
我们把积分拆成两部分,第一部分 I D I_D ID很简单,就是我们所需的完美形式,可以直接使用Daniel的方法求得解析解:
∫ S t 1 ∣ ∣ s − p ∣ ∣ 2 ⋅ cos ⁡ θ t π d w t = 1 2 π ∑ j = 1 e n p ⋅ Γ j ∣ ∣ Γ j ∣ ∣ Υ j = I D − − − − − − − − − − − − − − − − − − − − Γ j = Y j × Y j + 1 \int_{S_t}\frac{1}{||s-p||^2} \cdot \frac{\cos{\theta_t}}{\pi} dw_t=\frac{1}{2\pi}\sum_{j=1}^{e}{n_p\cdot\frac{\Gamma_j}{||\Gamma_j||}}\Upsilon_j=I_D \\ --------------------\\ \Gamma_j=Y_j \times Y_{j+1} Stsp21πcosθtdwt=2π1j=1enpΓjΓjΥj=IDΓj=Yj×Yj+1
2️⃣第二部分 I L I_L IL 怎么办呢?作者的方法是,可以看做纹理进行滤波后的值。在预过滤的步骤中,作者使用高斯滤波器,采取不同的过滤核半径进行过滤,对应不同的LOD

根据论文,这个过滤还有个需要注意的地方:预过滤纹理的值必须定义在纹理空间的每个地方,甚至在纹理之外——作者在纹理周围引入一个边缘,在这个区域,增加了滤镜的半径,使其与纹理相交。
物理光源:Linearly Transformed Cosines_第5张图片

3️⃣对于 I L I_L IL ,我们还有最后一个需要解决的问题,那就是用来读取这个纹理的UVLOD Level

  • LOD Level,论文里面是通过一个一维函数——纹理平面的平方距离 r 2 r^2 r2与多边形的面积 a a a之比: r 2 a \frac{r^2}{a} ar2。此外,最终结果需要进行混合,例如,我们得到的LOD Level3.4,那么我们需要读取Level 3Level 4的纹理值,然后进行混合(权重各自是0.60.4)。
  • UV。我们在使用Daniel的方法进行求解的时候,会得到一个指向面光源的向量,这个向量所指面光源上的点是对光照贡献最大的点,记作Q点。我们需要做到就是得到这个Q点,并算出它在这个纹理平面上对应的UV坐标。

4️⃣以下读取纹理贴图的源码来自Monica大佬的Github:

// vPolygonalLightVertexPos: 多边形光源的顶点
// vLooupVector:Daniel方法进行求解得到的向量
vec3 fecthFilteredLightTexture(vec3 vPolygonalLightVertexPos[4], vec3 vLooupVector)
{
	vec3 V1 = vPolygonalLightVertexPos[1] - vPolygonalLightVertexPos[0];
	vec3 V2 = vPolygonalLightVertexPos[3] - vPolygonalLightVertexPos[0];
	vec3 PlaneOrtho = cross(V1, V2);
	float PlaneAreaSquared = dot(PlaneOrtho, PlaneOrtho);
	
	SRay Ray;
	Ray.m_Origin = vec3(0);
	Ray.m_Dir = vLooupVector;
	vec4 Plane = vec4(PlaneOrtho, -dot(PlaneOrtho, vPolygonalLightVertexPos[0]));
	float Distance2Plane;
	rayPlaneIntersect(Ray, Plane, Distance2Plane);
	
	vec3 P = Distance2Plane * Ray.m_Dir - vPolygonalLightVertexPos[0];

	float Dot_V1_V2 = dot(V1, V2);
	float Inv_Dot_V1_V1 = 1.0 / dot(V1, V1);
	vec3  V2_ = V2 - V1 * Dot_V1_V2 * Inv_Dot_V1_V1;
	vec2  UV;
	UV.y = dot(V2_, P) / dot(V2_, V2_);
	UV.x = dot(V1, P) * Inv_Dot_V1_V1 - Dot_V1_V2 * Inv_Dot_V1_V1 * UV.y;
	UV = vec2(1 - UV.y, 1 - UV.x);
	
	float Distance = abs(Distance2Plane) / pow(PlaneAreaSquared, 0.25);
	
	float Lod = log(2048.0 * Distance) / log(3.0);

	float LodA = floor(Lod);
	float LodB = ceil(Lod);
	float t = Lod - LodA;
	vec3 ColorA = texture(u_FilteredLightTexture, vec3(UV, LodA)).rgb;
	vec3 ColorB = texture(u_FilteredLightTexture, vec3(UV, LodB)).rgb;
	return mix(ColorA, ColorB, t);
}

5️⃣最终,我们得到了此时的结果:
I = I L ⋅ I D I=I_L\cdot I_D I=ILID

2.5 考虑菲涅尔项

不知道对不对

1️⃣之前在计算过程中一直忽略了菲涅耳项,因此我们还需要计算菲涅耳项用于对上面计算的光照结果进行矫正。这里的思路也是类似IBL的方法,所以我们还需要使用第二张LUT,也就是大名鼎鼎的BRDF LUT
物理光源:Linearly Transformed Cosines_第6张图片
然后,实时运行时,直接使用:

// F 就是 F0, 或者说 Specular Color
vec2 envBRDF  = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y);

2️⃣所以说,最终结果是:
I S p e c = ( F 0 ∗ L U T . x + L U T . y ) ⋅ I D ⋅ I L I_{Spec}=(F_0*LUT.x+LUT.y)\cdot I_D \cdot I_L ISpec=(F0LUT.x+LUT.y)IDIL

2.6 关于漫反射项

1️⃣以上一切都是关于计算高光的,和漫反射无关。

2️⃣而实际上,漫反射的计算要简单的多,因为它没有lob,是均匀的,所以实际上它根本不需要考虑变换矩阵 M M M,在代码上,则是如下:

vec3 LTC_Evaluate(vec3 N, vec3 V, vec3 P, mat3 Minv, vec3 points[4], bool twoSided);
...
vec3 spec = LTC_Evaluate(N, V, pos, Minv, points, twoSided);
spec *= texture2D(ltc_mag, uv).w;
// 矩阵M是单位矩阵,不需要任何转换
vec3 diff = LTC_Evaluate(N, V, pos, mat3(1), points, twoSided); 
col  = lcol*(scol*spec + dcol*diff);
col /= 2.0*pi;

3. 参考

[1]Monica的小甜甜的博客
[2]官方源码
[3][Real-Time Polygonal-Light Shading with Linearly Transformed Cosines]

你可能感兴趣的:(计算机图形学,图形学,游戏引擎)