目录

  • 3.4、漫反射项的重要性采样计算
    • 3.4.1、漫反射项的二重积分形式极其近似预积分计算
    • 3.4.2、漫反射辐照度积分项的直接积分计算
    • 3.4.3、漫反射辐照度积分项的蒙特卡洛积分重要性采样计算

3.4、漫反射项的重要性采样计算

3.4.1、漫反射项的二重积分形式极其近似预积分计算

  进一步来说,因为对漫反射项积分来说 κ d 、 c 、 π \kappa_d 、c、\pi κd​、c、π 等参数对于指定的一点 p ⃗ \vec{p} p ​ 来说是常数,可以提到积分外面,所以其中第一个积分可以进一步推导为:
L o d ( p ⃗ , ω o ⃗ ) = κ d c π ∫ Ω L i ( p ⃗ , ω i ⃗ ) n ⃗ ⋅ ω i ⃗ d ω i ⃗ ∵ d ω = sin ⁡ ( θ ) d θ d ϕ , n ⃗ ⋅ ω i ⃗ = cos ⁡ ( θ ) = κ d c π ∫ 0 2 π ∫ 0 π 2 L i ( p ⃗ , ω i ⃗ ) cos ⁡ ( θ ) sin ⁡ ( θ ) d θ d ϕ \mathrm{L}_{o_d}(\vec{p},\vec{\omega_{o}}) = \kappa_d \frac{c}{\pi} \mathop{\int}_{\Omega} \mathrm{L}_i(\vec{p},\vec{\omega_i}) \vec{n} \cdot \vec{\omega_i} d\vec{\omega_i} \\[2ex] \because d \omega = \sin(\theta) d \theta d \phi, \quad \vec{n} \cdot \vec{\omega_i} = \cos(\theta) \\[2ex] = \kappa_d \frac{c}{\pi} \int \limits_0^{2\pi} \int \limits_0^{\frac{\pi}{2}} \mathrm{L}_i(\vec{p},\vec{\omega_i}) \cos(\theta) \sin(\theta) d \theta d \phi Lod​​(p ​,ωo​ ​)=κd​πc​∫Ω​Li​(p ​,ωi​ ​)n ⋅ωi​ ​dωi​ ​∵dω=sin(θ)dθdϕ,n ⋅ωi​ ​=cos(θ)=κd​πc​0∫2π​0∫2π​​Li​(p ​,ωi​ ​)cos(θ)sin(θ)dθdϕ
  上式中,角度 θ \theta θ 就是入射光线与点 p ⃗ \vec{p} p ​ 处法线 n ⃗ \vec{n} n 的夹角也就是入射角。同时在点 p ⃗ \vec{p} p ​ 处作直角坐标系如下图所示,其中正 z z z 方向与点 p ⃗ \vec{p} p ​ 处法线 n ⃗ \vec{n} n 方向一致, x x x 轴与 y y y 轴在与点 p ⃗ \vec{p} p ​ 处法线 n ⃗ \vec{n} n 垂直的平面内。

  接着取入射光 ω i \omega_i ωi​ 与点 p ⃗ \vec{p} p ​ 处法线 n ⃗ \vec{n} n 所成平面与 x x x 轴间的夹角为 ϕ \phi ϕ (称之为方位角),然后计算得到入射立体角微分 d ω = sin ⁡ ( θ ) d ω d ϕ d \omega = \sin(\theta) d \omega d \phi dω=sin(θ)dωdϕ 最终就得到了上式中漫反射项的最终可计算的二重积分形式。

  需要注意的是,我们现在使用的模型是针对不透明物体的表面的反射,所以积分的区域是点 p ⃗ \vec{p} p ​ 处,朝向法线方向的单位正半球,所以方位角 ϕ \phi ϕ 的范围是 [ 0 , 2 π ] [0,2\pi] [0,2π],对应的天顶角 θ \theta θ 范围就是 [ 0 , π 2 ] [0,\frac{\pi}{2}] [0,2π​] 。

  这个积分项在 IBL 方法中称之为“漫反射项”。注意到其中积分部分已经完全与 ω o ⃗ \vec{\omega_o} ωo​ ​ 也即视方向无关了,所以其实这一项是可以从每一帧循环渲染计算中完全提取出来的。当然提取的前提是被照射的物体与其环境在某段固定的时间中基本不变,或者说针对一个相对固定的场景来说。

  具体来讲,对于一个固定点 p ⃗ \vec{p} p ​ 来说,整个式子又可以被拆分成两部分去计算,一部分是针对点 p ⃗ \vec{p} p ​ 的常数项 κ d c π \kappa_d \cfrac{c}{\pi} κd​πc​ ,这个可以被延迟到渲染循环中的物体表面 PS 中去计算,因为参数 c c c 就是物体表面点的漫反射率,也就是我们通常所说的物体表面的颜色,而 κ d \kappa_d κd​ 就是控制整体能量守恒的系数,需要先计算点 p ⃗ \vec{p} p ​ 处的 κ s \kappa_s κs​ 也就是菲涅尔系数后再确定,所以整个常数项实际是与物体表面相关联的,因此就放到最终渲染循环中去。

  而第二部分积分项,其实本质上也是与物体表面的点 p ⃗ \vec{p} p ​ 相关联的,但如果也因此而放到最终渲染循环中的话,就会导致计算量过大,而无法实现实时的 PBR 渲染。因此在这里第一个关于质量和性能的折中处理,就是我们总是假设这个积分中的点 p ⃗ \vec{p} p ​ 就是物体的几何中心点所在的位置,然后将整个积分的计算从渲染循环中提取出来,变成一个全局预处理的计算过程,而这个预积分计算的结果就被称之为“预积分辐照度贴图”,因为整个预计算部分正好就是针对所有方向的入射光也就是入射辐照度进行积分。

  实际应用时,这需要假设我们可以提前预知点 p ⃗ \vec{p} p ​ 的位置。对于固定的场景中的固定的物体位置来说,这本来就是已知量。这就使得预积分辐照度贴图最终完全可以在场景设计确定的阶段就计算完毕,并存储起来,在真正运行渲染时进行采样使用即可。

  在本章示例中我们只是用了最简单的 Cubemap 纹理(或者叫2D纹理数组 Texture2DArray)的方式,对这个辐照图贴图进行了存储,这样的存储方式还是比较耗费空间的。更高级的可以使用球谐函数的方式简化存储。

  在示例代码运行后的矩形框中,紧跟解算后的6副环境映射纹理之后的6副纹理就展示了预积分贴图的六个面:

  如果场景中的物体是经常变动位置时,而且大多数情况下,我们渲染的核心很多时候就是3D动画的场景,这时点 p ⃗ \vec{p} p ​ 的位置就会经常变动,就使的这个预积分计算跟渲染循环,或者说跟具体的渲染帧发生了关联,因此刚才说的假设点 p ⃗ \vec{p} p ​ 是某物体中心点的近似预积分方法就可能失效了。

  此时可以在场景中的某些按照一定规律固定排列的点 p ⃗ n \vec{p}_n p ​n​ 来作为预积分计算的点,然后在渲染循环中,根据物体位置离哪个点 p ⃗ n \vec{p}_n p ​n​ 近,就取那个点处的预积分贴图来进行漫反射项的近似计算。这样就可以在场景设计定型的时候,预计算一堆关于点 p ⃗ n \vec{p}_n p ​n​ 的漫反射辐照度预积分贴图。而这些点 p ⃗ n \vec{p}_n p ​n​ 就被称为 Probe (探针)。更进一步的,在探针技术的基础上,可以使用球谐函数等来简化预积分辐照度贴图计算结果的存储和使用。从而变成一个一次性计算(或称之为预计算)!这些话题后续教程再说。

