路径追踪中的纹理过滤

问题引出

最近在做基于WebGL的路径追踪时,遇到了一个法线(凹凸)贴图的问题,如下图,凹凸效果走样特别严重。通过问题分析,目前渲染器还缺少对不同纹理过滤类型的实现,今天刚好完成了相关内容,趁热将其记录下来。

小球凹凸效果的问题

小球细节

其他渲染器的效果

原因分析

本文示例里小球的凹凸贴图如下图。通过凹凸贴图计算法线的过程如下:

  1. 通过对贴图采样得到点 P P P的高度 h p h_p hp;

  2. 分别右移和上移一个微小的距离得到 h p + Δ x h_{p+\Delta{x}} hp+Δx、 h p + Δ y h_{p+\Delta{y}} hp+Δy;

  3. 最终的法线可以表示为:

    N ′ = N + α ( h p + Δ x − h p ) T + α ( h p + Δ y − h p ) ( N × T ) \mathbf{N}^{'} = \mathbf{N} + \alpha(h_{p+\Delta{x}}-h_p)\mathbf{T} + \alpha(h_{p+\Delta{y}}-h_p)(\mathbf{N}\times\mathbf{T}) N′=N+α(hp+Δx−hp)T+α(hp+Δy−hp)(N×T)

    别忘了归一化: N ′ ′ = N ′ ∥ N ′ ∥ \mathbf{N}^{''} = \tfrac{\mathbf{N}^{'}}{\begin{Vmatrix} \mathbf{N}^{'}\end{Vmatrix}} N′′=∥ ∥N′∥ ∥N′

    上式中 α \alpha α为凹凸强度, T \mathbf{T} T为切向向量。

以上过程,最重要的便是 Δ x \Delta{x} Δx和 Δ y \Delta{y} Δy的选取。已知的是在屏幕空间水平和竖直方向的偏移分别为 1 / r e s o l u t i o n x 1/resolution_x 1/resolutionx和 1 / r e s o l u t i o n y 1/resolution_y 1/resolutiony, r e s o l u t i o n resolution resolution为渲染屏幕的分辨率,我们需要通过屏幕空间中的偏移去求每个物体在其所在的UV空间的偏移 Δ x \Delta{x} Δx和 Δ y \Delta{y} Δy,最后采样求得最终的法线。显然 Δ x \Delta{x} Δx和 Δ y \Delta{y} Δy还与视角有关,即当物体离摄像机较近时,偏移很小,而距离增加时,偏移增大。

小球凹凸贴图( 1500 ∗ 1500 1500*1500 1500∗1500)

对于上述的根据视角自适应采样的方法如何实现?这篇文章详细的介绍了纹理采样,其中的基于MipMap的三线性过滤可以满足我们的要求。在我们使用光栅化渲染,当设置纹理的TEXTURE_MIN_FILTER或TEXTURE_MAG_FILTER为LINEAR_MIPMAP_LINEAR时,OpenGL/WebGL会自动的根据当前像素在UV上的变化率选取合适的MipMap,这是已经集成在硬件上的功能。

解决方案一

基于上述的分析,我们知道光栅化的时候,可以直接利用纹理过滤选项,让硬件帮我们完成最佳的采样。对于凹凸贴图,我们可以直接使用内置的微分函数 d F d x dFdx dFdx、 d F d y dFdy dFdy:

glsl 复制代码
float hp = texture2D(bump, uv).r;
float hpdx = texture2D(bump, uv + dFdx(uv)).r;
float hpdy = texture2D(bump, uv + dFdy(uv)),r;

我们怎么将上面的光栅化应用到光线追踪呢?我们可以把法线的结果通过光栅化预计算到FrameBuffer,将计算结果传入光追的Shader,通过坐标变换求得屏幕坐标,采样即可得到法线结果,下图分别为光栅化得到的法线以及最终渲染结果:

但,这种方案有哪些问题呢?

从这种方案的原理出发,很显然,可以预见它有如下的一些问题:

  1. 仅对摄像机视角内的像素点有效,且无法得到被遮挡的物体的法线结果;
  2. 折射/反射后失真;
  3. 视角转动需重新渲染法线结果的FrameBuffer;
  4. 光线追踪每个像素都会使用低差异序列的抗锯齿采样,而光栅化并无此特性,造成两者实际的渲染点不一致,容易引起物体边缘的不连续。

基于上述问题,引出本文的重点:光线微分法。

光线微分法

感兴趣的朋友可以搜索原论文:《Tracing ray differentials.》Igehy, H.本文结合这篇文章以及实际工程中的一些问题来介绍这个算法。 对于任一射线 R → \overrightarrow {R} R 可以表示为:

R → = ⟨ P , D ⟩ \overrightarrow {R} = \lang \mathbf{P}, \mathbf{D}\rang R =⟨P,D⟩

P \mathbf{P} P为射线的起点, D \mathbf{D} D为射线的方向向量。Ray Tracing的第一次求交时起点为相机的位置,求方向时将屏幕坐标考虑进来,令:

d ( x , y ) = V i e w + x R i g h t + y U p \mathbf{d}(x,y) = \mathbf{View} + x\mathbf{Right} + y\mathbf{Up} d(x,y)=View+xRight+yUp

V i e w \mathbf{View} View为相机的朝向, R i g h t \mathbf{Right} Right为相机的 x x x轴方向向量, U p \mathbf{Up} Up为相机的 y y y轴方向向量,因此:

D = d ∥ d ∥ = d ( d ⋅ d ) 1 / 2 \mathbf{D} = \tfrac{\mathbf{d}}{\begin{Vmatrix} \mathbf{d}\end{Vmatrix}} = \tfrac{\mathbf{d}}{(\mathbf{d}\cdot\mathbf{d})^{1/2}} D=∥d∥d=(d⋅d)1/2d

初始化时:

∂ P ∂ x = 0 \tfrac{\partial\mathbf{P}}{\partial{x}}=0 ∂x∂P=0

∂ D ∂ x = ∂ ( d ( d ⋅ d ) 1 / 2 ) ∂ x = ( d ⋅ d ) R i g h t − ( d ⋅ R i g h t ) d ( d ⋅ d ) 3 / 2 \tfrac{\partial\mathbf{D}}{\partial{x}}=\tfrac{\partial({\tfrac{\mathbf{d}}{(\mathbf{d}\cdot\mathbf{d})^{1/2}})}}{\partial{x}}= \tfrac{(\mathbf{d}\cdot\mathbf{d})\mathbf{Right}-(\mathbf{d}\cdot\mathbf{Right})\mathbf{d}}{(\mathbf{d}\cdot\mathbf{d})^{3/2}} ∂x∂D=∂x∂((d⋅d)1/2d)=(d⋅d)3/2(d⋅d)Right−(d⋅Right)d

∂ D ∂ y \tfrac{\partial\mathbf{D}}{\partial{y}} ∂y∂D的求解方法与 ∂ D ∂ x \tfrac{\partial\mathbf{D}}{\partial{x}} ∂x∂D类似,本文不再列出。

当光线沿着方向 D \mathbf{D} D传播时,直到与某点相交时,得到交点 P ′ \mathbf{P}^{'} P′: P ′ = P + t D \mathbf{P}^{'}=\mathbf{P} + t\mathbf{D} P′=P+tD,求微分,得:

∂ P ′ ∂ x = ∂ P ∂ x + t ∂ D ∂ x + ∂ t ∂ x D \tfrac{\partial\mathbf{P}^{'}}{\partial{x}}=\tfrac{\partial\mathbf{P}}{\partial{x}}+t\tfrac{\partial\mathbf{D}}{\partial{x}}+\tfrac{\partial{t}}{\partial{x}}\mathbf{D} ∂x∂P′=∂x∂P+t∂x∂D+∂x∂tD

上式中的 ∂ D ∂ x \tfrac{\partial\mathbf{D}}{\partial{x}} ∂x∂D和前一步的 ∂ D ∂ x \tfrac{\partial\mathbf{D}}{\partial{x}} ∂x∂D一致,因为射线直线传播时方向不变,那么如何求 ∂ t ∂ x \tfrac{\partial{t}}{\partial{x}} ∂x∂t?

如上图,射线从点 P P P出发,沿方向 D \mathbf{D} D传播,与 △ A B C \triangle{ABC} △ABC相交于点 P ′ P{'} P′。设 △ A B C \triangle{ABC} △ABC的平面方程为 A x + B y + C z = d Ax+By+Cz=d Ax+By+Cz=d,则其法线 N = A , B , C \mathbf{N}=A,B,C N=A,B,C,法线方向由三角形确定,与 x x x、 y y y不相关。由几何关系得到:

( P − P ′ ) ⋅ N = − t N ⋅ D \mathbf{(P-P')\cdot\mathbf{N}} = -t\mathbf{N}\cdot\mathbf{D} (P−P′)⋅N=−tN⋅D

⇒ t = − P ⋅ N N ⋅ D + d N ⋅ D \Rightarrow t = -\tfrac{\mathbf{P\cdot\mathbf{N}}}{\mathbf{N}\cdot\mathbf{D}}+\tfrac{d}{\mathbf{N}\cdot\mathbf{D}} ⇒t=−N⋅DP⋅N+N⋅Dd

t t t求微分,可得:

∂ t ∂ x = − ( ∂ P ∂ x + ∂ D ∂ x ) ⋅ N N ⋅ D − d ⋅ ( N ⋅ ∂ D ∂ x ) ( N ⋅ D ) 2 \tfrac{\partial{t}}{\partial{x}}=-\tfrac{(\tfrac{\partial{\mathbf{P}}}{\partial{x}}+\tfrac{\partial{\mathbf{D}}}{\partial{x}})\cdot\mathbf{N}}{\mathbf{N}\cdot\mathbf{D}}-\tfrac{d\cdot(\mathbf{N}\cdot\tfrac{\partial{\mathbf{D}}}{\partial{x}})}{(\mathbf{N\cdot{D}})^2} ∂x∂t=−N⋅D(∂x∂P+∂x∂D)⋅N−(N⋅D)2d⋅(N⋅∂x∂D)

接下来我们来分析 P ′ P{'} P′处的UV坐标。已知 P ′ P{'} P′的UV、法线、顶点坐标均为点 A 、 B 、 C A、B、C A、B、C三个顶点内差所得,令点 A 、 B 、 C A、B、C A、B、C处的占比分别为 α 、 β 、 γ \alpha、\beta、\gamma α、β、γ,则满足以下条件:

α + β + γ = 1 \alpha+\beta+\gamma=1 α+β+γ=1

α A + β B + γ C = P ′ \alpha\mathbf{A}+\beta\mathbf{B}+\gamma\mathbf{C}=\mathbf{P{}'} αA+βB+γC=P′

A x A y A z B x B y B z C x C y C z 1 1 1 α β γ = P ′ x P ′ y P ′ z 1 \Rightarrow \left \\begin{array}{ccc} \\mathbf{A}_x \& \\mathbf{A}_y \& \\mathbf{A}_z \\\\ \\mathbf{B}_x \& \\mathbf{B}_y \& \\mathbf{B}_z \\\\ \\mathbf{C}_x \& \\mathbf{C}_y \& \\mathbf{C}_z \\\\ 1 \& 1 \& 1 \\end{array} \\right\left \\begin{array}{ccc} \\alpha \\\\ \\beta \\\\ \\gamma \\end{array} \\right=\left \\begin{array}{ccc} \\mathbf{P{'}}_x \\\\ \\mathbf{P{'}}_y \\\\ \\mathbf{P{'}}_z \\\\ 1 \\end{array} \\right ⇒⎣ ⎡AxBxCx1AyByCy1AzBzCz1⎦ ⎤⎣ ⎡αβγ⎦ ⎤=⎣ ⎡P′xP′yP′z1⎦ ⎤

假定当前的交点是满足上述内差条件的,则可得:

A x A y A z B x B y B z C x C y C z α β γ = P ′ x P ′ y P ′ z \left \\begin{array}{ccc} \\mathbf{A}_x \& \\mathbf{A}_y \& \\mathbf{A}_z \\\\ \\mathbf{B}_x \& \\mathbf{B}_y \& \\mathbf{B}_z \\\\ \\mathbf{C}_x \& \\mathbf{C}_y \& \\mathbf{C}_z \\end{array} \\right\left \\begin{array}{ccc} \\alpha \\\\ \\beta \\\\ \\gamma \\end{array} \\right=\left \\begin{array}{ccc} \\mathbf{P{'}}_x \\\\ \\mathbf{P{'}}_y \\\\ \\mathbf{P{'}}_z \\end{array} \\right ⎣ ⎡AxBxCxAyByCyAzBzCz⎦ ⎤⎣ ⎡αβγ⎦ ⎤=⎣ ⎡P′xP′yP′z⎦ ⎤

α + β + γ = 1 \alpha+\beta+\gamma=1 α+β+γ=1。

M = A x A y A z B x B y B z C x C y C z \mathbf{M}=\left \\begin{array}{ccc} \\mathbf{A}_x \& \\mathbf{A}_y \& \\mathbf{A}_z \\\\ \\mathbf{B}_x \& \\mathbf{B}_y \& \\mathbf{B}_z \\\\ \\mathbf{C}_x \& \\mathbf{C}_y \& \\mathbf{C}_z \\end{array} \\right M=⎣ ⎡AxBxCxAyByCyAzBzCz⎦ ⎤,若 M \mathbf{M} M可逆,可得:

α β γ = M − 1 P ′ x P ′ y P ′ z \left \\begin{array}{ccc} \\alpha \\\\ \\beta \\\\ \\gamma \\end{array} \\right=\mathbf{M}^{-1}\left \\begin{array}{ccc} \\mathbf{P{'}}_x \\\\ \\mathbf{P{'}}_y \\\\ \\mathbf{P{'}}_z \\end{array} \\right ⎣ ⎡αβγ⎦ ⎤=M−1⎣ ⎡P′xP′yP′z⎦ ⎤

由此我们得到了内差系数 α 、 β 、 γ \alpha、\beta、\gamma α、β、γ和内差结果的变换关系。然而上述等式成立的条件是变换矩阵可逆,这个条件有的时候可能不满足,比如所有顶点都在 x y xy xy平面上,此时所有点的 z z z轴分量为0,矩阵不可逆。

为了解决这个问题,笔者构造了一个新的空间,使得该空间的 x x x轴为三角形其中一边, z z z轴为与三角形所在平面的法线和新的 x x x轴都成45度的向量, y y y轴即为两者的正交向量,令新空间的变换矩阵为 M 1 \mathbf{M_1} M1,则变换后的顶点 A ′ 、 B ′ 、 C ′ \mathbf{A'}、\mathbf{B'}、\mathbf{C'} A′、B′、C′分别为:

A ′ = M 1 A \mathbf{A'}=\mathbf{M_1}\mathbf{A} A′=M1A

B ′ = M 1 B \mathbf{B'}=\mathbf{M_1}\mathbf{B} B′=M1B

C ′ = M 1 C \mathbf{C'}=\mathbf{M_1}\mathbf{C} C′=M1C

A ′ 、 B ′ 、 C ′ \mathbf{A'}、\mathbf{B'}、\mathbf{C'} A′、B′、C′依旧满足:

A ′ x A ′ y A ′ z B ′ x B ′ y B ′ z C ′ x C ′ y C ′ z α β γ = M 1 P ′ x P ′ y P ′ z \left \\begin{array}{ccc} \\mathbf{A'}_x \& \\mathbf{A'}_y \& \\mathbf{A'}_z \\\\ \\mathbf{B'}_x \& \\mathbf{B'}_y \& \\mathbf{B'}_z \\\\ \\mathbf{C'}_x \& \\mathbf{C'}_y \& \\mathbf{C'}_z \\end{array} \\right\left \\begin{array}{ccc} \\alpha \\\\ \\beta \\\\ \\gamma \\end{array} \\right=\mathbf{M_1}\left \\begin{array}{ccc} \\mathbf{P{'}}_x \\\\ \\mathbf{P{'}}_y \\\\ \\mathbf{P{'}}_z \\end{array} \\right ⎣ ⎡A′xB′xC′xA′yB′yC′yA′zB′zC′z⎦ ⎤⎣ ⎡αβγ⎦ ⎤=M1⎣ ⎡P′xP′yP′z⎦ ⎤

M 2 = A ′ x A ′ y A ′ z B ′ x B ′ y B ′ z C ′ x C ′ y C ′ z \mathbf{M_2}=\left \\begin{array}{ccc} \\mathbf{A'}_x \& \\mathbf{A'}_y \& \\mathbf{A'}_z \\\\ \\mathbf{B'}_x \& \\mathbf{B'}_y \& \\mathbf{B'}_z \\\\ \\mathbf{C'}_x \& \\mathbf{C'}_y \& \\mathbf{C'}_z \\end{array} \\right M2=⎣ ⎡A′xB′xC′xA′yB′yC′yA′zB′zC′z⎦ ⎤,则:

α β γ = M 2 − 1 M 1 P ′ x P ′ y P ′ z \left \\begin{array}{ccc} \\alpha \\\\ \\beta \\\\ \\gamma \\end{array} \\right=\mathbf{M_2^{-1}}\mathbf{M_1}\left \\begin{array}{ccc} \\mathbf{P{'}}_x \\\\ \\mathbf{P{'}}_y \\\\ \\mathbf{P{'}}_z \\end{array} \\right ⎣ ⎡αβγ⎦ ⎤=M2−1M1⎣ ⎡P′xP′yP′z⎦ ⎤

对于 P ′ P{'} P′的UV坐标 S ′ = u ′ v ′ 1 \mathbf{S^{'}}=\left \\begin{array}{ccc} u\^{'} \\\\ v\^{'} \\\\ 1 \\end{array} \\right S′=⎣ ⎡u′v′1⎦ ⎤,依然满足内插规则:

α S a + β S b + γ S c = S ′ \alpha\mathbf{S_a}+\beta\mathbf{S_b}+\gamma\mathbf{S_c}=\mathbf{S'} αSa+βSb+γSc=S′

M = M 2 − 1 M 1 M=\mathbf{M_2^{-1}}\mathbf{M_1} M=M2−1M1,对 x x x求微分:

∂ S ′ ∂ x = ∂ α ∂ x S a + ∂ β ∂ x S b + ∂ γ ∂ x S c \tfrac{\partial{\mathbf{S'}}}{\partial{x}}=\tfrac{\partial{\alpha}}{\partial{x}}\mathbf{S}_a+\tfrac{\partial{\beta}}{\partial{x}}\mathbf{S}_b+\tfrac{\partial{\gamma}}{\partial{x}}\mathbf{S}_c ∂x∂S′=∂x∂αSa+∂x∂βSb+∂x∂γSc

∂ S ′ ∂ x = M 0 ∂ P ′ ∂ x S a + M 1 ∂ P ′ ∂ x S b + M 2 ∂ P ′ ∂ x S c \tfrac{\partial{\mathbf{S'}}}{\partial{x}}=\mathbf{M}_{0}\tfrac{\partial{\mathbf{P'}}}{\partial{x}}\mathbf{S}a+\mathbf{M}{1}\tfrac{\partial{\mathbf{P'}}}{\partial{x}}\mathbf{S}b+\mathbf{M}{2}\tfrac{\partial{\mathbf{P'}}}{\partial{x}}\mathbf{S}_c ∂x∂S′=M0∂x∂P′Sa+M1∂x∂P′Sb+M2∂x∂P′Sc

M i \mathbf{M}_{i} Mi M \mathbf{M} M的第 i i i行向量。

下两张图分别为使用光线微分和光栅化计算得到的 ∂ S ′ ∂ x \tfrac{\partial{\mathbf{S'}}}{\partial{x}} ∂x∂S′,为了显示更明显,将其值放大了10倍。

现在我们得到了 ∂ S ′ ∂ x \tfrac{\partial{\mathbf{S'}}}{\partial{x}} ∂x∂S′,对纹理进行采样时需要利用相关数据计算MipMap的等级。

渲染点 P ′ P{'} P′与其向右和向上一个像素点对应的UV差值为:

Δ T x ≈ Δ x ∂ S ′ ∂ x \Delta\mathbf{T}_x\approx\Delta{x}\tfrac{\partial{\mathbf{S'}}}{\partial{x}} ΔTx≈Δx∂x∂S′

Δ T y ≈ Δ y ∂ S ′ ∂ y \Delta\mathbf{T}_y\approx\Delta{y}\tfrac{\partial{\mathbf{S'}}}{\partial{y}} ΔTy≈Δy∂y∂S′

MipMap的等级 l o d lod lod可以表示为:

l o d = 0.5 l o g 2 m a x ( Δ x ⋅ Δ x , Δ y ⋅ Δ y ) lod=0.5log_2max(\\Delta{x}\\cdot\\Delta{x},\\Delta{y}\\cdot\\Delta{y}) lod=0.5log2max(Δx⋅Δx,Δy⋅Δy)

计算出了 l o d lod lod的值,我们需要对纹理进行三线性插值计算:

插值计算的两级MipMap分别为:

l o d s u b = f l o o r ( l o d ) lod_{sub} = floor(lod) lodsub=floor(lod)

l o d u p = f l o o r ( l o d ) + 1 lod_{up} = floor(lod)+1 lodup=floor(lod)+1

F u p = l o d − l o d s u b F_{up} = lod - lod_{sub} Fup=lod−lodsub

分别采样 l o d s u b lod_{sub} lodsub和 l o d u p lod_{up} lodup两个等级的结果,再将两者进行线性插值,其中 l o d u p lod_{up} lodup的占比为 F u p F_{up} Fup。

使用光线微分后,渲染的结果如下:

总结

要做出高质量的光线追踪渲染,在做纹理采样时需要应用纹理过滤,光线追踪时由于无法使用诸如 d F d x dFdx dFdx的函数,需要根据射线的表达式手动计算微分,而本文所用的光线微分便为其中一种方法。需要注意的是,本文仅对光线直线传播时进行了分析,当光线发生折射和反射时,光线的方向发生了变化,还需要将 ∂ N ∂ x \tfrac{\partial{\mathbf{N}}}{\partial{x}} ∂x∂N和 ∂ N ∂ y \tfrac{\partial{\mathbf{N}}}{\partial{y}} ∂y∂N考虑进来,感兴趣的读者可以阅读上面的Paper,这部分的内容我也将在近期分享。

参考

《Tracing ray differentials.》Igehy, H. (1999). SIGGRAPH '99 Proceedings

相关推荐
山河木马2 天前
矩阵专题3-怎么创建投影矩阵(uProjectionMatrix)
javascript·webgl·计算机图形学
山河木马3 天前
矩阵专题2-怎么创建视图矩阵(uViewMatrix)
javascript·webgl·计算机图形学
山河木马8 天前
矩阵专题1-怎么创建模型矩阵(uModelMatrix)
javascript·webgl·计算机图形学
山河木马9 天前
矩阵专题0-webGL中的矩阵
javascript·webgl·计算机图形学
一颗烂土豆13 天前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
数据知道22 天前
视觉伪装(下):WebGL 渲染器与厂商特征的底层伪造与屏蔽
javascript·数据采集·webgl·指纹浏览器
niconicoC22 天前
让 Three.js 场景更真实:我用高斯泼溅和 SparkJS 做了一个可交互的 3D Demo
前端·webgl
sinat_3845031123 天前
【无标题】
unity·webgl
山河木马1 个月前
无框架-原生webGL渲染-底层入门-1
前端·javascript·webgl
拾忆丶夜1 个月前
unity webgl 阴影条纹问题
unity·游戏引擎·webgl