- 人工智能
- 深度学习
- 计算机视觉
- 图形学
【免费下载链接】pytorch3d
PyTorch3D is FAIR's library of reusable components for deep learning with 3D data
PyTorch3D 的pytorch3d.renderer.mesh.shader模块将光栅化(Rasterization)输出的Fragments与场景参数(相机、灯光、材质)组合起来,最终生成渲染图像,是"网格渲染管线"的最后一步。本文基于该模块的 API 文档与源码实现,系统讲解 10 个内置着色器(Hard/Soft Phong、Gouraud、Flat、Silhouette、Depth、Splatter)的适用场景、初始化方式、forward 调用约定,并结合 shading.py、blending.py 与 tests/test_shader.py 剖析其可微渲染的底层原理,读完即可独立搭建自定义 Shader 并接入MeshRenderer。
Shader 在渲染管线中的位置与职责
在 PyTorch3D 中,一个渲染器由"光栅化器 + 着色器"两部分组合而成,见 renderer.py 的MeshRenderer:
fragments = self.rasterizer(meshes_world, **kwargs) images = self.shader(fragments, meshes_world, **kwargs)- 光栅化器(
MeshRasterizer)负责世界坐标 → 屏幕坐标的变换,并把每个像素命中的 top-K 个面输出为Fragments; - Shader则负责把
Fragments变成最终图像,典型操作包括(见 shader.py 的模块注释):- 用重心坐标(barycentric coordinates)插值顶点属性;
- 从纹理图采样颜色;
- 逐像素应用光照(lighting);
- 对每个像素的 top-K 个面做颜色混合(blending)。
Fragments由 rasterizer.py 定义,包含四个字段:
| 字段 | 形状 | 含义 |
|---|---|---|
pix_to_face | (N, H, W, K) | 每个像素按 z 排序命中的 K 个面索引(packed 表示),未命中像素填充 -1 |
zbuf | (N, H, W, K) | 每个像素命中的 K 个面的 NDC z 坐标(按 z 升序) |
bary_coords | (N, H, W, K, 3) | 每个像素相对命中面的重心坐标 |
dists | (N, H, W, K) | 像素中心到命中面的二维欧氏距离(NDC 单位),软混合(soft blending)依赖它 |
所有 Shader 的forward签名统一为forward(fragments, meshes, **kwargs),返回形状为(N, H, W, 4)的 RGBA 图像(深度着色器除外,见下文)。
基类 ShaderBase:相机、灯光、材质的默认注入
ShaderBase(shader.py)是所有光照类着色器的基类,构造参数为:
device:默认"cpu",用于初始化默认灯光与材质;cameras:相机实例,可为None(此时必须在 forward 时通过cameras=传入);lights:灯光实例,默认为PointLights(device=device);materials:材质实例,默认为Materials(device=device);blend_params:混合参数,默认为BlendParams()。
其中_get_cameras(**kwargs)的取值优先级是"forward 传入的cameraskwargs → 构造时设置的self.cameras",两者均为None时抛出ValueError("Cameras must be specified either at initialization or in the forward pass of the shader")。测试 tests/test_shader.py 的test_cameras_check逐一验证了全部内置 Shader 在缺失相机时都会正确报错。
此外ShaderBase.to(device)会手动迁移cameras、materials、lights三个非nn.Module子类的属性(它们实现TensorProperties.to),保证.to(cuda)后整条管线可用。需要说明:所有内置着色器的 forward 都支持通过 kwargs 临时覆盖lights、materials、blend_params,例如shader(fragments, meshes, cameras=cam, lights=my_lights)。
纹理采样:meshes.sample_textures(fragments)
Phong/Flat 类着色器在光照前都会执行texels = meshes.sample_textures(fragments),得到形状为(N, H, W, K, 3)的逐像素纹理颜色。这一步会根据网格的纹理类型(TexturesVertex、TexturesUV、TexturesAtlas)分别做顶点颜色插值、UV 插值采样或图集采样。只有 Gouraud 着色器不调用它——因为 Gouraud 是先给顶点上光照再插值颜色,纹理插值发生在着色步骤内部(源码注释在 shader.py 有明确说明)。
逐像素 Phong 着色:HardPhongShader 与 SoftPhongShader
Phong 着色模型在 shading.py 的phong_shading/_phong_shading_with_pixels中实现,流程为:
- 取 packed 顶点坐标
verts_packed()、顶点法线verts_normals_packed(); - 用
interpolate_face_attributes按重心坐标插值出逐像素的世界/相机坐标与逐像素法线; - 调用
_apply_lighting计算环境光(ambient)、漫反射(diffuse)、高光(specular)三项; - 输出颜色 =
(ambient + diffuse) * texels + specular。
HardPhongShader
HardPhongShader:逐像素光照 +硬混合(hard blending),即每个像素直接取最近面(K=0)的颜色。初始化示例(来自 docstring):
shader = HardPhongShader(device=torch.device("cuda:0"))其forward等价于images = hard_rgb_blend(phong_shading(...), fragments, blend_params)。
SoftPhongShader
SoftPhongShader:逐像素光照 +软混合。混合时使用softmax_rgb_blend,需要znear/zfar(默认从cameras.znear/cameras.zfar读取,缺失时分别回退到 1.0 与 100.0),也可通过 kwargs 覆盖:
shader = SoftPhongShader(device=torch.device("cuda:0"))使用提示:当blur_radius > 0时,光栅化会产生重心坐标越界(落在面外)的像素,MeshRenderer的 docstring 建议将rasterizer.raster_settings.clip_barycentric_coords=True,否则纹理与深度插值会越界,见 renderer.py。
TexturedSoftPhongShader(已弃用)
TexturedSoftPhongShader目前是一个向后兼容的函数(shader.py),调用时会发出PendingDeprecationWarning并直接返回SoftPhongShader实例。新代码请统一使用SoftPhongShader。
逐顶点 Gouraud 着色:HardGouraudShader 与 SoftGouraudShader
Gouraud 与 Phong 的关键区别在于光照计算的位置:
- Phong:先插值法线与坐标(逐像素),再算光照;
- Gouraud:先在每个顶点上计算光照,得到顶点着色颜色,再用重心坐标把顶点颜色插值到像素。
gouraud_shading(shading.py)的实现细节值得注意:
- 仅支持
TexturesVertex:源码第 149-150 行显式断言meshes.textures必须是TexturesVertex,否则抛ValueError; - 批量处理:当
len(meshes) > 1时,会用gather_props(vert_to_mesh_idx)把灯光/相机/材质属性从(N, ...)扩展到(V, ...)(packed 顶点数),保证异构批次每个顶点拿到属于自己网格的光照参数; - 顶点着色颜色 =
verts_colors * (ambient + diffuse) + specular,然后interpolate_face_attributes插值到像素。
两个实现:
shader = HardGouraudShader(device=torch.device("cuda:0")) # 硬混合最近面 shader = SoftGouraudShader(device=torch.device("cuda:0")) # 软混合 top-K 面逐面 Flat 着色:HardFlatShader
HardFlatShader 使用平均面位置与面法线(faces_normals_packed())计算光照,因此同一面内颜色恒定,产生明显的棱面感(faceting)效果。flat_shading(shading.py)中有一个实现细节:光栅化会把无命中像素的pix_to_face填为 -1,Flat 着色在 gather 之前先把 -1 临时替换为 0 再 gather,随后把背景像素的坐标与法线清零,避免索引越界。
shader = HardFlatShader(device=torch.device("cuda:0"))轮廓(Silhouette)着色:SoftSilhouetteShader
SoftSilhouetteShader 不依赖任何光照与纹理,专用于生成软轮廓图,其 RGB 通道固定为 1(colors = torch.ones_like(fragments.bary_coords)),Alpha 通道由sigmoid_alpha_blend按像素到面的二维欧氏距离概率映射计算,方法源自 SoftRasterizer(Liu et al., ICCV 2019,见 docstring 引用 [0])。
该 Shader 只接受blend_params参数。为了让轮廓与 SoftRasterizer 行为一致,docstring 明确要求按如下方式设置光栅化的blur_radius:
blend_params = BlendParams(sigma=1e-4, gamma=1e-4) raster_settings = RasterizationSettings( image_size=128, blur_radius=np.log(1.0 / 1e-4 - 1.0) * blend_params.sigma, # 关键! faces_per_pixel=100, )该配置组合在官方教程 camera_position_optimization_with_differentiable_rendering.ipynb 与 fit_textured_mesh.ipynb 中被反复使用(配合softmax_rgb_blend与sigmoid_alpha_blend实现可微的 RGB 与轮廓损失)。
sigmoid_alpha_blend的底层是一个自定义torch.autograd.Function_SigmoidAlphaBlend(blending.py),前向/反向分别调用 CUDA/C++ 扩展_C.sigmoid_alpha_blend与_C.sigmoid_alpha_blend_backward(实现位于 csrc/blending),反向通过已保存的dists、pix_to_face、alphas计算dists的梯度——这是轮廓可微性的关键。
深度着色:HardDepthShader 与 SoftDepthShader
两个深度着色器输出形状为(N, H, W, 1)(只有深度通道,非 RGBA):
- HardDepthShader:直接取每个像素最近面的 z 值(
zbuf[..., 0:1]),背景像素(pix_to_face < 0)填充相机的zfar(默认 100.0); - SoftDepthShader:基于像素到面的距离概率(
torch.sigmoid(-dists / sigma))对 top-K 深度做加权聚合,并在末尾拼接一个 zfar 的"额外面"参与归一化。注意:它要求fragments.dists非空,否则直接抛ValueError("SoftDepthShader requires Fragments.dists to be present.")——因此使用它时必须让光栅化的blur_radius > 0,否则dists不会返回。
shader = HardDepthShader(device=torch.device("cuda:0")) shader = SoftDepthShader(device=torch.device("cuda:0"))Splatter 着色:SplatterPhongShader(可微渲染进阶)
SplatterPhongShader 实现了 Cole et al. 的 "Differentiable Surface Rendering via Non-differentiable Sampling"(docstring [0])方法:逐像素 Phong 光照后,不再按像素独立混合,而是把每个像素的颜色向周围 3×3 邻域做高斯 splat(溅射),从而让颜色梯度能够跨越光栅化的硬边界传播。
实现要点(见 splatter_blend.py 的SplatterBlender):
- 它对光栅化的
fragments做了detach(),但保留_phong_shading_with_pixels插值出的相机坐标系像素坐标(pixel_coords_cameras),梯度通过该坐标流回网格顶点(pixel_coords_cameras是meshes.verts_packed()的插值,见 shading.py); SplatterBlender根据(N, H, W, K)形状只预计算一次常量(crop_ids_h/crop_ids_w/offsets,见_precompute),避免重复计算;BlendParams.sigma此时表示高斯核的标准差(单位:像素),check_blend_params会对sigma != 0.5发出警告,因为 0.5 是唯一符合像素网格间距的合理取值。
shader = SplatterPhongShader(device=torch.device("cuda:0"))BlendParams:软混合与轮廓的关键超参
BlendParams是定义在 blending.py 的NamedTuple,三个字段全部有默认值:
| 字段 | 默认值 | 语义 |
|---|---|---|
sigma | 1e-4 | 控制距离 sigmoid 的宽度,决定边缘锐度:越大边缘越模糊。对SplatterPhongShader则解释为高斯核标准差 |
gamma | 1e-4 | 控制指数项缩放,决定颜色不透明度:越大面越透明 |
background_color | (1.0, 1.0, 1.0) | 背景 RGB,可为 tuple 或长度为 3 的 tensor |
三种混合函数的行为对比(均返回 RGBA):
hard_rgb_blend(blending.py):取 K=0 最近面颜色,Alpha 恒为 1,背景像素填充background_color;sigmoid_alpha_blend(blending.py):RGB 取最近面,Alpha 由距离 sigmoid 概率决定,用于轮廓;softmax_rgb_blend(blending.py):RGB 与 Alpha 都基于"距离概率 × 相对深度权重"做加权和(z_inv = (zfar - zbuf)/(zfar - znear)),并用cumprod保证完全遮挡时 Alpha 为 0,背景权重通过 exp-normalize trick 参与归一化。
组合矩阵与实战接线示例
官方渲染入门文档 renderer_getting_started.md 给出了各内置 Shader 在"纹理类型 × 着色模型 × 混合方式"上的支持矩阵(节选):
| Shader | 顶点纹理 | UV 纹理 | 纹理图集 | Flat | Gouraud | Phong | 硬混合 | 软混合 |
|---|---|---|---|---|---|---|---|---|
| HardPhongShader | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |||
| SoftPhongShader | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |||
| HardGouraudShader | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |||
| SoftGouraudShader | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |||
| HardFlatShader | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |||
| SoftSilhouetteShader | ✔️ |
一个完整的、可复制的渲染器组装示例(改造自 renderer_getting_started.md):
from pytorch3d.renderer import ( FoVPerspectiveCameras, look_at_view_transform, RasterizationSettings, BlendParams, MeshRenderer, MeshRasterizer, HardPhongShader, ) device = torch.device("cuda:0") # 1. 相机:围绕目标放置、看向原点 R, T = look_at_view_transform(2.7, 10, 20) cameras = FoVPerspectiveCameras(device=device, R=R, T=T) # 2. 光栅化设置:512x512 输出,无模糊,每像素 1 个面 raster_settings = RasterizationSettings( image_size=512, blur_radius=0.0, faces_per_pixel=1, ) # 3. 组合渲染器:光栅化 + 着色 renderer = MeshRenderer( rasterizer=MeshRasterizer(cameras=cameras, raster_settings=raster_settings), shader=HardPhongShader(device=device, cameras=cameras), ) # 4. 渲染:images 形状 (N, H, W, 4) images = renderer(meshes_world)若需要同时拿到深度图等中间结果,可改用MeshRendererWithFragments(renderer.py):images, fragments = renderer(meshes),depth = fragments.zbuf。
自定义 Shader 的编写范式
内置 Shader 的 docstring(如HardPhongShader)明确说明"只需传入 device 即可用默认值初始化",因为ShaderBase会自动创建PointLights与Materials。编写自定义 Shader 时建议遵循以下范式(与ShaderBase的子类契约一致):
- 继承
ShaderBase,在构造时接收device/cameras/lights/materials/blend_params; forward(fragments, meshes, **kwargs)内先cameras = super()._get_cameras(**kwargs)解析相机;- 用
meshes.sample_textures(fragments)取纹理,调用phong/gouraud/flat_shading或自写光照; - 用
hard_rgb_blend/softmax_rgb_blend/sigmoid_alpha_blend/ 自定义混合收尾; - 所有光照类模块都可通过 kwargs 覆盖,保证组合灵活性。
测试 tests/test_shader.py 的test_to验证了所有内置 Shader 在cpu → cuda迁移后,cameras/materials/lights的设备一致性——自定义 Shader 若新增非nn.Module的属性,也需要在to(device)中手动迁移,否则多 GPU 或设备切换时会出问题。
小结
pytorch3d.renderer.mesh.shader模块以ShaderBase为骨架、以"纹理采样 → 光照 → 混合"三段式为流水线,提供了覆盖 Phong/Gouraud/Flat 三种着色模型、硬/软两种混合策略,以及轮廓、深度、Splat 三种特殊用途的完整内置 Shader 族。无论是入门级的HardPhongShader快速出图,还是需要可微梯度支撑的SoftSilhouetteShader/SplatterPhongShader优化任务,都可以在此基础上组合或扩展。理解Fragments的四个字段与BlendParams的三个超参,是驾驭整个 Shader 体系的关键。
- 人工智能
- 深度学习
- 计算机视觉
- 图形学
【免费下载链接】pytorch3d
PyTorch3D is FAIR's library of reusable components for deep learning with 3D data
相关推荐
F3D 全屏后处理着色器(--final-shader)实战指南:从 GLSL 编写到渲染管线原理
F3D 全屏后处理着色器( final shader)实战指南:从 GLSL 编写到渲染管线原理 F3D 是一款快速、极简的开源 3D 查看器,其 final
3D渲染图形学桌面应用PyTorch3D 可微渲染器入门指南:从坐标约定到自定义 Shader 的完整实战
PyTorch3D 可微渲染器入门指南:从坐标约定到自定义 Shader 的完整实战 导读 本文以 PyTorch3D 官方笔记 renderer_gettin
人工智能深度学习计算机视觉图形学Aria2常见问题排查手册:从安装失败到下载异常的解决方案
Aria2常见问题排查手册:从安装失败到下载异常的解决方案 Aria2是一款高效的命令行下载工具,通过Aria2一键安装管理脚本(aria2.sh)可以快速部署
CLI运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考