3.4.2、漫反射辐照度积分项的直接积分计算

  至此其实漫反射项的二重积分形式已经可以直接编码进行计算了,在示例代码 Shader 文件: GRSD3D12Sample/GRS_IBL_Diffuse_Irradiance_Convolution_With_Integration_PS.hlsl 中就展示了直接二重积分计算的方法:

float4 PSMain(ST_GRS_HLSL_PS_INPUT pin):SV_Target
{float3 N = normalize(pin.m_v4WPos.xyz);float3 irradiance = float3(0.0f, 0.0f, 0.0f);float3 up = float3(0.0f, 1.0f, 0.0f);float3 right = normalize(cross(up, N));up = normalize(cross(N, right));N = normalize(cross(right,up));float nrSamples = 0.0f;float deltaPhi = (2.0f * PI) / 180.0f;float deltaTheta = (0.5f * PI) / 90.0f;// 积分运算,直接翻译自公式for (float phi = 0.0f; phi < 2.0f * PI; phi += deltaPhi){for (float theta = 0.0f; theta < 0.5f * PI; theta += deltaTheta){// 球坐标转换到笛卡尔坐标(切空间)float3 tangentSample = float3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));// 切空间转换到世界坐标空间float3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;//float3 sampleVec = right;irradiance += (g_texHDREnvCubemap.Sample(g_sapLinear, sampleVec).xyz * cos(theta) * sin(theta));nrSamples += 1.0f;}}irradiance = PI * irradiance * (1.0 / float(2 * nrSamples));return float4(irradiance, 1.0f);
}

  上面代码中变量 irradiance 代表最终点 p ⃗ \vec{p} p ​ 处的所有入射光引起的入射辐照度,所以这个过程最终计算出来的预积分纹理也被称为“辐照度贴图”。

  代码中的 deltaPhi 即 d ϕ d\phi dϕ,根据代码中的表达式,可以看出它的步长(step)是 2 π 180 \cfrac{2\pi}{180} 1802π​弧度,也就是总共计算180次、deltaTheta 即 d θ d \theta dθ ,对应的其步长是 π 2 90 \cfrac{\frac{\pi}{2}}{90} 902π​​弧度,总共计算 90 次。最终这使得双重循环黎曼和计算对于一个采样点 p ⃗ \vec{p} p ​ 来说总共需要 180 × 90 = 16200 180 \times 90 =16200 180×90=16200 次!然而这还只是一个很粗糙的颗粒度,这从其步长设置就可以看出来。并且实际的效果也是惨不忍睹的。

  而总体上我们总共有 6 × 32 × 32 = 6144 6 \times 32 \times 32 = 6144 6×32×32=6144 个点,这只是低分辨率下的辐照度贴图映射的大小。在示例代码开头处我们用宏定义指定了这个大小:

#define GRS_RTA_IBL_DIFFUSE_MAP_SIZE_W  32
#define GRS_RTA_IBL_DIFFUSE_MAP_SIZE_H  32

  接着代码中主要就是用点 p ⃗ \vec{p} p ​ 的方向用作其自身的法线,并假设向上的方向为正 y 轴方向,然后用施密特正交法计算得到了点 p ⃗ \vec{p} p ​ 处局部切空间的三个正交坐标轴,也就是得到了切空间转换到世界坐标空间的变换矩阵,这也是一个非常重要和基础的切空间计算的简单技巧。然后用这个切空间的变换矩阵,将点 p ⃗ \vec{p} p ​ 处局部坐标系中的立体角微分向量变换到了世界坐标中,并作为 3D 采样(Sample)操作的向量对环境映射进行采样。

  最终再一次提醒,在没有完全把握的情况下,请千万不要使用和运行GRSD3D12Sample/GRS_IBL_Diffuse_Irradiance_Convolution_With_Integration_PS.hlsl 这个 Shader 脚本,以免因计算量过大造成显卡损伤。放在本章示例中,仅仅是为了说明预计算辐照度贴图计算过程的基本原理。或者说是为了说明下基本的数值积分代码如何编写而已。

3.4.3、漫反射辐照度积分项的蒙特卡洛积分重要性采样计算

  了解完了基本的预积分辐照度贴图的计算过程之后,希望你也看了 3D数学系列之——再谈特卡洛积分和重要性采样 并明白了其中介绍的重要性采样方法。索性的是,蒙特卡洛方法可以使我们极大的减小预积分辐照度贴图的计算量,并且还能保持一定的精度。

  根据文中的公式:
p ( x ) = ∣ f ( x ) ∣ ∫ a b f ( x ) d x \mathrm{p}(x) = \cfrac{|{f}(x)|}{\int \limits_a^b {f}(x) \mathrm{d}x} p(x)=a∫b​f(x)dx∣f(x)∣​
  我们继续使用瞪眼法来观察漫反射项中的积分部分:
∫ 0 2 π ∫ 0 π 2 L i ( p ⃗ , ω i ⃗ ) cos ⁡ ( θ ) sin ⁡ ( θ ) d θ d ϕ \int \limits_0^{2\pi} \int \limits_0^{\frac{\pi}{2}} \mathrm{L}_i(\vec{p},\vec{\omega_i}) \cos(\theta) \sin(\theta) d \theta d \phi 0∫2π​0∫2π​​Li​(p ​,ωi​ ​)cos(θ)sin(θ)dθdϕ
  虽然这是个二重积分,但是根据蒙特卡洛积分的特性,这也是可以用蒙特卡洛积分来处理的。只是需要我们选取合适的概率密度函数 p \mathrm{p} p 。通过观察我们可以发现,使用如下的概率密度函数,就可以进行重要性采样计算:
p ( θ , ϕ ) = c cos ⁡ ( θ ) sin ⁡ ( ϕ ) \mathrm{p}(\theta,\phi) = c \cos(\theta)\sin(\phi) p(θ,ϕ)=ccos(θ)sin(ϕ)
  首先要注意的是,因为被积函数是关于 θ , ϕ \theta,\phi θ,ϕ 的二元函数(点 p ⃗ \vec{p} p ​ 参数相对固定对于整个积分来说相当于常数,而入射光向量 ω ⃗ i \vec{\omega}_i ω i​ 及立体角微分都是可以用球坐标系下的 θ , ϕ \theta,\phi θ,ϕ 表达的),所以我们设计的概率密度函数也是关于相同自变量的二元函数。

  另外在函数表达式中只是提取了三角函数的因子,这主要是因为一般函数表达式中如果乘入了三角函数因子尤其是正弦或余弦函数时,函数的形状就主要会受到他们的显著影响,所以我们选择如上的概率密度函数,作为我们进一步简化积分计算的切入点。

  根据重要性采样的方法步骤,第一步就是要验证概率密度函数的“归一性”,从而还可以顺道计算出式中常数项 c c c 的值,这可以根据概率密度函数的性质要求来计算:
p ( θ , ϕ ) = ∫ 0 2 π ∫ 0 π 2 c cos ⁡ ( θ ) sin ⁡ ( θ ) d θ d ϕ = 1 ⇒ c ∫ 0 2 π d ϕ ∫ 0 π 2 cos ⁡ ( θ ) sin ⁡ ( θ ) d θ = 1 ∵ d sin ⁡ ( θ ) = cos ⁡ ( θ ) d θ c ∫ 0 2 π d ϕ ∫ 0 π 2 sin ⁡ ( θ ) d sin ⁡ ( θ ) = 1 ⇒ c [ ϕ ] 0 2 π [ 1 2 sin ⁡ 2 ( θ ) ] 0 π 2 = 1 ⇒ c = 1 π ⇒ p ( θ , ϕ ) = 1 π cos ⁡ ( θ ) sin ⁡ ( θ ) \mathrm{p}(\theta,\phi) = \int \limits_0^{2\pi} \int \limits_0^{\frac{\pi}{2}} c \cos(\theta) \sin(\theta) d \theta d \phi = 1 \\[2ex] \Rightarrow c \int \limits_0^{2\pi} \mathrm{d}\phi \int \limits_0^{\frac{\pi}{2}} \cos(\theta)\sin(\theta) \mathrm{d} \theta = 1 \\[2ex] \because \mathrm{d} \sin(\theta) = \cos(\theta)\mathrm{d} \theta \\[2ex] c \int \limits_0^{2\pi} \mathrm{d}\phi \int \limits_0^{\frac{\pi}{2}} \sin(\theta) \mathrm{d} \sin(\theta) = 1 \\[2ex] \Rightarrow c \Biggl[ \phi \Biggr]_0^{2\pi} \Biggl[ \cfrac{1}{2} \sin^2(\theta) \Biggr]_0^{\frac{\pi}{2} } = 1 \\[2ex] \Rightarrow c = \cfrac{1}{\pi} \\[2ex] \Rightarrow \mathrm{p}(\theta,\phi) = \cfrac{1}{\pi} \cos(\theta)\sin(\theta) p(θ,ϕ)=0∫2π​0∫2π​​ccos(θ)sin(θ)dθdϕ=1⇒c0∫2π​dϕ0∫2π​​cos(θ)sin(θ)dθ=1∵dsin(θ)=cos(θ)dθc0∫2π​dϕ0∫2π​​sin(θ)dsin(θ)=1⇒c[ϕ]02π​[21​sin2(θ)]02π​​=1⇒c=π1​⇒p(θ,ϕ)=π1​cos(θ)sin(θ)

  因为这个概率密度函数是二元函数,而我们产生随机数需要一个变量一个变量的产生,这就需要进一步针对每个随机变量计算其边缘概率密度,从而对随机变量进行分离,此时推算如下:
