【DX12龙书】第七章 利用Direct3D绘制几何体(续)

本章是 Direct3D 12 绘制体系的进阶核心内容,承接前序章节的基础绘制流程,构建了高性能、可复用的工业级渲染架构,完整补充了根签名的全量用法,实现了程序化 3D 几何体生成与 CPU 端动态顶点动画,为后续复杂场景渲染、特效实现奠定了核心框架。章节核心解决了 CPU-GPU 同步的性能瓶颈,规范了渲染数据的分层管理,同时完善了 Direct3D 12 核心资源的使用规则。

各小节内容概况

7.1 帧资源

本小节核心解决每帧 Flush 命令队列导致的 CPU-GPU 空闲与性能浪费问题。

  • 问题根源:前序渲染流程中,每帧结束必须强制同步 CPU 与 GPU,等待 GPU 完成当前帧所有指令后才能重置命令分配器、更新常量缓冲,导致 CPU 和 GPU 频繁出现空闲,硬件利用率极低。

  • 核心方案:设计环形帧资源数组(通常设置 3 个元素),每个帧资源封装了单帧渲染专属的、CPU 可修改的全部资源,包括:帧专属命令分配器、逐 Pass / 逐物体常量缓冲、标记帧指令执行进度的围栏值。

  • 运行逻辑:CPU 无需等待当前帧 GPU 执行完成,即可循环取用数组中未被 GPU 占用的帧资源,提前处理后续 2 帧的资源更新、命令列表录制与提交;仅当 CPU 超过 GPU 处理进度 2 帧以上时,才需要等待 GPU 同步,确保 GPU 命令队列始终非空,最大化 CPU-GPU 并行利用率。

  • 补充内容:讲解了帧资源的生命周期管理、基于围栏的帧同步实现细节,以及 CPU 空闲周期的复用逻辑(可用于 AI、物理等游戏逻辑计算)。

7.2 渲染项

本小节核心实现单物体绘制全量数据的标准化封装与管理,简化多物体场景的渲染逻辑。

  • 渲染项定义:一个轻量级结构体,封装了提交一次完整绘制调用所需的全部参数,核心字段包括:物体世界矩阵、脏标记NumFramesDirty、物体常量缓冲索引、关联的几何体数据指针、图元拓扑类型、DrawIndexedInstanced所需的索引计数、起始索引 / 顶点位置等。

  • 核心价值:

    1. 统一管理场景中所有可绘制物体,将绘制逻辑与物体数据解耦;

    2. 支持多渲染项共享同一份几何体数据,减少显存冗余;

    3. 通过脏标记优化常量缓冲更新:仅当物体数据发生变化时,才会更新所有帧资源中对应的常量缓冲,避免无效的 CPU-GPU 数据传输。

  • 工程实践:按管线状态对象(PSO)对渲染项进行分类管理(如不透明渲染项、透明渲染项),减少渲染过程中的管线状态切换,提升性能。

7.3 渲染 Pass 常量

本小节核心实现常量缓冲的按更新频率分组优化,减少常量缓冲的更新开销。

  • 核心设计:将着色器使用的常量数据,按更新频率拆分为两类,分别对应独立的常量缓冲:

    1. 逐物体常量(Object Constants):每个物体独有,如世界矩阵,仅当物体发生位移、旋转、缩放时才需要更新;

    2. 逐 Pass 常量(Pass Constants):整个渲染 Pass 内所有物体共享,每帧仅需更新一次,包括视图 / 投影 / 视口投影矩阵及其逆矩阵、相机世界位置、渲染目标尺寸、近远平面、游戏总时间 / 帧间隔时间等。

  • 工程实现:讲解了两类常量缓冲的结构体定义、CPU 端更新逻辑,以及对应的着色器代码、根签名配置修改;同时给出性能建议:着色器中使用的常量缓冲数量建议控制在 5 个以内,避免硬件性能损耗。

7.4 形状几何体

