Files
2025-02-23 20:51:53 +08:00

8.6 KiB

环境光渲染(二)


3. 高光部分

3.1 原理

光照方程中的高光部分


L_{os}(\vec{\omega_o})=\int_{\Omega}f_s(\vec{\omega_i}, \vec{\omega_o})L_i(\vec{\omega_i})(\vec{\omega}_i\cdot\vec{n})d\omega_i

同样使用蒙特卡洛进行近似积分


\begin{aligned}
&\int_{\Omega}f_s(\vec{\omega_i}, \vec{\omega_o})L_i(\vec{\omega_i})(\vec{\omega}_i\vec{n})d\omega_i \\
\approx &\dfrac{1}{N}\sum_{k=1}^{N}\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})L_i(\vec{\omega_k})(\vec{\omega_k}\cdot\vec{n})}{\text{pdf}(\vec{\omega_k})}
\end{aligned}

EPIC在此基础上提出了一种近似计算方法


\begin{aligned}
&\dfrac{1}{N}\sum_{k=1}^{N}\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})L_i(\vec{\omega_k})(\vec{\omega_k}\cdot\vec{n})}{\text{pdf}(\vec{\omega_k}, \vec{\omega_o})}\\
\approx&\left(\dfrac{1}{N}\sum_{k=1}^N L_i(\vec{\omega_k})\right)\left(\dfrac{1}{N}\sum_{k=1}^{N}\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})(\vec{\omega_k}\cdot\vec{n})}{\text{pdf}(\vec{\omega_k})}\right)
\end{aligned}

这种方法的好处是可以通过分开预计算,尽量减少实际渲染时的计算量,这个公式可以理解为“Env. lighting x BRDF”,左边的第一部分是小平面受到的所有环境光的采样值,第二部分只跟材质的BRDF属性相关。

3.2 预渲染环境光贴图(pre-filtered environment map)

第一个拆分项只和环境光照以及采样点有关,首先采样点集中在$\vec{\omega}_r$周围($\vec{\omega}_r$是$\vec{\omega}_o$相对于法线$\vec{n}$的反射向量),并且粗糙度越大,采样点越分散。 实际应用中,这个拆分项是通过把环境光贴图预先渲染到一个环境贴图中实现的,称为为"pre-filter map"或者"radiance map",最终渲染时根据$\vec{\omega}_r$,以及把粗糙度(0~1)转换为mip获得该方向的数值


\dfrac{1}{N}\sum_{k=1}^N L_i(\vec{\omega_k})=\text{TexCubeLod}(\text{PrefilterMap}, \vec{\omega}_r, \text{mip})

3.3 预渲染贴图的生成

生成这张贴图时,首先假定$\vec{n}=\vec{\omega_o}=\vec{\omega_r}$,也就是摄像机从法线方向垂直观察小平面,然后开始采样

再最终渲染时,当$\vec{n}$和$\vec{\omega_o}$不等时,采样点的分布会有一定差异,但基本上可以忽略

3.3.1 采样点的生成

根据前面章节的分析,微表面的法线分布可以用法线分布函数$D(h)$描述,并且符合


\int_{\Omega}D(h)\cos\theta_hd\omega=1

由于微表面法线的概率密度函数$\text{pdf}(h)$符合以下公式


\int_{\Omega}\text{pdf}(h)=1

所以


\text{pdf}(h)=D(h)\cos\theta_h

用极坐标表示


\text{pdf}(\theta,\phi)=D(h)\cos\theta\sin\theta

对于各向同性系统,$\text{pdf}(\theta,\phi)$和$\phi$无关,所以 $$\begin{split} \text{pdf}(\phi)&=\dfrac{1}{2\pi} \ \text{pdf}(\theta)&=\int_{0}^{2\pi}\text{pdf}(\theta,\phi)d\phi=2\pi\cdot\text{pdf}(\theta,\phi) \end{split}$$ D函数使用GGX函数,也就是


D(h)=\dfrac{\alpha^2}{\pi(\cos^2\theta(\alpha^2-1)+1)^2}

其中$\alpha=\text{roughness}^2$,此时


