6 光线追踪与动画

供应商没有提供有关加速结构性能以及动态几何如何影响光线追踪性能的实际数字的答案。仅提供有关如何使用加速结构标志的建议。我们在上一篇文章中提供了静态性能数据。现在是时候关注加速结构构建者了。

我们将画动画人物。因为所有角色都是独立的,所以他们都需要一个底层的加速结构。每个像素将查询两条光线,一条用于场景相交,另一条用于阴影相交。分辨率为1600×900。

加速结构构建器有很多配置参数,这就是表中数据很多的原因。我们还将 Vulkan 和 Direct3D12 结果拆分为不同的行。

  • FT (RT) – 启用快速跟踪标志的所有 BLAS 构建时间和场景跟踪时间(在圆括号中)。
  • FB (RT) – 快速构建时间和(RQ 场景跟踪时间)。
  • CS (RT) – 计算着色器 BLAS 构建时间(CS 场景跟踪时间)。
  • FTU (RT) – 启用快速跟踪标志的所有 BLAS 更新时间和(RQ 场景跟踪时间)。
  • FBU (RT) – 快速构建更新时间和(RQ 场景跟踪时间)。
  • CSU (RT) – 计算着色器 BLAS 更新时间(CS 场景跟踪时间)。
  • BLAS(暂存) ——BLAS 缓冲区和(暂存缓冲区大小)所需的内存。

有 52K 三角形模型的 81 个实例的结果。动画顶点/三角形的总数为 2.9M/4.2M。关节数为4212:

金融时报(RT)FB(RT)CS(RT)FTU(RT)FBU(RT)科罗拉多州立大学(RT)BLAS(划痕)
GeForce 2080 Ti (D3D12)16.9 毫秒(0.6 毫秒)15.7 毫秒(0.6 毫秒)9.1 毫秒(5.3 毫秒)3.7 毫秒(0.8 毫秒)1.2 毫秒(0.9 毫秒)2.1 毫秒(7.7 毫秒)255 MB (16 MB)
GeForce 2080 Ti (VK)17.1 毫秒(0.6 毫秒)15.1 毫秒(0.7 毫秒)8.7 毫秒(3.4 毫秒)3.8 毫秒(0.8 毫秒)1.5 毫秒(1.0 毫秒)1.8 毫秒(4.7 毫秒)255 MB (16 MB)
Radeon 6700 XT (D3D12)30.2 毫秒(1.2 毫秒)30.2 毫秒(1.2 毫秒)8.7 毫秒(4.8 毫秒)4.6 毫秒(1.3 毫秒)4.6 毫秒(1.3 毫秒)3.0 毫秒(6.1 毫秒)656 MB (872 MB)
Radeon 6700 XT (VK)223.0 毫秒(1.4 毫秒)73.0 毫秒(1.5 毫秒)8.9 毫秒(4.9 毫秒)4.6 毫秒(1.6 毫秒)4.6 毫秒(1.6 毫秒)3.2 毫秒(6.2 毫秒)656 MB (872 MB)
Radeon Vega 56 (macOS)185.5 毫秒(8.4 毫秒)185.6 毫秒(8.4 毫秒)16.8 毫秒(8.1 毫秒)21.5 毫秒(11.9 毫秒)21.7 毫秒(12.1 毫秒)4.54 毫秒(11.2 毫秒)355MB (355MB)
苹果 M1 (macOS)394.9 毫秒(29.2 毫秒)395.2 毫秒(29.0 毫秒)74.5 毫秒(42.6 毫秒)29.8 毫秒(34.5 毫秒)29.9 毫秒(34.8 毫秒)18.1 毫秒(49.2 毫秒)355MB (355MB)

这是我们所拥有的:

  • 为每一帧中的动画角色构建 BLAS 是不可能的。必须使用BLAS更新模式。否则,BLAS 构建器将显着降低 FPS。
  • AMD Vulkan BLAS 构建器比 Direct3D12 构建器慢得多。最好的 AMD BLAS 构建时间是 Nvidia 的两倍。
  • Apple M1 上的射线查询比 CS 解决方案快两倍,考虑到我们之前的测试没有差异,这很有趣。
  • Metal 着色语言中没有一致的缓冲存储器。这就是我们使用原子执行 BVH 更新步骤的原因。它会对性能产生负面影响。
  • AMD 的 BLAS 和 Scratch 缓冲区消耗大量内存。
  • 我们的 GPU BVH 构建器在完全重建模式下的性能优于任何可用的实现。

现在让我们通过增加字符数来找到光线追踪限制。这次我们将使用 1500 个三角形的简化模型。角色总数为2401个。场景中的三角形数量为3.6M。关节数量为125K。CPU 独立地为所有角色设置动画变得越来越困难,因此我们将跨多个实例重用联合转换。

