Differentiable Rendering
可微渲染计算渲染出的每个像素如何随场景形状、材质和相机变化,从而可以根据照片拟合 3D 场景。
可微渲染是指将 3D 场景的几何形状、材质、光照和相机描述转换为图像的渲染过程,并以能够计算每个输出像素相对于这些场景参数的导数的方式实现。随后,可以通过梯度下降最小化比较渲染图像与照片的损失函数,同时让反向传播穿过渲染器传递到 3D 场景中。因此,可微渲染是逆渲染的核心引擎;逆渲染是从图像还原场景的计算机视觉任务。它也是神经渲染方法的核心,例如神经辐射场(NeRF)和高斯泼溅,这些方法通过渲染场景来训练场景模型。
渲染为何需要特殊处理#
标准光栅化器在关键环节上不可微。像素是否被三角形覆盖,是顶点位置的阶跃函数,因此稍微移动顶点不会改变任何像素,直到边缘越过像素中心;所以几何形状相关的梯度几乎处处为零。遮挡则会在一个表面遮住另一个表面的地方引入更多不连续性。可微渲染器会用不同方式处理这些边缘:
- 近似梯度: OpenDR(2014)和 Neural 3D Mesh Renderer(2018)在前向传播中保留标准光栅化器,并在反向传播中近似梯度。
- 软光栅化: SoftRas(2019)将每个像素的硬覆盖改为一个概率,该概率会随像素与三角形边缘的距离增加而平滑下降,并对重叠面进行混合,因此边缘附近的每个像素都能将梯度传递到几何形状。
- 基于物理的可微渲染: redner(2018)和 Mitsuba 3(2022)等光线追踪器会对轮廓边缘进行采样,或对渲染积分重新参数化,从而获得阴影、反射和间接光照的梯度。
- 体渲染与泼溅渲染: NeRF 沿每条光线对密度进行 alpha 合成,而高斯泼溅则对光栅化后的 3D 高斯进行处理;按照设计,它们都是参数的平滑函数。
用 PyTorch 实现单个圆盘的软光栅化器可以说明这一原理:覆盖率是圆盘边缘有符号距离的 sigmoid 函数,因此边界附近的每个像素都能将梯度传递到圆盘中心和半径。
import torch
# Pixel coordinates of a 64x64 image
ys, xs = torch.meshgrid(torch.arange(64.0), torch.arange(64.0), indexing="ij")
def render(center, radius):
"""Soft-rasterize a disk: pixel coverage is a sigmoid of the signed distance to its edge."""
distance = torch.sqrt((xs - center[0]) ** 2 + (ys - center[1]) ** 2 + 1e-6)
return torch.sigmoid(radius - distance)
# Target image: a disk at (40, 24) with radius 12
target = render(torch.tensor([40.0, 24.0]), torch.tensor(12.0))
# Recover the scene from a wrong guess by backpropagating the pixel loss
center = torch.tensor([20.0, 40.0], requires_grad=True)
radius = torch.tensor(6.0, requires_grad=True)
optimizer = torch.optim.Adam([center, radius], lr=0.5)
for _ in range(300):
loss = ((render(center, radius) - target) ** 2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"center {center.detach().round(decimals=1).tolist()}, radius {radius.item():.1f}")运行后会输出 center [40.0, 24.0], radius 12.0:从大约 26 像素之外、半径只有一半的位置开始,Adam 优化器仅根据像素差异就能还原圆盘。若使用硬覆盖率,(distance < radius).float(),损失就不会向任一参数传递梯度。
实际应用#
代码中的轮廓损失会将渲染的物体掩码与观测掩码进行比较;对于没有标注的照片,实例分割模型(如 YOLO26)(yolo26n-seg.pt)可以为图像中的每个物体提供掩码。










