大气散射(六)和大气球体相交

一、和大气球体相交

如前所述,我们计算大气层穿过的分段的光学深度的唯一方法是通过数值积分。这意味着将区间分成长度为 ds 的较小段,并假设每个段的密度是恒定的,然后计算每个段的光学深度。

如上图所示, <math xmlns="http://www.w3.org/1998/Math/MathML"> A B ‾ \overline{AB} </math>AB 的光学深度使用 4 个样本计算,每个样本仅考虑段本身中心的密度。

显然,第一步是找到点 A 和点 B。如果我们假设我们渲染的是一个球体,Unity 将尝试渲染其表面。屏幕上的每个像素对应于球体上的一个点。在下面的图中,该点称为 O,表示原点。在表面着色器中,O 对应于 Input 结构中的 worldPos 变量。着色器的处理到此为止;我们唯一可用的信息是 O、指示视图射线方向的方向 D,以及以 C 为中心、半径为 R 的大气球体。挑战在于计算 A 和 B。最快的方法是使用几何方法,将问题简化为找到大气球体和来自摄像机的视图射线之间的交点。

首先,我们应该注意到 O、A 和 B 都位于视图射线上。这意味着我们可以将它们的位置表示为不是三维空间中的点,而是从原点开始的视图射线上的距离。虽然 A 是实际的点(在着色器中表示为 float3),但 AO 是其到原点 O 的距离(作为浮点数)。A 和 AO 都是指示同一点的两种有效方式,有:

<math xmlns="http://www.w3.org/1998/Math/MathML"> A = O + A O ‾   D A = O + \overline{AO}\,D </math>A=O+AOD

<math xmlns="http://www.w3.org/1998/Math/MathML"> B = O + B O ‾   D B = O + \overline{BO}\,D </math>B=O+BOD

其中上方的标记 <math xmlns="http://www.w3.org/1998/Math/MathML"> X Y ‾ \overline{XY} </math>XY 表示任意点 X 和 Y 之间的段的长度。

出于效率原因,在着色器代码中,我们将使用 AO 和 BO,并从 OT 计算它们:

<math xmlns="http://www.w3.org/1998/Math/MathML"> A O ‾ = O T ‾ − A T ‾ \overline{AO} = \overline{OT} - \overline{AT} </math>AO=OT−AT

<math xmlns="http://www.w3.org/1998/Math/MathML"> B O ‾ = O T ‾ + B T ‾ \overline{BO} = \overline{OT} + \overline{BT} </math>BO=OT+BT

我们还应该注意,段 <math xmlns="http://www.w3.org/1998/Math/MathML"> A T ‾ \overline{AT} </math>AT 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> B T ‾ \overline{BT} </math>BT 的长度相同。现在,我们需要找到交点的是计算 <math xmlns="http://www.w3.org/1998/Math/MathML"> A O ‾ \overline{AO} </math>AO 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> A T ‾ \overline{AT} </math>AT。

段 <math xmlns="http://www.w3.org/1998/Math/MathML"> O T ‾ \overline{OT} </math>OT 最容易计算。如果我们观察上图,可以看到 <math xmlns="http://www.w3.org/1998/Math/MathML"> O T ‾ \overline{OT} </math>OT 是向量 CO 投影到视图射线上。从数学上讲,这种投影可以使用点积完成。如果你熟悉着色器,可能会知道点积是衡量两个方向"对齐程度"的一种方法。当它应用于两个向量并且第二个向量的长度为单位时,它变成了一个投影运算符:

<math xmlns="http://www.w3.org/1998/Math/MathML"> O T ‾ = ( C − O ) ⋅ D \overline{OT} = \left(C-O\right) \cdot D </math>OT=(C−O)⋅D

应该注意, <math xmlns="http://www.w3.org/1998/Math/MathML"> ( C − O ) \left(C-O\right) </math>(C−O) 是一个三维向量,而不是 C 和 O 之间的段的长度。

接下来,我们需要计算段 <math xmlns="http://www.w3.org/1998/Math/MathML"> A T ‾ \overline{AT} </math>AT 的长度。这可以使用勾股定理在三角形 <math xmlns="http://www.w3.org/1998/Math/MathML"> A C T △ \overset{\triangle}{ACT} </math>ACT△上计算。事实上,有:

<math xmlns="http://www.w3.org/1998/Math/MathML"> R 2 = A T ‾ 2 + C T ‾ R^2 = \overline{AT}^2 + \overline{CT} </math>R2=AT2+CT

这意味着:

<math xmlns="http://www.w3.org/1998/Math/MathML"> A T ‾ = R 2 − C T ‾ \overline{AT} = \sqrt{R^2 - \overline{CT}} </math>AT=R2−CT

段 <math xmlns="http://www.w3.org/1998/Math/MathML"> C T ‾ \overline{CT} </math>CT 的长度仍然未知。然而,它可以再次应用勾股定理在三角形 <math xmlns="http://www.w3.org/1998/Math/MathML"> O C T △ \overset{\triangle}{OCT} </math>OCT△ 上计算:

<math xmlns="http://www.w3.org/1998/Math/MathML"> C O ‾ 2 = O T ‾ 2 + C T ‾ 2 \overline{CO}^2 = \overline{OT}^2 + \overline{CT}^2 </math>CO2=OT2+CT2

<math xmlns="http://www.w3.org/1998/Math/MathML"> C T ‾ = C O ‾ 2 − O T ‾ 2 \overline{CT} = \sqrt{\overline{CO}^2 - \overline{OT}^2} </math>CT=CO2−OT2

现在我们已经获得了所有所需的量。总结一下:

<math xmlns="http://www.w3.org/1998/Math/MathML"> O T ‾ = ( C − O ) ⋅ D \overline{OT} = \left( C - O \right) \cdot D </math>OT=(C−O)⋅D

<math xmlns="http://www.w3.org/1998/Math/MathML"> C T ‾ = C O ‾ 2 − O T ‾ 2 \overline{CT} = \sqrt{\overline{CO}^2 - \overline{OT}^2} </math>CT=CO2−OT2

<math xmlns="http://www.w3.org/1998/Math/MathML"> A T ‾ = R 2 − C T ‾ 2 \overline{AT} = \sqrt{R^2 - \overline{CT}^2} </math>AT=R2−CT2

<math xmlns="http://www.w3.org/1998/Math/MathML"> A O ‾ = O T ‾ − A T ‾ \overline{AO} = \overline{OT} - \overline{AT} </math>AO=OT−AT

<math xmlns="http://www.w3.org/1998/Math/MathML"> B O ‾ = O T ‾ + A T ‾ \overline{BO} = \overline{OT} + \overline{AT} </math>BO=OT+AT

这组方程包含平方根。它们仅在非负数上定义。如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> R 2 > C T ‾ 2 R^2 > \overline{CT}^2 </math>R2>CT2,那么就没有解,这意味着视图射线与球体不相交。

我们可以将此转换为以下 Cg 函数:

c# 复制代码
bool rayIntersect
(
    // 射线
    float3 O, // 原点
    float3 D, // 方向
    // 球体
    float3 C, // 中心
    float R,    // 半径
    out float AO, // 第一个交点的时间
    out float BO  // 第二个交点的时间
)
{
    float3 L = C - O;
    float DT = dot (L, D);
    float R2 = R * R;
    float CT2 = dot(L,L) - DT*DT;
    
    // 交点在圆外部
    if (CT2 > R2)
        return false;
    float AT = sqrt(R2 - CT2);
    float BT = AT;
    AO = DT - AT;
    BO = DT + BT;
    return true;
}

这不是一个单一的值,而是三个要返回的值: <math xmlns="http://www.w3.org/1998/Math/MathML"> A O ‾ \overline{AO} </math>AO、 <math xmlns="http://www.w3.org/1998/Math/MathML"> B O ‾ \overline{BO} </math>BO 以及是否存在交点。这两个段的长度使用 out 关键字返回,该关键字使函数对这些参数的任何更改在其终止后保持持久性。

二、行星碰撞

需要额外考虑的问题是,某些视线射线会击中行星,因此它们穿过大气层的旅程会提前终止。一种方法是重新审查上面提到的推导。

一种更简单但效率较低的方法是运行rayIntersect两次,然后根据需要调整结束点。

这段代码的意思是:

c# 复制代码
// 与大气层球体的交点
float tA;    // 大气层进入点(worldPos + V * tA)
float tB;    // 大气层退出点(worldPos + V * tB)
if (!rayIntersect(O, D, _PlanetCentre, _AtmosphereRadius, tA, tB))
    return fixed4(0,0,0,0); // 视线射线朝向深空
// 射线是否穿过行星核心?
float pA, pB;
if (rayIntersect(O, D, _PlanetCentre, _PlanetRadius, pA, pB))
    tB = pA;
相关推荐
神夜大侠9 分钟前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱11 分钟前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js
柯南二号44 分钟前
HarmonyOS ArkTS 下拉列表组件
前端·javascript·数据库·harmonyos·arkts
究极无敌暴龙战神X1 小时前
前端学习之ES6+
开发语言·javascript·ecmascript
明辉光焱1 小时前
【ES6】ES6中,如何实现桥接模式?
前端·javascript·es6·桥接模式
nameofworld2 小时前
前端面试笔试(二)
前端·javascript·面试·学习方法·数组去重
hummhumm2 小时前
第 12 章 - Go语言 方法
java·开发语言·javascript·后端·python·sql·golang
hummhumm2 小时前
第 8 章 - Go语言 数组与切片
java·开发语言·javascript·python·sql·golang·database
zhanghaisong_20153 小时前
Caused by: org.attoparser.ParseException:
前端·javascript·html·thymeleaf
南城夏季4 小时前
蓝领招聘二期笔记
前端·javascript·笔记