Nu-NeRF, Neural Reconstruction of Nested Transparent Objects with Uncontrolled Capture Environment

Zhangwenniu 于 2025-02-18 发布

链接

论文引用

@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. 零厚度界面的背景

2. 薄界面的处理方法

3. 折射率的定义

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. 非零厚度界面的引入

2. 局部区域的建模

3. 边缘效应的处理

4. 图示说明

5. 总结

翻译

4.3 “洋葱”式迭代策略
我们现在将界面重建与光线追踪重建结合起来。整个流程类似于“剥洋葱”,对最外层和内层表面重复相同的步骤:
(1) 给定输入图像及其对应物体的姿态,应用界面重建(第3节)。在这个步骤中,重建了最外层的几何形状(可以是透明的或不透明的),同时学习了几何形状和背景NeRF的材料属性。
(2) 将第一步的几何形状固定,并将其转换为一个界面,每个表面点上定义了两个折射率(IoRs)和一个可选的厚度(第4.1节、第4.2节)。光线被追踪并折射进入界面。在界面内部执行另一次界面重建过程以获得内层几何形状。第一阶段的所有网络(除了SDF网络)直接加载,并以较低的学习率与第二阶段的网络联合学习。这是为了通过第二阶段的精确光线追踪对第一阶段的网络进行细化。在第二次界面重建过程中,由于感兴趣的区域完全固定,因此移除了外层的NeRF和lilc。
从理论上讲,(2)可以重复进行,以处理具有两层以上几何形状的物体。然而,在现实生活中,具有多个嵌套透明界面的几何形状很少见。考虑到流程的简洁性,本文仅考虑两层几何结构。

解释

1. “洋葱”式迭代策略的背景

2. 步骤1:界面重建

3. 步骤2:光线追踪与内层重建

4. 多层几何结构的处理

5. 总结

部分论文代码解释【Sonnet-3.5】

Stage2Renderer.render代码讲解

这段代码实现了一个光线追踪渲染器,主要用于模拟透明物体的光线传播。让我为您解析主要功能:

主要功能

ray_trace 函数的主要功能:

def ray_trace(self,rays_o,rays_d,mask):
    # rays_o: 光线起点
    # rays_d: 光线方向
    # mask: 遮罩

主要实现了以下功能:

  1. 光线追踪循环:
    • 最多追踪3次光线反射/折射
    • 每次追踪都会:
      • 计算光线与物体的交点
      • 计算表面法线
      • 根据折射率计算折射方向
      • 处理全反射(TIR)情况
  2. 折射计算:
    # 计算入射角余弦
    cos_thetai = torch.sum(normal * -next_dir,dim=-1,keepdim=True)
    # 计算折射角正弦平方
    sin_thetat_2 = sin_thetai_2 * ior_ratio * ior_ratio
    
  3. 采样点生成:
    • 在光线路径上生成采样点用于体积渲染
    • 对内部和外部区域使用不同的采样策略
  4. render_core 函数实现了渲染核心功能:
    • 计算每个采样点的颜色和不透明度
    • 使用体积渲染积分得到最终颜色
    • 处理内部和外部区域的渲染
    • 计算法线、高光等渲染特征

主要创新点:

这是一个复杂的渲染系统,用于生成透明物体的真实感图像。它结合了传统光线追踪和神经渲染的技术。

需要注意的是,代码中使用了一些深度学习组件(如 sdf_network、color_network 等),这表明这是一个基于神经网络的渲染器。

曲率作用

在这个渲染系统中,曲率(curvature)的计算主要有以下几个用途:

  1. 表面特征表示:
    # 曲率计算用于表征表面的局部几何特征
    # mean curvature = (k1 + k2)/2,其中k1和k2是主曲率
    mean_curvature = torch.sum(curvature, dim=-1, keepdim=True) * 0.5
    

主要作用:

  1. 渲染效果增强:
    • 曲率信息可以用来调整:
      • 表面的反射特性
      • 高光的分布
      • 边缘的渲染效果
    • 特别是在透明物体中,曲率较大的区域往往会产生更明显的光学效果
  2. 物理准确性:
    # 曲率影响光线的传播
    # 在高曲率区域,光线会发生更强的折射和反射
    if use_curvature:
     specular = specular * (1.0 + mean_curvature)
    
  3. 视觉提示:
    • 曲率信息可以帮助观察者更好地理解:
      • 物体的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]

计算过程主要包括:

  1. 获取一阶导数(梯度):
    • 首先通过 gradient 函数获取SDF关于空间坐标的一阶导数
    • 这个梯度表示了表面法线方向
  2. 计算二阶导数:
    • 使用 torch.autograd.grad 计算Hessian矩阵的元素
    • 包括:
      • 纯二阶导数:d2_x, d2_y, d2_z
      • 混合偏导数:d2_xy, d2_xz, d2_yz
  3. 曲率计算: ```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)

具体来看:

  1. 曲率是通过 self.scene.Dintersect() 函数计算得到的,作为 intersection_info 的一部分返回
  2. 通过 intersection_info.g_k 获取高斯曲率值
  3. 这个曲率值后续被用于:
    • 计算曲率半径: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)

具体过程是:

  1. 在初始化时,通过 pymesh.meshio.load_mesh() 加载网格
  2. 调用 add_attribute("vertex_gaussian_curvature") 计算每个顶点的高斯曲率
  3. 使用 get_attribute() 获取计算好的高斯曲率值
  4. 将曲率值限制在 [-10.0, 10.0] 范围内
  5. 转换为 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)

具体来说:

  1. 输入是一个mesh文件(如.obj, .ply等格式)
  2. mesh包含:
    • vertices(顶点)
    • faces(面,由三个顶点索引组成的三角形)
  3. 曲率计算是基于这个三角网格结构进行的,而不是点云
  4. 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)

主要用途:

  1. 曲率用于确定表面的形状(凹面或凸面)
  2. 曲率半径用于:
    • 计算光线在曲面上的传播距离
    • 确定曲面的中心位置
    • 计算光线的下一个起点
  3. 不同曲率类型的处理:
    • 正曲率(凸面):光线向外弯曲
    • 负曲率(凹面):光线向内弯曲
  4. 结合厚度(thickness)参数,计算光线在透明物体内的传播路径

这种处理方式考虑了曲面的几何特性对光线传播的影响,使得渲染结果更接近真实的物理现象。

曲率估计折射路径长度

对于一个曲面上的折射,主要涉及以下几个步骤:

  1. 首先是斯涅尔定律(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}
    
  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
      
  3. 光线传播距离: \(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)
    
  4. 对于凹面(负曲率),公式变为: \(\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
    
  5. 曲面中心的位置计算
    • 凸面:$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
    

这些计算考虑了:

这样可以更准确地模拟光线在曲面上的折射行为,特别是:

  1. 光线在曲面上的偏折
  2. 光线在介质中的传播距离
  3. 不同曲率(凹凸)对光线路径的影响

推导传播距离【sonnet-3.5】

对于一个曲面(以凸面为例):

假设我们有:

我们要找到光线穿过厚度 $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$。

这个公式考虑了:

  1. 曲面的几何形状(通过曲率半径 $R$)
  2. 折射后光线的传播方向(通过 $\theta_t$)
  3. 材料的厚度(通过 $t$)

这样就能准确计算出光线在曲面材料中的实际传播距离。