\text{pdf}(\theta)=2\pi\cdot\text{pdf}(\theta,\phi)=\dfrac{2\alpha^2\cos\theta\sin\theta}{(\cos^2\theta(\alpha^2-1)+1)^2}

计算$\theta$的概率分布函数 $$\begin{split} \text{cdf}(\theta_h)&=\int_{0}^{\theta_h}\text{pdf}(\theta)d\theta\ &=\int_{0}^{\theta_h}\dfrac{2\alpha^2\cos\theta\sin\theta}{(\cos^2\theta(\alpha^2-1)+1)^2}d\theta \end{split}$$ 使用Mathmatica计算可得


\begin{aligned}
\text{cdf}(\theta)&=\dfrac{\sin^2\theta}{\cos^2\theta(\alpha^2-1)+1}\\
&=\dfrac{1-\cos^2\theta}{\cos^2\theta(\alpha^2-1)+1} 
\end{aligned}

根据前面的概率知识,可以得到微表面的法线的球坐标$\theta$和$\phi$的采样函数为 $$\begin{split} \theta&=\text{cdf}^{-1}(\xi_\theta)=\arccos\left(\sqrt{\dfrac{1-\xi_\theta}{(\alpha^2-1)\xi_\theta+1}}\right) \ \phi&=2\pi\xi_\phi \end{split}$$ 然后即可计算出光线采样点的入射向量\vec{\omega_k}

3.3.2 代码

实际运算时,每个采样点的权重是不一样的,采用和宏法线的点积作为权重


\dfrac{1}{N}\sum_{k=1}^N L_i(\vec{\omega}_k)\approx\dfrac{1}{\sum_{k=1}^{N}(\vec{n}\cdot\vec{\omega}_k)}\sum_{k=1}^{N}{L_i(\vec{\omega}_k)(\vec{n}\cdot\vec{\omega}_k)}
// make the simplyfying assumption that V equals R equals the normal 
vec3 R = N;
vec3 V = R;

const uint SAMPLE_COUNT = 1024u;
vec3 prefilteredColor = vec3_splat(0.0);
float totalWeight = 0.0;

for(uint i = 0u; i < SAMPLE_COUNT; ++i)
{
    // generates a sample vector that's biased towards the preferred 
    //alignment direction (importance sampling).
    vec2 Xi = Hammersley(i, SAMPLE_COUNT);
    vec3 H = ImportanceSampleGGX(Xi, N, roughness);
    vec3 L  = normalize(2.0 * dot(V, H) * H - V);

    float NdotL = max(dot(N, L), 0.0);
    if(NdotL > 0.0)
    {
        prefilteredColor += texture2D(s_texSkybox, SampleSphericalMap(L)).rgb * NdotL;
        totalWeight      += NdotL;
    }
}

prefilteredColor = prefilteredColor / totalWeight;

3.3 BRDF部分

观察公式的第二个拆分项


\dfrac{1}{N}\sum_{k=1}^{N}\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})(\vec{\omega_k}\cdot\vec{n})}{\text{pdf}(\vec{\omega_k})}

其中 $$\begin{aligned} f_s(\vec{\omega}_k,\vec{\omega}_o)&=\dfrac{F(\vec{\omega}_k, \vec{\omega}_h)D(\vec{\omega}_h)G(\vec{\omega}_k, \vec{\omega}_o)}{4(\vec{\omega_k}\cdot\vec{n})(\vec{\omega_o}\cdot\vec{n})} \ \text{pdf}(\vec{\omega_k})&=\dfrac{\text{pdf}(\vec{\omega_h})}{4(\vec{\omega_h}\cdot\vec{\omega_o})}=\dfrac{D(\vec{\omega_h})(\vec{\omega_h}\cdot\vec{n})}{4(\vec{\omega_h}\cdot\vec{\omega_o})} \ F(\vec{\omega}_k, \vec{\omega}_h)&=F_0+(1-F_0)(1-\vec{\omega_h}\cdot\vec{\omega_o})^5 \end{aligned}


设$F_c=(1-\vec{\omega_h}\cdot\vec{\omega_o})^5$,那么

