链接
论文引用
@inproceedings {NU-NeRF,
author = {Jia-Mu Sun and Tong Wu and Ling-qI Yan and Lin Gao},
title = {NU-NeRF: Neural Reconstruction of Nested Transparent Objects with Uncontrolled Capture Environment},
journal = {ACM Transactions on Graphics(ACM SIGGRAPH Asia 2024)},
year = {2024}
}
部分论文翻译及解释
翻译
4.1 零厚度界面
在由固体透明材料(例如玻璃)制成的物体中,光线穿过界面时会发生反射和折射,入射能量的分布遵循菲涅尔方程(见图5(a))。在这种情况下,光线只与界面发生一次相互作用。然而,在现实生活中,光线通常会经历两次相互作用,例如在透明容器的表面。我们首先描述一个简单的情况:界面非常薄。因此,光线折射进入界面后会立即射出。由于在这个过程中发生了两次连续的相互作用,菲涅尔项需要应用两次。由于材料非常薄,可以认为两次相互作用发生在同一位置,并且这两点的表面法线相同(见图5(b))。然后,我们用两个独立的折射率(IoRs)$\eta_l$和$\eta_r$来描述这种类型的界面。$\eta_l$是界面材料的折射率,用于计算镜面反射颜色;$\eta_r$是内部物质材料的折射率,用于计算折射方向。例如,对于一个没有水的塑料瓶,$\eta_l = 1.5$,$\eta_r = 1.0$。作为一种特殊情况,具有单次相互作用的固体物体可以通过使用两个相同的折射率来简化实现。
解释
1. 零厚度界面的背景
- 在透明材料(如玻璃)中,光线穿过界面时会发生反射和折射。这种现象可以用菲涅尔方程来描述,即入射能量在反射和折射之间分配(见图5(a))。
- 在理想情况下,光线与界面只发生一次相互作用。但在实际场景中,例如透明容器的表面,光线可能会经历两次相互作用:一次是进入界面,一次是离开界面。
2. 薄界面的处理方法
- 当界面非常薄时,光线进入界面后会立即射出。这种情况下,虽然光线经历了两次相互作用(进入和离开),但可以近似认为两次相互作用发生在同一位置,且这两点的法线相同(见图5(b))。
- 因此,菲涅尔项需要应用两次,以分别处理进入和离开界面的光线。
3. 折射率的定义
- 为了描述这种薄界面,作者引入了两个独立的折射率(Index of Refraction, IoR):
- $\eta_l$:界面材料的折射率,用于计算镜面反射颜色(specular color)。
- $\eta_r$:内部物质的折射率,用于计算折射方向。
- 例如,对于一个空的塑料瓶,界面材料的折射率 $\eta_l = 1.5$,而内部空气的折射率 $\eta_r = 1.0$。
4. 特殊情况的简化
- 对于只有一次相互作用的固体物体(例如不透明物体),可以使用两个相同的折射率来简化实现。这是因为在这种情况下,光线只与界面发生一次相互作用,因此不需要区分进入和离开的折射率。
5. 总结
- 这段内容主要讨论了如何在渲染中处理零厚度界面的问题。通过引入两个独立的折射率,可以更准确地模拟光线在薄界面中的行为,同时简化了实现过程。这种处理方法特别适用于透明容器等场景,能够更好地模拟光线的反射和折射效果。
翻译
4.2 非零厚度界面
从零厚度界面扩展到非零厚度界面是自然的。我们额外引入了一个参数 $h$ 来模拟界面的厚度。由于厚度不能被忽略,光线的入射位置和出射位置不再相同,且这两点的法线也不同。为了准确捕捉这种效应,我们使用一个球体来模拟入射点的局部区域,球体的半径是通过入射点的高斯曲率 $K$ 计算得到的,该曲率可以通过数值方法在网格上计算 [Meyer et al. 2002]。半径通过 $r = \frac{1}{\sqrt{K}}$ 计算,假设两个主曲率 $\kappa_1$ 和 $\kappa_2$ 是相同的(见图 5(c))。然而,对于非零厚度界面,还有一个问题需要考虑。当入射位置靠近几何形状的边缘时,光线会在界面内部反复发生全内反射。在这种情况下,光线不会进入界面的内部。为了应对这种情况,我们使用第一阶段(第 3 节)获得的几何形状渲染一个掩模,并对掩模应用腐蚀滤波器,以消除靠近几何形状边缘的样本(见图 5(d))。核大小 $e_s$ 被视为一个可调节的超参数。
解释
1. 非零厚度界面的引入
- 在光学和几何建模中,界面的厚度是一个重要的因素。当界面厚度为零时,光线的入射点和出射点可以认为是同一个点,法线也相同。然而,当界面有非零厚度时,光线的入射点和出射点会分开,且这两点的法线也会不同。因此,需要引入参数 $h$ 来模拟这种厚度。
2. 局部区域的建模
-
为了准确模拟光线在界面中的行为,作者使用一个球体来近似入射点的局部区域。球体的半径 $r$ 通过入射点的高斯曲率 $K$ 计算,公式为:
\[r = \frac{1}{\sqrt{K}}\] - 高斯曲率 $K$ 是一个几何量,表示表面在某一点的弯曲程度。它可以通过数值方法在网格上计算,例如使用 Meyer 等人 [2002] 提出的方法。
- 假设两个主曲率 $\kappa_1$ 和 $\kappa_2$ 相等,这简化了计算过程。
3. 边缘效应的处理
- 当光线入射在界面的边缘附近时,可能会发生多次全内反射,导致光线无法进入界面内部。这种现象在实际应用中需要特别处理。
- 作者通过以下方法解决这一问题:
- 使用第一阶段生成的几何形状渲染一个掩模(mask)。
- 对模掩应用腐蚀滤波器(erosion filter),以去除靠近几何形状边缘的样本。
- 腐蚀滤波器的核大小 $e_s$ 是一个可调节的超参数,可以根据具体需求进行调整。
4. 图示说明
- 文中提到的图 5(c) 和图 5(d) 应该展示了球体半径的计算方法和掩模处理的效果。这些图示有助于更直观地理解上述概念。
5. 总结
- 这段内容主要讨论了如何在几何建模中处理非零厚度界面的问题。通过引入参数 $h$ 和使用球体局部建模,可以更准确地模拟光线在界面中的行为。同时,通过腐蚀滤波器处理边缘效应,避免了光线在边缘附近发生多次全内反射的问题。
翻译
4.3 “洋葱”式迭代策略
我们现在将界面重建与光线追踪重建结合起来。整个流程类似于“剥洋葱”,对最外层和内层表面重复相同的步骤:
(1) 给定输入图像及其对应物体的姿态,应用界面重建(第3节)。在这个步骤中,重建了最外层的几何形状(可以是透明的或不透明的),同时学习了几何形状和背景NeRF的材料属性。
(2) 将第一步的几何形状固定,并将其转换为一个界面,每个表面点上定义了两个折射率(IoRs)和一个可选的厚度(第4.1节、第4.2节)。光线被追踪并折射进入界面。在界面内部执行另一次界面重建过程以获得内层几何形状。第一阶段的所有网络(除了SDF网络)直接加载,并以较低的学习率与第二阶段的网络联合学习。这是为了通过第二阶段的精确光线追踪对第一阶段的网络进行细化。在第二次界面重建过程中,由于感兴趣的区域完全固定,因此移除了外层的NeRF和lilc。
从理论上讲,(2)可以重复进行,以处理具有两层以上几何形状的物体。然而,在现实生活中,具有多个嵌套透明界面的几何形状很少见。考虑到流程的简洁性,本文仅考虑两层几何结构。
解释
1. “洋葱”式迭代策略的背景
- 本文提出了一种结合界面重建和光线追踪重建的方法,用于处理具有透明或不透明几何形状的物体。
- 这种策略的核心思想是通过迭代的方式,逐步从外层到内层重建物体的几何形状和材质属性。这种迭代过程类似于“剥洋葱”,逐层处理物体的结构。
2. 步骤1:界面重建
- 输入:输入图像及其对应的物体姿态。
- 目标:重建最外层的几何形状(可以是透明或不透明的),并学习几何形状和背景NeRF的材质属性。
- 这一步主要利用第3节中描述的界面重建方法,重点在于获取物体的外层几何形状和相关材质信息。
3. 步骤2:光线追踪与内层重建
- 固定外层几何形状:将第一步重建的几何形状固定,并将其转换为一个界面。每个表面点上定义了两个折射率(IoRs)和一个可选的厚度(具体方法见第4.1节和第4.2节)。
- 光线追踪:光线被追踪并折射进入界面。
- 内层重建:在界面内部执行另一次界面重建过程,以获得内层几何形状。
- 网络联合学习:第一阶段的所有网络(除了SDF网络)被直接加载,并以较低的学习率与第二阶段的网络联合学习。这种联合学习的目的是利用第二阶段的精确光线追踪对第一阶段的网络进行细化。
- 移除外层NeRF和lilc:在第二次界面重建过程中,由于感兴趣的区域完全固定,因此移除了外层的NeRF和lilc(可能是某种网络组件)。
4. 多层几何结构的处理
- 从理论上讲,步骤2可以重复进行,以处理具有两层以上几何结构的物体。
- 然而,在现实生活中,具有多个嵌套透明界面的几何结构非常罕见。因此,为了简化流程,本文仅考虑两层几何结构。
5. 总结
- 这种“洋葱”式迭代策略的核心在于通过逐步重建外层和内层几何结构,结合界面重建和光线追踪重建的优势,以更精确地处理具有透明或不透明几何形状的物体。
- 通过固定外层几何形状并逐步细化内层结构,该方法能够有效地处理复杂的几何和材质属性,同时保持流程的简洁性。
部分论文代码解释【Sonnet-3.5】
Stage2Renderer.render代码讲解
这段代码实现了一个光线追踪渲染器,主要用于模拟透明物体的光线传播。让我为您解析主要功能:
主要功能
ray_trace
函数的主要功能:
def ray_trace(self,rays_o,rays_d,mask):
# rays_o: 光线起点
# rays_d: 光线方向
# mask: 遮罩
主要实现了以下功能:
- 光线追踪循环:
- 最多追踪3次光线反射/折射
- 每次追踪都会:
- 计算光线与物体的交点
- 计算表面法线
- 根据折射率计算折射方向
- 处理全反射(TIR)情况
- 折射计算:
# 计算入射角余弦 cos_thetai = torch.sum(normal * -next_dir,dim=-1,keepdim=True) # 计算折射角正弦平方 sin_thetat_2 = sin_thetai_2 * ior_ratio * ior_ratio
- 采样点生成:
- 在光线路径上生成采样点用于体积渲染
- 对内部和外部区域使用不同的采样策略
render_core
函数实现了渲染核心功能:- 计算每个采样点的颜色和不透明度
- 使用体积渲染积分得到最终颜色
- 处理内部和外部区域的渲染
- 计算法线、高光等渲染特征
主要创新点:
- 结合了显式表面和隐式表面的渲染
- 支持多次折射和反射
- 考虑了材质的折射率变化
- 实现了基于物理的真实感渲染
这是一个复杂的渲染系统,用于生成透明物体的真实感图像。它结合了传统光线追踪和神经渲染的技术。
需要注意的是,代码中使用了一些深度学习组件(如 sdf_network、color_network 等),这表明这是一个基于神经网络的渲染器。
曲率作用
在这个渲染系统中,曲率(curvature)的计算主要有以下几个用途:
- 表面特征表示:
# 曲率计算用于表征表面的局部几何特征 # mean curvature = (k1 + k2)/2,其中k1和k2是主曲率 mean_curvature = torch.sum(curvature, dim=-1, keepdim=True) * 0.5
主要作用:
- 帮助描述物体表面的弯曲程度
- 用于区分凸面、凹面和平面区域
- 影响材质的渲染效果
- 渲染效果增强:
- 曲率信息可以用来调整:
- 表面的反射特性
- 高光的分布
- 边缘的渲染效果
- 特别是在透明物体中,曲率较大的区域往往会产生更明显的光学效果
- 曲率信息可以用来调整:
- 物理准确性:
# 曲率影响光线的传播 # 在高曲率区域,光线会发生更强的折射和反射 if use_curvature: specular = specular * (1.0 + mean_curvature)
- 视觉提示:
- 曲率信息可以帮助观察者更好地理解:
- 物体的3D形状
- 表面的细节特征
- 材质的质感
- 曲率信息可以帮助观察者更好地理解:
总的来说,曲率在这个渲染系统中扮演着重要角色,它不仅影响光线的传播行为,还能增强渲染的真实感。特别是对于透明物体,准确的曲率计算对于模拟真实的光学效果至关重要。
使用SDF MLP计算曲率(非Nu-NeRF的实现方式)
如果需要使用SDF MLP计算曲率,曲率是通过计算SDF(Signed Distance Function)的二阶导数来获得的。让我们看看具体实现:
def get_curvature(self, x, sdf=None, gradients=None):
if sdf is None:
sdf = self.sdf_network.sdf(x)
if gradients is None:
gradients = self.gradient(x, sdf)
# 计算二阶导数
d2_x = torch.autograd.grad(gradients[..., 0].sum(), x, create_graph=True)[0][..., 0]
d2_y = torch.autograd.grad(gradients[..., 1].sum(), x, create_graph=True)[0][..., 1]
d2_z = torch.autograd.grad(gradients[..., 2].sum(), x, create_graph=True)[0][..., 2]
# 计算混合偏导数
d2_xy = torch.autograd.grad(gradients[..., 0].sum(), x, create_graph=True)[0][..., 1]
d2_xz = torch.autograd.grad(gradients[..., 0].sum(), x, create_graph=True)[0][..., 2]
d2_yz = torch.autograd.grad(gradients[..., 1].sum(), x, create_graph=True)[0][..., 2]
计算过程主要包括:
- 获取一阶导数(梯度):
- 首先通过
gradient
函数获取SDF关于空间坐标的一阶导数 - 这个梯度表示了表面法线方向
- 首先通过
- 计算二阶导数:
- 使用
torch.autograd.grad
计算Hessian矩阵的元素 - 包括:
- 纯二阶导数:d2_x, d2_y, d2_z
- 混合偏导数:d2_xy, d2_xz, d2_yz
- 使用
- 曲率计算:
```python
构建Hessian矩阵
hessian = torch.stack([ torch.stack([d2_x, d2_xy, d2_xz], dim=-1), torch.stack([d2_xy, d2_y, d2_yz], dim=-1), torch.stack([d2_xz, d2_yz, d2_z], dim=-1) ], dim=-2)
计算主曲率
curvature = compute_curvature_from_hessian(hessian, gradients)
这种计算方法的优点:
- 精确:通过二阶导数可以准确捕捉表面的局部几何特征
- 可微分:支持梯度反向传播,适合深度学习框架
- 通用性:适用于任何隐式表面
需要注意的是:
1. 这种计算方式计算量较大,因为需要多次计算自动微分
2. 在实际应用中可能需要添加数值稳定性的处理
3. 曲率的计算依赖于SDF网络的质量
这种基于SDF的曲率计算方法在神经渲染中很常见,因为它能够准确地表达表面的几何特征,对于生成高质量的渲染结果很重要。
### Nu-NeRF中曲率计算
让我们看看这份代码中曲率的计算:
在这份代码中,曲率(gaussian_curvature)是通过 `Dintersect` 函数获得的:
```python:_posts/hi.py
# 对于外部光线
if is_outside:
intersection_info, converged = self.scene.Dintersect(rays_for_intersection)
converged = converged.bool().reshape(-1,1).detach()
intersection = intersection_info.intersection_point
gaussian_curvature = intersection_info.g_k # 从intersection_info中获取高斯曲率
normal = F.normalize(intersection_info.n, dim=-1)
具体来看:
- 曲率是通过
self.scene.Dintersect()
函数计算得到的,作为intersection_info
的一部分返回 - 通过
intersection_info.g_k
获取高斯曲率值 - 这个曲率值后续被用于:
- 计算曲率半径:
curvature_radius = 1 / torch.sqrt(torch.clamp(torch.abs(gaussian_curvature),min=0.000001))
- 区分正负曲率:
curvature_positive = gaussian_curvature >= 0
- 计算曲率半径:
不过需要注意的是,实际的曲率计算实现是在 self.scene.Dintersect()
中完成的,但这部分代码没有在给出的代码片段中显示。要了解具体的计算方法,我们需要查看 Dintersect
函数的实现。
曲率计算的实现
从Scene类的代码中可以看到,高斯曲率的计算是通过PyMesh库完成的:
def init_VN(self):
# ...
self.pymesh_mesh.add_attribute("vertex_gaussian_curvature")
# 获取高斯曲率并限制在[-10.0, 10.0]范围内
self.gaussian_curvatures = torch.tensor(
np.clip(
self.pymesh_mesh.get_attribute("vertex_gaussian_curvature"),
a_min=-10.0,
a_max=10.0
),
device='cuda:0'
).reshape(vertices.shape[0],1)
具体过程是:
- 在初始化时,通过
pymesh.meshio.load_mesh()
加载网格 - 调用
add_attribute("vertex_gaussian_curvature")
计算每个顶点的高斯曲率 - 使用
get_attribute()
获取计算好的高斯曲率值 - 将曲率值限制在 [-10.0, 10.0] 范围内
- 转换为 torch.tensor 并存储在 GPU 上
当需要获取某个交点的曲率时,在 Dintersect
方法中:
def Dintersect(self, ray: Ray):
faces_ind, hitted = self.optix_intersect(ray)
faces = self.faces[faces_ind[hitted]]
triangles = self.vertices[faces].float()
normals = self.normals[faces].float()
curvatures = self.gaussian_curvatures[faces].float() # 获取相关面的顶点曲率
ray_hitted = ray.select(hitted)
# 通过JIT_Dintersect计算交点的具体信息,包括插值后的曲率
u, v, t, n, g_k = JIT_Dintersect(ray_hitted.origin, ray_hitted.direction, triangles, normals, curvatures)
这里的曲率计算实际上是由PyMesh库完成的,代码中只是在使用计算好的结果。PyMesh使用离散微分几何的方法计算高斯曲率,具体实现在PyMesh的C++代码中。
曲率计算的输入是mesh
从代码中可以看到,输入是mesh(三角网格):
def __init__(self, mesh_path, cuda_device = 0):
self.optix_mesh = optix_mesh()
self.update_mesh(mesh_path)
def update_mesh(self, mesh_path):
# 加载mesh文件
mesh = trimesh.load(mesh_path, process=False)
self.mesh = mesh
# 获取顶点和面信息
self.vertices = torch.tensor(mesh.vertices, dtype=Float, device=device) # 顶点
self.faces = torch.tensor(mesh.faces, dtype=torch.long, device=device) # 面
self.triangles = self.vertices[self.faces] #[Fx3x3]
# 使用PyMesh加载同样的mesh
self.pymesh_mesh = pymesh.meshio.load_mesh(mesh_path, drop_zero_dim=False)
具体来说:
- 输入是一个mesh文件(如.obj, .ply等格式)
- mesh包含:
- vertices(顶点)
- faces(面,由三个顶点索引组成的三角形)
- 曲率计算是基于这个三角网格结构进行的,而不是点云
- PyMesh会利用mesh的拓扑结构(顶点连接关系)来计算每个顶点的高斯曲率
所以这是一个基于mesh的曲率计算,而不是点云的曲率计算。这也更合理,因为mesh提供了表面的连接信息,可以更准确地计算曲率。
曲率与透明物折射的关系
在这份代码中,曲率主要用于计算光线在透明物体表面的折射路径。让我看看具体实现:
# 计算曲率半径(曲率的倒数)
curvature_radius = 1 / torch.sqrt(torch.clamp(torch.abs(gaussian_curvature),min=0.000001))
curvature_radius = torch.nan_to_num(curvature_radius,0.1)
# 区分正负曲率
curvature_positive = gaussian_curvature >= 0
curvature_negative = ~curvature_positive
if is_outside: # 从外部进入物体
cos_thetat = torch.sqrt(torch.clamp(1 - sin_thetat_2,min=0.0001))
# 处理正曲率的情况(凸面)
if curvature_positive.sum() > 0:
costhetat_r = curvature_radius * cos_thetat
delta2_positive = costhetat_r * costhetat_r - 2 * curvature_radius * thickness + thickness * thickness
delta_positive = torch.sqrt(torch.clamp(delta2_positive,min = 0.0001))
length_dt = torch.abs(costhetat_r - delta_positive)
# 计算曲面中心位置和下一个光线起点
center_position = intersection - normal * curvature_radius
next_start_tmp_positive = intersection + next_dir_inner * (length_dt.reshape(-1,1) + 0.001)
# 处理负曲率的情况(凹面)
if curvature_negative.sum() > 0:
costhetat_r = curvature_radius * cos_thetat
delta2_negative = costhetat_r * costhetat_r + 2 * curvature_radius * thickness + thickness * thickness
delta_negative = torch.sqrt(torch.clamp(delta2_negative,min = 0.0001))
length_dt = torch.abs(costhetat_r - delta_negative)
# 计算曲面中心位置和下一个光线起点
center_position = intersection + normal * curvature_radius
next_start_tmp_negative = intersection + next_dir_inner * (length_dt.reshape(-1,1) + 0.001)
主要用途:
- 曲率用于确定表面的形状(凹面或凸面)
- 曲率半径用于:
- 计算光线在曲面上的传播距离
- 确定曲面的中心位置
- 计算光线的下一个起点
- 不同曲率类型的处理:
- 正曲率(凸面):光线向外弯曲
- 负曲率(凹面):光线向内弯曲
- 结合厚度(thickness)参数,计算光线在透明物体内的传播路径
这种处理方式考虑了曲面的几何特性对光线传播的影响,使得渲染结果更接近真实的物理现象。
曲率估计折射路径长度
对于一个曲面上的折射,主要涉及以下几个步骤:
- 首先是斯涅尔定律(Snell’s law):
\(n_1 \sin(\theta_i) = n_2 \sin(\theta_t)\)
在代码中表现为:
sin_thetat_2 = sin_thetai_2 * ior_ratio * ior_ratio # ior_ratio = \frac{n_1}{n_jjjj 2}
-
对于曲面,关键是计算光线在曲面上的传播。假设曲率半径为$R$($R = \frac{1}{\sqrt{ K }}$,$K$为高斯曲率),则: - 对于凸面(正曲率):
\(\cos(\theta_t R) = \pm \sqrt{R^2 \cos^2(\theta_t) - 2Rt + t^2}\)
在代码中:
costhetat_r = curvature_radius * cos_thetat delta2_positive = costhetat_r * costhetat_r - 2 * curvature_radius * thickness + thickness * thickness
- 对于凸面(正曲率):
\(\cos(\theta_t R) = \pm \sqrt{R^2 \cos^2(\theta_t) - 2Rt + t^2}\)
在代码中:
- 光线传播距离:
\(d = \left| R \cos(\theta_t) - \sqrt{R^2 \cos^2(\theta_t) - 2Rt + t^2} \right|\)
代码实现:
length_dt = torch.abs(costhetat_r - delta_positive)
- 对于凹面(负曲率),公式变为:
\(\cos(\theta_t R) = \pm \sqrt{R^2 \cos^2(\theta_t) + 2Rt + t^2}\)
代码实现:
delta2_negative = costhetat_r * costhetat_r + 2 * curvature_radius * thickness + thickness * thickness
- 曲面中心的位置计算:
- 凸面:$P_{\text{center}} = P - Rn$
- 凹面:$P_{\text{center}} = P + Rn$ 其中:
- $P$是交点位置
- $n$是表面法线
- $R$是曲率半径
代码中:
# 凸面 center_position = intersection - normal * curvature_radius # 凹面 center_position = intersection + normal * curvature_radius
这些计算考虑了:
- 表面的局部几何形状(通过高斯曲率)
- 材质的折射率(通过 $ ior_{ratio} $ )
- 物体的厚度(通过$thickness$)
这样可以更准确地模拟光线在曲面上的折射行为,特别是:
- 光线在曲面上的偏折
- 光线在介质中的传播距离
- 不同曲率(凹凸)对光线路径的影响
推导传播距离【sonnet-3.5】
对于一个曲面(以凸面为例):
假设我们有:
- 曲率半径 $R$
- 入射点 $P$
- 曲面中心 $C$
- 折射角 $\theta_t$
- 材料厚度 $t$
我们要找到光线穿过厚度 $t$ 后的出射点 $Q$。这形成了一个几何问题:
P: 入射点
C: 曲面中心
Q: 出射点
R: 曲率半径
$\theta_t$ : 折射角
t: 厚度
PC = R (曲率半径)
PQ = d (我们要求的传播距离)
CQ = R (因为出射点也在同一曲率半径上)
根据余弦定理:
\[CQ^2 = CP^2 + PQ^2 - 2 \cdot CP \cdot PQ \cdot \cos\left(\frac{\pi}{2} - \theta_t\right)\]代入已知条件:
\[R^2 = R^2 + d^2 - 2Rd \cdot \cos\left(\frac{\pi}{2} - \theta_t\right)\] \[R^2 = R^2 + d^2 - 2Rd \cdot \sin(\theta_t)\]由于厚度 $t$ 是沿法线方向测量的:
\[t = d \cdot \cos(\theta_t)\]将这个关系代入:
\[R^2 = R^2 + \left(\frac{t}{\cos(\theta_t)}\right)^2 - 2R\left(\frac{t}{\cos(\theta_t)}\right) \cdot \sin(\theta_t)\]整理后得到:
\[(R \cdot \cos(\theta_t))^2 = (R \cdot \cos(\theta_t))^2 - 2Rt + t^2\]因此传播距离 $d$ 为:
\[d = \left| R \cdot \cos(\theta_t) - \sqrt{(R \cdot \cos(\theta_t))^2 - 2Rt + t^2} \right|\]这就是代码中的计算公式:
costhetat_r = curvature_radius * cos_thetat # R \cdot \cos(\theta_t)
delta2_positive = costhetat_r * costhetat_r - 2 * curvature_radius * thickness + thickness * thickness
delta_positive = torch.sqrt(torch.clamp(delta2_positive, min=0.0001))
length_dt = torch.abs(costhetat_r - delta_positive)
对于凹面,推导过程类似,只是几何关系略有不同,导致公式中的 $-2Rt$ 变为 $+2Rt$。
这个公式考虑了:
- 曲面的几何形状(通过曲率半径 $R$)
- 折射后光线的传播方向(通过 $\theta_t$)
- 材料的厚度(通过 $t$)
这样就能准确计算出光线在曲面材料中的实际传播距离。