本小节核心实现基础 3D 几何体的程序化生成,提供可复用的几何体生成工具。

  • 工具设计:封装GeometryGenerator工具类,通过内嵌的MeshData结构体存储网格的完整数据,包括顶点属性(位置、法线、切线、UV 坐标)和索引数据,支持 16 位 / 32 位索引格式转换。

  • 核心几何体生成算法:

    1. 网格(Grid):通过行列数细分生成平面网格,可用于地形、水面、平面场景;

    2. 圆柱体(Cylinder):支持上下底面自定义半径(可实现圆台、圆锥),通过切片数(Slice)和堆叠数(Stack)控制网格精度,分为侧面、顶面、底面三部分独立生成;

    3. 球体(Sphere):基于三角函数的环式生成算法,通过切片和堆叠数控制球面精度,适配不同的平滑度需求。

  • 适用场景:生成的几何体可用于调试可视化、碰撞体展示、天空盒、地形基底、基础场景搭建等。

7.5 形状Demo

本小节将前 4 节的核心内容整合为完整可运行的渲染 Demo,落地全流程渲染架构。

  • 核心实现内容:

    1. 帧资源的初始化、环形循环管理与围栏同步;

    2. 多渲染项的创建与管理,实现盒子、球体、圆柱体、网格等多个物体的同场景渲染,支持几何体数据共享;

    3. 逐 Pass / 逐物体常量缓冲的按帧更新逻辑,基于脏标记的优化实现;

    4. 适配新常量结构的根签名、管线状态对象(PSO)配置;

    5. 完整的渲染循环:命令列表录制、提交、帧同步的全流程实现。

7.6 细探根签名

本小节完整讲解 Direct3D 12 根签名的全量参数类型、配置规则与性能优化方案,补充前序章节未覆盖的核心用法。

  1. 根参数的三种完整类型

  2. 表格

  1. 核心配置规则

    • 硬限制:根签名的总大小不得超过 64 个 DWORD;

    • 着色器可见性:可限制根参数仅对特定着色器阶段可见(顶点 / 像素 / 几何 / 外壳 / 域着色器),减少不必要的资源广播,优化性能;

    • 描述符范围配置:详细讲解了描述符类型、数量、起始着色器寄存器、寄存器空间、描述符偏移量的配置规则,支持寄存器空间的资源隔离。

  2. 性能优化与版本化机制

    • 硬件会为每个绘制调用保存根参数的快照,因此应尽量减小根签名的大小,降低快照复制开销;

    • 建议设计兼容多个 PSO 的通用根签名,减少渲染过程中的根签名切换,避免管线刷新带来的性能损耗;

    • 校验规则:创建 PSO 时会强制校验根签名与着色器输入的匹配性,着色器使用的资源必须在根签名中声明,反之无强制要求。

7.7 地形与水波模拟

本小节核心实现CPU 端顶点动画与动态顶点缓冲的工程应用,是前序网格几何体的进阶延伸。

  • 核心内容:

    1. 动态顶点缓冲:讲解了动态顶点缓冲的创建、CPU 内存映射、数据更新与 GPU 上传的完整流程,实现运行时顶点数据的实时修改;

    2. 地形生成:基于正弦函数叠加算法生成高度图,修改网格顶点的 Y 轴坐标,实现起伏的山地地形;

    3. 水波动画:通过时间变量驱动的正弦函数,实时修改水面网格的顶点位置、法线数据,实现动态波动的水面效果;

  • 补充说明:分析了动态顶点缓冲的性能开销,明确其适用场景为顶点数据高频变化的动画效果,不适合静态几何体。

7.8 小结

对全章核心内容进行复盘,再次强调:

  • 帧资源对 CPU-GPU 并行利用率的提升价值;

  • 渲染项与常量分组对渲染架构的规范化作用;

  • 根签名三类参数的用法、区别与性能优化原则;

  • 动态顶点缓冲的实现逻辑与顶点动画的核心思路。


核心知识点列表