金融时报(RT)FB(RT)CS(RT)FTU(RT)FBU(RT)科罗拉多州立大学(RT)BLAS(划痕)
GeForce 2080 Ti (D3D12)15.8 毫秒(0.6 毫秒)14.5 毫秒(0.6 毫秒)8.3 毫秒(6.2 毫秒)7.0 毫秒(0.8 毫秒)1.5 毫秒(0.9 毫秒)1.6 毫秒(6.7 毫秒)224 MB (15 MB)
GeForce 2080 Ti (VK)16.3 毫秒(0.6 毫秒)14.6 毫秒(0.7 毫秒)8.1 毫秒(4.5 毫秒)4.8 毫秒(0.8 毫秒)1.1 毫秒(1.0 毫秒)1.5 毫秒(4.6 毫秒)224 MB (15 MB)
Radeon 6700 XT (D3D12)55.9 毫秒(1.5 毫秒)55.8 毫秒(1.5 毫秒)7.5 毫秒(6.0 毫秒)3.0 毫秒(1.5 毫秒)3.0 毫秒(1.5 毫秒)2.4 毫秒(6.7 毫秒)559 MB (742 MB)
Radeon 6700 XT (VK)2000 毫秒(5.2 毫秒)1070 毫秒(5.3 毫秒)7.8 毫秒(6.3 毫秒)18.5 毫秒(1.7 毫秒)18.7 毫秒(1.8 毫秒)2.6 毫秒(7.0 毫秒)559 MB (742 MB)
Radeon Vega 56 (macOS)1840 毫秒(9.8 毫秒)1800 毫秒(9.7 毫秒)14.7 毫秒(10.3 毫秒)297.1 毫秒(10.5 毫秒)297.1 毫秒(10.1 毫秒)3.6 毫秒(11.6 毫秒)304 MB (325 MB)
苹果 M1 (macOS)1600 毫秒(33.1 毫秒)1600 毫秒(32.8 毫秒)63.4 毫秒(58.3 毫秒)271.1 毫秒(36.3 毫秒)273.1 毫秒(36.9 毫秒)14.9 毫秒(62.7 毫秒)304 MB (325 MB)
  • 实例数量不会影响 Nvidia 和 Tellusim BVH 构建器时间。只有三角形的数量才可以。
  • AMD Vulkan 和 Metal 时序没有错误。BLAS 构建时间以秒为单位。
  • Vulkan API 在单个 API 调用中执行所有 BLAS 构建/更新。但 AMD 正在 Vulkan 上以非并行方式执行 BLAS 构建。Metal API 也在做同样的事情。
  • 在第二次测试中,Windows 上 Nvidia GPU 的利用率仅为 50-70%。但它在 Linux 上全速工作。

以下是在 BLAS 更新模式下捕获的这两个测试的视频。否则,CS 解决方案比 HW 光线追踪更快。

10000 个 BLAS 实例怎么样?它将有 520K 个关节和 15M 个三角形。CPU 无法很好地处理 50 万个关节。所以我们将停止动画。Nvidia 要求所有实例有 1 GB BLAS 缓冲区。AMD 的 BLAS 缓冲区为 2.3 GB,暂存缓冲区需要额外的 3 GB 内存。我们不计算预转换的顶点(每个顶点 48 字节的情况下为 600 MB)。BLAS 构建器将在更新模式下工作。由于利用率不足,Nvidia 2080 Ti 在 2K 视频模式下提供 43 FPS,但整体时序已经超过 10 毫秒,因此并不比 100 FPS 好。Radeon 6700 XT 在 100% 利用率下显示 49 FPS。

每个动画、变形或变形的对象都需要用于顶点和 BLAS 数据的内存。有时可能是大量数据和大量内存带宽。并且它会影响性能。相比之下,光栅化除了原始几何和关节变换之外不需要任何东西。所有转换后的数据都直接进入光栅化器。Tellusim 引擎完全由 GPU 驱动,包括动画混合树。包含 10K 动画角色的视频在 Nvidia 2080 Ti 上以 135 FPS 的速度运行。Radeon 6700 XT 的性能超过 120 FPS。它可以在 Apple M1 移动设备上以20 FPS 和动画运行。每个角色都有独特的动画并投射出全局阴影。

如何使用Tellusim Engine制作10K动画角色场景:

// load object
ObjectMesh object(scene);
if(!object.load("object.glb", &material, ObjectMesh::FlagTriSkiBasTex | ObjectMesh::FlagAnimation)) return 1;

// create nodes
uint32_t size = 100;
Array nodes(size * size);
for(uint32_t i = 0; i < nodes.size(); i++) {
  nodes[i] = NodeObject(graph, object);
  nodes[i].setGlobalTransform(Matrix4x3d::translate((i % size) * step, (i / size) * step, 0.0));
}

// somewhere in the update loop
Random random(0);
for(NodeObject node : nodes) {
  ObjectFrame frame;
  uint32_t index = random.geti32(0, object.getNumAnimations() - 1);
  frame.append(ObjectFrame(object.getAnimation(index), time + random.getf32(0.0f, 32.0f)));
  node.setFrame(frame);
}
scene.updateGraph(graph);
graph.updateObjectTree();
graph.updateNodes();

这就是所需要的全部。CPU 只生成一些随机数。其他一切都委托给 GPU。

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注