F(\vec{\omega}_k, \vec{\omega}_h)=F_0(1-F_c)+F_c


带入上面的式中,可得
$$\begin{split}
\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})(\vec{\omega_k}\cdot\vec{n})}{\text{pdf}(\vec{\omega_k})} &= F_0\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})}{F(\vec{\omega}_k, \vec{\omega}_h)\text{pdf}(\vec{\omega_k})}(1-F_c)(\vec{\omega_k}\cdot\vec{n})+\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})}{F(\vec{\omega}_k, \vec{\omega}_h)\text{pdf}(\vec{\omega_k})}F_c(\vec{\omega_k}\cdot\vec{n}) \\
&=F_0(1-F_c)\dfrac{G(\vec{\omega}_k, \vec{\omega}_o)(\vec{\omega_h}\cdot\vec{\omega_o})}{(\vec{\omega_h}\cdot\vec{n})(\vec{\omega_o}\cdot\vec{n})}+F_c\dfrac{G(\vec{\omega}_k, \vec{\omega}_o)(\vec{\omega_h}\cdot\vec{\omega_o})}{(\vec{\omega_h}\cdot\vec{n})(\vec{\omega_o}\cdot\vec{n})}
\end{split}$$
设

G_v=\dfrac{G(\vec{\omega}_k, \vec{\omega}_o)(\vec{\omega_h}\cdot\vec{\omega_o})}{(\vec{\omega_h}\cdot\vec{n})(\vec{\omega_o}\cdot\vec{n})}


那么可得

\dfrac{1}{N}\sum_{k=1}^{N}\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})(\vec{\omega_k}\cdot\vec{n})}{\text{pdf}(\vec{\omega_k})} = F_0\left(\dfrac{1}{N}\sum{(1-F_c)G_v}\right) + \dfrac{1}{N}\sum F_cG_v



## 3.4 BRDF查找贴图
上面的公式中,除了$F_0$和运行时的材质相关,其他数值都只取决于所使用的微表面的函数类型,所以可以预先计算在一张二维贴图中

![](./env_brdf.png)

在运行时可以使用查表得方式从这张贴图上获取数据

\dfrac{1}{N}\sum_{k=1}^{N}\dfrac{f_s(\vec{\omega_k}, \vec{\omega_o})(\vec{\omega_k}\cdot\vec{n})}{\text{pdf}(\vec{\omega_k})} =F_0*\text{Tex2D}(\vec{\omega_o}\cdot\vec{n}, \text{Roughness}).r + \text{Tex2D}(\vec{\omega_o}\cdot\vec{n}, \text{Roughness}).g



### 3.4.1 BRDF贴图的生成
```cpp :no-line-numbers
vec2 IntegrateBRDF(float NdotV, float roughness)
{
    vec3 V;
    V.x = sqrt(1.0 - NdotV*NdotV);
    V.y = 0.0;
    V.z = NdotV;

    float A = 0.0;
    float B = 0.0; 

    vec3 N = vec3(0.0, 0.0, 1.0);
    
    const uint SAMPLE_COUNT = 1024u;
    for(uint i = 0u; i < SAMPLE_COUNT; ++i)
    {
        // generates a sample vector that's biased towards the
        // preferred alignment direction (importance sampling).
        vec2 Xi = Hammersley(i, SAMPLE_COUNT);
        vec3 H = ImportanceSampleGGX(Xi, N, roughness);
        vec3 L = normalize(2.0 * dot(V, H) * H - V);

        float NdotL = max(L.z, 0.0);
        float NdotH = max(H.z, 0.0);
        float VdotH = max(dot(V, H), 0.0);

        if(NdotL > 0.0)
        {
            float G = GeometrySmith(N, V, L, roughness);
            float G_Vis = (G * VdotH) / (NdotH * NdotV);
            float Fc = pow(1.0 - VdotH, 5.0);

            A += (1.0 - Fc) * G_Vis;
            B += Fc * G_Vis;
        }
    }
    A /= float(SAMPLE_COUNT);
    B /= float(SAMPLE_COUNT);
    return vec2(A, B);
}

```