∵ p ( θ , ϕ ) = 1 π cos ⁡ ( θ ) sin ⁡ ( θ ) ∴ p ( θ , ϕ ) = p ( θ ) × p ( ϕ ) \because \quad \mathrm{p}(\theta,\phi) = \cfrac{1}{\pi} \cos(\theta)\sin(\theta) \\[2ex] \therefore \mathrm{p}(\theta,\phi) = \mathrm{p}(\theta)\times \mathrm{p}(\phi) ∵p(θ,ϕ)=π1​cos(θ)sin(θ)∴p(θ,ϕ)=p(θ)×p(ϕ)

  上面过程说明我们要使用的随机变量 θ , ϕ \theta,\phi θ,ϕ 是相互独立的,其实从其定义也可以看出,这两个角度变量是没有关联性的。所以就可以继续推导每一个独立随机变量的概率密度函数:
p ( ϕ ) = ∫ 0 π 2 p ( θ , ϕ ) d θ = ∫ 0 π 2 1 π cos ⁡ ( θ ) sin ⁡ ( θ ) d θ = [ 1 2 π sin ⁡ 2 ( θ ) ] 0 π 2 = 1 2 π \mathrm{p}(\phi) = \int \limits_0^{\frac{\pi}{2}} \mathrm{p}(\theta,\phi) \mathrm{d} \theta \\[2ex] = \int \limits_0^{\frac{\pi}{2}} \cfrac{1}{\pi} \cos(\theta)\sin(\theta) \mathrm{d} \theta \\[2ex] =\Biggl[ \cfrac{1}{2\pi} \sin^2(\theta) \Biggr]_0^{\frac{\pi}{2} } \\[2ex] = \cfrac{1}{2\pi} p(ϕ)=0∫2π​​p(θ,ϕ)dθ=0∫2π​​π1​cos(θ)sin(θ)dθ=[2π1​sin2(θ)]02π​​=2π1​

∵ p ( θ , ϕ ) = p ( θ ) × p ( ϕ ) 1 π cos ⁡ ( θ ) sin ⁡ ( θ ) = p ( θ ) × 1 2 π ⇒ p ( θ ) = 2 cos ⁡ ( θ ) sin ⁡ ( θ ) \because \quad \mathrm{p}(\theta,\phi) = \mathrm{p}(\theta)\times \mathrm{p}(\phi) \\[2ex] \cfrac{1}{\pi} \cos(\theta)\sin(\theta) = \mathrm{p}(\theta) \times \cfrac{1}{2\pi} \\[2ex] \Rightarrow \mathrm{p}(\theta) = 2 \cos(\theta) \sin(\theta) ∵p(θ,ϕ)=p(θ)×p(ϕ)π1​cos(θ)sin(θ)=p(θ)×2π1​⇒p(θ)=2cos(θ)sin(θ)

  至此,我们就分析推导清楚了我们需要的随机变量的概率密度函数,接着按照重要性采样的步骤,就需要计算每个随机变量对应的概率分布函数 CDF :
C D F ( θ ) = ∫ 0 θ 2 cos ⁡ ( x ) sin ⁡ ( x ) d x = [ 2 × 1 2 sin ⁡ 2 ( x ) ] 0 θ = sin ⁡ 2 ( θ ) C D F ( ϕ ) = ∫ 0 ϕ 1 2 π d x = 1 2 π ϕ \mathrm{CDF}(\theta) = \int \limits_0^{\theta} 2\cos(x) \sin(x) \mathrm{d} x \\[2ex] = \Biggl[ 2 \times \cfrac{1}{2} \sin^2(x) \Biggr]_0^{\theta} \\[2ex] = \sin^2(\theta) \\[2ex] \mathrm{CDF}(\phi) = \int \limits_0^{\phi} \cfrac{1}{2\pi} \mathrm{d} x \\[2ex] = \cfrac{1}{2\pi} \phi CDF(θ)=0∫θ​2cos(x)sin(x)dx=[2×21​sin2(x)]0θ​=sin2(θ)CDF(ϕ)=0∫ϕ​2π1​dx=2π1​ϕ
  接着继续计算 CDF 的反函数如下:
