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

一、和大气球体相交

如前所述,我们计算大气层穿过的分段的光学深度的唯一方法是通过数值积分。这意味着将区间分成长度为 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;
相关推荐
前端Hardy16 分钟前
HTML&CSS:有趣的漂流瓶
前端·javascript·css
前端Hardy17 分钟前
HTML&CSS :惊艳 UI 必备!卡片堆叠动画
前端·javascript·css
无羡仙41 分钟前
替代 Object.freeze 的精准只读模式
前端·javascript
小菜全1 小时前
uniapp新增页面及跳转配置方法
开发语言·前端·javascript·vue.js·前端框架
白水清风2 小时前
关于Js和Ts中类(class)的知识
前端·javascript·面试
前端Hardy2 小时前
只用2行CSS实现响应式布局,比媒体查询更优雅的布局方案
javascript·css·html
车口2 小时前
滚动加载更多内容的通用解决方案
javascript
艾小码2 小时前
手把手教你实现一个EventEmitter,彻底告别复杂事件管理!
前端·javascript·node.js
Jedi Hongbin5 小时前
Three.js shader内置矩阵注入
前端·javascript·three.js
掘金安东尼6 小时前
Node.js 如何在 2025 年挤压 I/O 性能
前端·javascript·github