1. 渲染性能与 CPU-GPU 同步

  • 帧资源(Frame Resources)的设计思想、环形数组实现与生命周期管理

  • 基于围栏(Fence)的帧级同步机制,替代每帧 Flush 的优化方案

  • CPU-GPU 并行化渲染循环的设计,最大化硬件利用率

  • 渲染资源的帧隔离设计,避免 GPU 执行过程中资源被 CPU 修改

2. 渲染数据管理架构

  • 渲染项(Render Item)的封装设计,单绘制调用全量数据的标准化管理

  • 按更新频率的常量缓冲分层设计:逐物体常量、逐 Pass 常量

  • 脏标记(Dirty Flag)机制,优化常量缓冲的更新频率

  • 多渲染项的分类管理,基于 PSO 的渲染分组优化

3. 程序化几何体生成

  • GeometryGenerator工具类与MeshData网格数据结构设计

  • 网格、圆柱体、球体的程序化生成算法

  • 顶点属性(位置、法线、切线、UV)的计算与索引缓冲的构建

  • 几何体数据的复用与显存优化

4. 根签名进阶与优化

  • 根签名三类根参数的区别、用法与适用场景

  • 根签名的 64 DWORD 硬件限制,各参数的开销计算

  • 着色器可见性的配置与性能优化

  • 描述符范围与寄存器空间的配置规则

  • 根签名的版本化机制,通用根签名的设计原则

  • 根签名与着色器的校验规则

5. 动态顶点缓冲与顶点动画

  • 动态顶点缓冲的创建、映射与更新全流程

  • CPU 端顶点动画的实现逻辑,基于时间的顶点数据修改

  • 地形高度图生成算法,水波动画的数学实现

  • 动态顶点缓冲的性能开销与适用场景边界


难点笔记

渲染架构

总体概述

第七章 利用Direct3D绘制几何体(续)-diagram

Shape Demo的类图如上。

地形水面模拟Demo,因为需要按波动方程动态更新水面的顶点位置,所以顶点也被整合到帧资源中。

帧资源(FrameResource)

现在是单线程,游戏业务逻辑(App::Update)与渲染命令编制(App::Draw)是在主线程串行执行。

帧资源是一个长度为3的数组,通过对Index取模,实现类似循环列表。

在帧开始时,先去获取帧资源,并且检查它的FenceValue。如果帧资源是空闲的,即可进行当帧的工作;如果帧资源还在使用的状态,CPU执行将被阻塞。所以逻辑可以领先渲染3帧,这样是的CPU和GPU都在同时独立地不相互影响地工作。

第七章 利用Direct3D绘制几何体(续)-diagram-1

商业引擎的Tick,都是多线程。像Unity,会拆分出负责业务逻辑的主线程,和1或n条用于编制渲染命令的渲染线程;而Unreal Engine,更是对渲染进行更细致拆分,有编制渲染命令的渲染线程和具体与图形API打交道的RHI线程。

几何体类(MeshGeometry)

Shape Demo中所有几何体(盒子、栅格、圆柱、球体),在ShapesApp::BuildShapeGeometry,打包为一个大的顶点/索引缓冲,对应一个MeshGeometry,设置到mGeometries中。

每个几何体对应一个SubmeshGeometry,里面记录顶点/索引偏移,可以独立绘制

第七章 利用Direct3D绘制几何体(续)-diagram-2

Submesh的索引和顶点的信息,通过RenderItem,传递给ID3D12GraphicsCommandList的DrawIndexedInstanced进行绘制。但没有使用实例渲染,所以InstanceCount传入1,StartInstanceLocation传入0,即只画一个实例。

可以改进之处

通过RenderDoc截帧发现一些问题。

  • 在视椎体外的Mesh的RenderItem也被传入管线进行绘制,经过顶点着色器后被剔除

第七章 利用Direct3D绘制几何体(续)-image

可以通过软光栅、GPUScene等技术,在CPU侧或GPU侧进行剔除

  • 虽然共用Mesh几何数据,但还是一个一个地渲染Mesh。

第七章 利用Direct3D绘制几何体(续)-image-1

应该通过实例化渲染,这样能节省DC

posted @ 2026-04-11 22:53  斌伯  阅读(10)  评论(0)    收藏  举报