令 μ = sin ⁡ 2 ( θ ) , ν = 1 2 π ϕ 则: θ = arcsin ⁡ ( μ ) ϕ = 2 π ν 令 \quad \mu = \sin^2(\theta), \nu = \cfrac{1}{2\pi} \phi \quad 则: \\[2ex] \theta = \arcsin(\sqrt{\mu}) \\[2ex] \phi = 2\pi \nu 令μ=sin2(θ),ν=2π1​ϕ则:θ=arcsin(μ ​)ϕ=2πν
   最后我们只需要产生两个均匀分布的伪随机序列 μ , ν \mu,\nu μ,ν 就可以利用蒙特卡洛积分公式计算辐照度积分了。那么让我们将所有推导的结果反代回蒙特卡洛积分先做一下简化:
∵ ∫ a b f ( x ) d x ≈ 1 N ∑ n = 1 N f ( x n ) p ( x n ) L o d ( p ⃗ , ω o ⃗ ) = κ d c π ∫ 0 2 π ∫ 0 π 2 L i ( p ⃗ , ω i ⃗ ) cos ⁡ ( θ ) sin ⁡ ( θ ) d θ d ϕ ∵ p ( θ , ϕ ) = 1 π cos ⁡ ( θ ) sin ⁡ ( θ ) ∴ L o d ( p ⃗ , ω o ⃗ ) ≈ κ d c π 1 N ∑ n = 1 N L i ( p ⃗ , ω i ⃗ ) cos ⁡ ( θ ) sin ⁡ ( θ ) 1 π cos ⁡ ( θ ) sin ⁡ ( θ ) = κ d c 1 N ∑ n = 1 N L i ( p ⃗ , ω i ⃗ ) \because \quad \int \limits_a^b {f}(x) \mathrm{d} x \approx \cfrac{1}{N} \sum \limits_{n=1}^{N} \cfrac{{f}(x_n)}{\mathrm{p}(x_n)} \\[2ex] \mathrm{L}_{o_d}(\vec{p},\vec{\omega_{o}}) = \kappa_d \frac{c}{\pi} \int \limits_0^{2\pi} \int \limits_0^{\frac{\pi}{2}} \mathrm{L}_i(\vec{p},\vec{\omega_i}) \cos(\theta) \sin(\theta) d \theta d \phi \\[2ex] \because \quad \mathrm{p}(\theta,\phi) = \cfrac{1}{\pi} \cos(\theta)\sin(\theta) \\[2ex] \therefore \quad \mathrm{L}_{o_d}(\vec{p},\vec{\omega_{o}}) \approx \kappa_d \frac{c}{\pi} \cfrac{1}{N} \sum \limits_{n=1}^{N} \cfrac{\mathrm{L}_i(\vec{p},\vec{\omega_i}) \cos(\theta) \sin(\theta)}{\cfrac{1}{\pi}\cos(\theta)\sin(\theta)} \\[2ex] = \kappa_d c \cfrac{1}{N} \sum \limits_{n=1}^{N} \mathrm{L}_i(\vec{p},\vec{\omega_i}) ∵a∫b​f(x)dx≈N1​n=1∑N​p(xn​)f(xn​)​Lod​​(p ​,ωo​ ​)=κd​πc​0∫2π​0∫2π​​Li​(p ​,ωi​ ​)cos(θ)sin(θ)dθdϕ∵p(θ,ϕ)=π1​cos(θ)sin(θ)∴Lod​​(p ​,ωo​ ​)≈κd​πc​N1​n=1∑N​π1​cos(θ)sin(θ)Li​(p ​,ωi​ ​)cos(θ)sin(θ)​=κd​cN1​n=1∑N​Li​(p ​,ωi​ ​)
  从上面的最终推导过程我们可以看出,首先在形式上比原先的二重积分形式简单了很多,并且计算过程直接变成了 1 维求和计算,同时还自动消去了一个除以 π \pi π 的计算;其次求和部分只剩下入射光函数 L i ( p ⃗ , ω i ⃗ ) \mathrm{L}_i(\vec{p},\vec{\omega_i}) Li​(p ​,ωi​ ​) ,这在 IBL 中仅仅只是一个 3D Sample 操作,最终整个求和式就变成了针对环境映射的一个采样平均值。

  具体的在本章示例代码的 Shader GRSD3D12Sample/GRS_IBL_Diffuse_Irradiance_Convolution_With_Monte_Carlo_PS.hlsl 中实现如下:

float4 PSMain(ST_GRS_HLSL_PS_INPUT stPSInput) :SV_Target
{float3 N = normalize(stPSInput.m_v4WPos.xyz);float3 irradiance = float3(0.0f,0.0f,0.0f);// 计算切空间float3 Up = float3(0.0f, 1.0f, 0.0f);float fdot = dot(Up, N);if ((1.0 - fdot) > 1e-6){Up = float3(1.0f, 0.0f, 0.0f);}float3 Right = normalize(cross(Up, N));Up = normalize(cross(N, Right));N = normalize(cross(Right, Up));float theta = 0.0f;float phi = 0.0f;float2 Xi;float3 L;const uint SAMPLE_NUM = 4096u;// 蒙特卡洛积分循环for (uint i = 0u; i < SAMPLE_NUM; i++){// 产生均匀分布随机数Xi = Hammersley(i, SAMPLE_NUM);// 根据 CDF 反函数计算指定分布的随机变量phi = TWO_PI * Xi.x;theta = asin(sqrt(Xi.y));// 球坐标转换到笛卡尔坐标(切空间)L = float3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));// 切空间转换到世界坐标空间L = L.x * Right + L.y * Up + L.z * N;// 采样求和统计入射辐照度irradiance += g_texHDREnvCubemap.Sample(g_sapLinear, L).xyz;}// 取平均得到最终结果irradiance *= 1.0 / float(SAMPLE_NUM);return float4(irradiance,1.0f);
}

  代码中的过程及注释已经很清晰的说明了整个计算过程,与我们做的数学推导过程是一致的。所以最终只要理解了整个数学推算的过程,最终的代码就变得很好理解了。

  但其中还是需要注意几个细节:首先,代码中生成均匀分布随机数的方法使用了流行的 Hammersley 算法,关于这个算法,回头在另外篇章的教程中单独介绍,本教程中就不在赘述了,现在只需要知道它能够产生高质量的 2D 均匀分布伪随机序列即可,并且范围在 [ 0 , 1 ] [0,1] [0,1] 之间。

  其次,整个蒙特卡洛积分循环的次数设置成了 4096 次,其实大概 2000多次的时候效果就已经很好了。但即就是使用4096次循环,也比之前的二重积分双循环运行16200次的直译算法效果好的很多,并且计算次数直接节省了到了四分之一以上。假如要使用直接数值积分算法效果达到重要性采样方法的级别,估计循环次数还需要再增进至少4倍以上,当然为了防止显卡损伤,所以还是不要去做这样的尝试了(主要是因为循环中嵌套着耗时并且费硬件的 Sample 操作)。

  最后,代码中为了做教程的目的,所以对 θ \theta θ 角的变量做了反三角函数的运算,其实实际工程代码中,不需要做这个操作,直接将开方后的随机值作为 θ \theta θ 角的正弦值即可,余弦值根据三角公式即可算得,这样就节省了反复计算三角函数的开销。而大多数实际的代码中确实是这样的做的,比如 UE 引擎的 Shader 代码中。

  额外需要提醒的是在著名的 [漫反射辐照 - LearnOpenGL CN](https://learnopengl-cn.github.io/07 PBR/03 IBL/01 Diffuse irradiance/) 教程中,既没有详细讲述整个数学过程,也没有推导重要性采样的过程,最后其漫反射辐照度贴图使用了计算量更大的双重循环来计算,如果不加思索的直接使用其代码,可能会造成显卡过热保护而退出,严重的情形下可能会造成显卡硬伤,所以在没有搞清楚的情况下请不要随意执行。而正确的做法是使用本章教程中所讨论的重要性采样方法来计算漫反射辐照度贴图。

DirectX12(D3D12)基础教程(二十一)—— PBR:IBL 的数学原理(3/5)漫反射积分项相关推荐

  1. DirectX12(D3D12)基础教程(二十一)—— PBR:IBL 的数学原理(1/5)

    目录 1.前言 1.1.一些感慨 1.2.运行效果展示 1.3.示例简介 1.4.示例操作说明 1.5.本章内容的简述 2.什么是IBL 2.1."Cook-Torrance" 模 ...

  2. 【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉的诡计 公告板 Billboard 技术

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本系列文 ...

  3. 群晖NAS教程(二十一)、利用Docker安装DDNS动态域名解析,自动更新域名解析到公网IP(支持阿里云、腾讯云dnspod、Cloudflare、华为云)

    群晖NAS教程(二十一).利用Docker安装DDNS动态域名解析,自动更新域名解析到公网IP(支持阿里云.腾讯云dnspod.Cloudflare.华为云) 为了更好的浏览体验,欢迎光顾勤奋的凯尔森 ...

  4. 【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉的诡计:公告板(Billboard)技术...

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 作者:毛星云(浅墨)   微博:@浅墨_毛星云邮箱: happylifemxy@163.com I'm back~,这段时间大家久等了~ ...

  5. 微信小程序入坑教程二十一:使用wx.saveImageToPhotosAlbum保存图片时通过检测scope.writePhotosAlbum权限来提醒用户是否需要授权

    摘要: 在昨天的文章<微信小程序入坑教程二十:生成海报前使用wx.downloadFile或wx.getImageInfo时潜在的坑>中提到了用户授权一事,scope.writePhoto ...

  6. DirectX12(D3D12)基础教程(十一)——几个“上古时代”的基于Pixel Shader的滤镜效果

    1.前言 记得大约是在10多年前,还在我努力的在学习着DirectX编程和3D引擎编程的方方面面知识的时候,初次接触到了当时很先进的Shader程序.当时搜集了很多资料,其中有一份就是讲解如何在Pix ...

  7. 小柏实战学习Liunx(图文教程二十一)

    前言:一定要知道每一个命令是啥意思,并且要学会看报错信息,学会使用搜索引擎. 前情回顾:小柏实战学习Liunx(图文教程二十)_Shaun-sheng的博客-CSDN博客 本节课主题:Linux安装A ...

  8. nCode:DesignLife案例教程二十一

    nCode:DesignLife 案例二十一--DesignLife中有限元模型的动画 21.1 案例文件 21.2 背景 21.3 分析流程 21.4 添加动画分析引擎 21.5 在FEDispla ...

  9. Python-EEG工具库MNE中文教程(10)-信号空间投影SSP数学原理

    目录 projector(投影)和投影背景 案例解释投影原理 导入工具库 什么是projector(投影)? 计算正交平面 使用SVD计算投影矩阵 本分享为脑机学习者Rose整理发表于公众号:脑机接口 ...

最新文章

  1. 如何获取js对象的对象名
  2. bootstrap bootstrapTable 隐藏列
  3. python __init__ __new___Python中的__init__和__new__介绍
  4. win11怎么回退原系统 Windows11回退的步骤方法
  5. matplotlib的一些代码
  6. cocos2dx3.2 画图方法小修改之 C++ final学习
  7. linux轮训创建文件夹,Linux文件和目录管理相关命令(三)
  8. 测试方案的设计及模板
  9. 【项目篇- 项目团队部分怎么写、如何作图?(两千字图文总结建议)】创新创业竞赛项目计划书、新苗国创(大创)申报书、挑战杯创业计划竞赛
  10. 易用的两个OCR(图像文字识别提取)开源项目
  11. FileOutputStream方法创建文件并写入数值
  12. InnerHTML、InnerText、outerHTML的用法与区别
  13. Python每日练习
  14. 软件工程部署图_部署图| 软件工程
  15. php select只有一条_读取数据库如何只取出一条数据????请赐教!
  16. 宜人贷 PaaS 数据服务平台Genie 简介(一) 1
  17. stm32添加了固件库但是找不到h文件
  18. 华北科技c语言综合实验报告,C语言综合性实验报告1.doc
  19. matlab 16qam误码率图,(完整版)16QAM理论误码率与实际误码率MATLAB仿真程序
  20. Matlab—绘图保存为视频VideoWrite()函数用法与自定义视频尺寸

热门文章

  1. LoginController________
  2. BUG记录:Exception evaluating SpringEL expression
  3. python boxcox1p_Python mpmath.mpf方法代码示例
  4. CYCA少儿形体礼仪 清远市培训成果考核圆满落幕
  5. 开发过程中经常犯的低级错误
  6. 产品经理 | 如何介绍自己的产品
  7. 在一个公司死磕10年是种什么体验?
  8. 【无为则无心Python基础】— 8、PyCharm下载与安装
  9. Unity3D - 反射(Refrections)
  10. 大数据-面试问题大全-持续更新