☰
DirectX 12 Raytracing(DXR)实战示例全解析:DirectX-Graphics-Samples 中的教程与高级场景指南
2026/10/6 18:45:19 网站建设 项目流程
  • 示例工程

【免费下载链接】DirectX-Graphics-Samples

This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.

项目地址:https://gitcode.com/gh_mirrors/di/DirectX-Graphics-Samples
点击查看免费下载

本文围绕当前仓库中 Samples/Desktop/D3D12Raytracing/readme.md 这一官方集合说明展开,系统梳理其中 7 个教程示例与 2 个高级示例的技术脉络,并深入剖析每个示例背后的 DXR 管线搭建、着色器表布局、Shader Execution Reordering、Opacity Micromaps 等关键概念。读完本文,你将掌握 DXR 从零开始的入门路径、各示例的运行方式与命令行参数,并理解每个示例对应的源码实现细节,可作为学习与二次开发的路线图。

一、集合概述:一套循序渐进的 DXR 入门与进阶教程

该示例集合的定位是DirectX Raytracing(DXR)的入门介绍。集合被划分为两类:

  • 教程示例(Tutorial Samples):共 7 个,每个示例只引入少量新的 DXR 概念,建议按编号顺序学习;
  • 高级示例(Advanced Samples):共 2 个,演示更复杂的技术与光追应用场景。

所有示例均基于 C++(页面元数据中声明languages: cpp),并假设读者已具备 Direct3D 12 编程基础。示例工程统一位于 Samples/Desktop/D3D12Raytracing/src 目录,其中D3D12Raytracing.slnx为集合解决方案文件,各示例的详细说明分别存放在各自子目录的readme.md中。

二、运行环境与硬件要求

官方明确列出的最低要求非常简洁:

  • GPU 与驱动必须支持 DirectX 12 Ultimate(包含 DXR、Mesh Shader、Sampler Feedback 等特性)。

部分示例还额外声明了 Windows 版本要求,归纳如下:

示例Windows 版本要求
Hello World、Simple Lighting、Procedural GeometryWindows 10 2018 年 10 月更新或更高
Library SubobjectsWindows 10 2019 年 5 月更新或更高
Hello SER、Sakura Forest SER、Opacity MicromapsWindows 10 2019 年 10 月更新或更高

各示例的启动方式一致:直接运行对应目录下的 exe,可附带可选参数[-forceAdapter <ID>]来指定使用哪块 GPU 适配器创建设备,默认使用适配器 0(该参数行为在 D3D12RaytracingHelloWorld/readme.md 等子文档中均有说明)。

三、已知问题:构建失败与修复线索

官方文档特别提示,部分示例在不同 Visual Studio 版本下可能编译失败,典型错误包括:

  • The system cannot find the path specified. *.hlsl.h
  • error MSB6006: "dxc.exe" exited with code 1.

这两类错误均与 HLSL 着色器经dxc.exe编译生成*.hlsl.h头文件的构建步骤相关,常见诱因是 dxc 编译环境变量或依赖路径配置差异。官方给出的是排查方向而非逐个打补丁:可参考上游仓库对应 issue(编号为 #657)中社区讨论的修复做法,并结合本机 Visual Studio 版本调整dxc.exe的调用环境。本文不再展开具体补丁细节,遇到该问题时建议优先核对构建工具链版本与 HLSL 编译目标。

四、教程示例逐个深入

1. Hello World:搭建第一条光线追踪管线

D3D12RaytracingHelloWorld 是整个集合的起点,演示了如何搭建光追管线并在屏幕空间渲染一个三角形。

管线初始化(Raytracing Initialization)

初始化集中在CreateDeviceDependentResources()中完成,包含四个核心步骤:

  1. 查询支持光追的 device 与 command list(对应 DXR 的 device 特性查询与ID3D12GraphicsCommandList4接口获取);
  2. 创建光追加速结构(Acceleration Structure),即场景的 BVH 数据;
  3. 创建光线追踪管线状态对象(Raytracing Pipeline State Object,RTPSO);
  4. 创建着色器表(Shader Table)与一块用于接收光追输出的 2D 纹理资源。

渲染流程(Rendering)

每帧渲染发生在OnRender()中,流程为:

  • 以与后缓冲(backbuffer)分辨率一致的 2D 网格尺寸执行DispatchRays();
  • 将光追结果复制到后缓冲,最后呈现到屏幕。

该示例只实现了三个着色器:ray generation(射线生成)、closest hit(最近命中)、miss(未命中)。射线生成着色器通过DispatchRays()对整个渲染目标执行;若某像素对应的射线索引落在模板窗口(stencil window)内,则向场景发射射线;窗口外的像素则直接基于射线的 xy 分派坐标(自左上角起算)输出颜色。命中三角形的射线输出射线命中位置在该三角形内的重心坐标(barycentric coordinates),未命中的射线输出黑色。

标题栏信息(UI)

运行时标题栏展示:示例名称、FPS、每秒百万主光线数(Million Primary Rays/s,基于 FPS 换算)、GPU[ID]: 名称。

2. Simple Lighting:动态相机与漫反射光照

D3D12RaytracingSimpleLighting 在 Hello World 基础上演示两件事:为动态透视相机生成射线、为立方体计算来自动态点光源的简单漫反射着色。它假设读者已掌握 Hello World 引入的 DXR 概念。

着色器分工

  • 射线生成着色器:为后缓冲中每个像素对应的分派射线计算世界空间相机射线。做法是取像素屏幕位置,经逆相机视图投影矩阵变换得到世界空间射线方向;
  • 最近命中着色器:计算射线命中点的漫反射着色。通过"命中点指向光源的方向"与"命中三角形法线"的点积,再乘以光源颜色与立方体颜色得到;
  • 未命中着色器:直接存储背景色。

着色器访问的资源

输入数据全部来自常量缓冲区与缓冲资源,共两块常量缓冲区:

  • CubeConstantBuffer:存放立方体颜色,经着色器记录(shader record)传入命中着色器;
  • SceneConstantBuffer:存放场景级相机与光源参数,经全局根签名(global root signature)暴露。

三角形法线从显式传入最近命中着色器的索引缓冲与顶点缓冲中读取:先从16 位索引缓冲加载命中三角形的三个顶点索引,再用索引在顶点缓冲中取法线——每个三角形顶点均重复存储一份法线。

控制:ALT+ENTER在窗口与全屏间切换。

3. Procedural Geometry:程序化几何与着色器表索引

D3D12RaytracingProceduralGeometry 是集合中技术含量最高的教程之一,讲解用 intersection shader(相交着色器)实现程序化几何,并引入:

  • 覆盖多几何体与多个底层加速结构(bottom-level AS,BLAS)的扩展着色器表布局与索引;
  • 射线递归与两种射线类型(radiance 与 shadow 射线)。

场景构成

场景由三角形几何与程序化几何混合构成:三角形几何为地面平面四边形;程序化几何为地面以上的所有物体。由于 DXR 不允许在单个 BLAS 内混用三角形与程序化几何类型,两者被分别存放在独立的 BLAS 中。

程序化几何的定义方式

程序化几何由**轴对齐包围盒(AABB)**加一个 intersection shader 定义。AABB 定义了 BLAS 对象空间内的几何范围;射线命中 AABB 时,系统调用该 AABB 命中组(hit group)关联的 intersection shader,由其判断射线是否与内部几何相交,若相交则调用ReportHit()。

示例实现了三个 intersection shader,每个 shader 下挂多个不同的图元:

Intersection Shader几何内容
Analytic geometry(解析几何)多个球体与一个轴对齐盒子
Volumetric geometry(体几何)Metaballs(元球)等值面,即"blobs"
Signed distance geometry(有符号距离几何)六个不同图元与一个分形金字塔

射线/图元测试均假定图元局部空间,AABB 隐含为<-1,1>。应用为每个图元施加缩放变换以放大 AABB。由于射线只能以**世界空间(WorldRay*()内建函数)**或 **BLAS 对象空间(ObjectRay*()内建函数)**获取,应用需向 intersection shader 传入 BLAS 对象空间与图元局部空间之间互相转换的变换矩阵。shader 被调用时先将对象空间射线变换到局部空间,再调用射线/图元相交测试,例如MyIntersectionShader_AnalyticPrimitive中的RayAnalyticGeometryIntersectionTest:

// Get ray in AABB's local space. Ray GetRayInAABBPrimitiveLocalSpace() { PrimitiveInstancePerFrameBuffer attr = g_AABBPrimitiveAttributes[l_aabbCB.instanceIndex]; // Retrieve a ray origin position and direction in bottom level AS space // and transform them into the AABB primitive's local space. Ray ray; ray.origin = mul(float4(ObjectRayOrigin(), 1), attr.bottomLevelASToLocalSpace).xyz; ray.direction = mul(ObjectRayDirection(), (float3x3) attr.bottomLevelASToLocalSpace); return ray; } [shader("intersection")] void MyIntersectionShader_AnalyticPrimitive() { Ray localRay = GetRayInAABBPrimitiveLocalSpace(); AnalyticPrimitive::Enum primitiveType = (AnalyticPrimitive::Enum) l_aabbCB.primitiveType; float thit; ProceduralPrimitiveAttributes attr; if (RayAnalyticGeometryIntersectionTest(localRay, primitiveType, thit, attr)) ...

测试返回命中时间thit与局部空间表面法线后,shader 需将法线变换到世界空间再交给ReportHit()决定接受或拒绝命中:

... if (RayAnalyticGeometryIntersectionTest(localRay, primitiveType, thit, attr)) { PrimitiveInstancePerFrameBuffer aabbAttribute = g_AABBPrimitiveAttributes[l_aabbCB.instanceIndex]; attr.normal = mul(attr.normal, (float3x3) aabbAttribute.localSpaceToBottomLevelAS); attr.normal = normalize(mul((float3x3) ObjectToWorld3x4(), attr.normal)); ReportHit(thit, /*hitKind*/ 0, attr); } }

为提高效率,示例在相交测试中直接按合法全局时间范围与RayFlags()校验命中,而不是立即返回后依赖ReportHit()复验并重做全部设置数学。这也让示例无需为ReportHit()之后的求值定义任何命中着色器:

// Test if a hit is culled based on specified RayFlags. bool IsCulled(in Ray ray, in float3 hitSurfaceNormal) { float rayDirectionNormalDot = dot(ray.direction, hitSurfaceNormal); bool isCulled = ((RayFlags() & RAY_FLAG_CULL_BACK_FACING_TRIANGLES) && (rayDirectionNormalDot > 0)) || ((RayFlags() & RAY_FLAG_CULL_FRONT_FACING_TRIANGLES) && (rayDirectionNormalDot < 0)); return isCulled; } // Test if a hit is valid based on specified RayFlags and <RayTMin, RayTCurrent> range. bool IsAValidHit(in Ray ray, in float thit, in float3 hitSurfaceNormal) { return IsInRange(thit, RayTMin(), RayTCurrent()) && !IsCulled(ray, hitSurfaceNormal); }

三类程序化几何的算法要点

  • 解析几何:直接求射线与球体、AABB 的解析解;
  • 体几何(Metaballs):等值面位于点源形成的势场内。每个源有其影响范围,势场由随到源中心距离平滑衰减的势多项式函数定义;多源时各源贡献的势值求和;等值面由应用指定的阈值定义。求交时射线在 AABB 内沿场逐步推进(ray marching),逐步累加总势能,直到触及阈值或退出 AABB;
  • 有符号距离几何:由有符号距离函数定义,函数返回从特定位置到几何体的最近距离。由于该距离未必沿射线方向,求交需迭代式 ray marching 并在每步计算有符号距离,直到足够接近表面——即sphere tracing(球体追踪)算法,其收敛速度快于固定步长推进。有符号距离函数支持不同逻辑运算符与变换,可组合简单图元形成复杂几何(分形金字塔即由此而来)。

几何更新

程序化几何的动画或修改只要 AABB 不变就无需更新加速结构。示例即以此方式动画化部分几何:每帧仅更新传入 shader 的旋转变换矩阵;Metaballs 场景还会传入应用时间,在各自 AABB 内动画化场源位置。

两种射线类型

  • Radiance(辐射)射线:用于TraceRay()的主射线与次级反射射线调用,为每次几何命中计算着色并在多次弹跳间聚合颜色贡献;
  • Shadow(阴影)射线:仅做朝向光源的可见性/遮挡测试,只需返回是否命中。实践中仅需 miss shader,不需要 any hit 与 closest hit shader——初始化射线载荷为"命中"标记,并用RayFlags()跳过除 miss 之外的所有 shader:
// Initialize shadow ray payload. // Set the initial value to true since closest and any hit shaders are skipped. // Shadow miss shader, if called, will set it to false. ShadowRayPayload shadowPayload = { true }; TraceRay(g_scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_FORCE_OPAQUE // ~skip any hit shaders | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER, // ~skip closest hit shaders, ...

若射线未命中任何几何,miss shader 将布尔值置为 false:

[shader("miss")] void MyMissShader_ShadowRay(inout ShadowRayPayload rayPayload) { rayPayload.hit = false; }

着色器表与索引(重点)

射线命中几何或需调用 miss shader 时,GPU 按下式索引应用提供的着色器表:

Miss shader table index = MissShaderIndex ~ from shader: TraceRay() Hit group shader table index = RayContributionToHitGroupIndex ~ from shader: TraceRay() + MultiplierForGeometryContributionToHitGroupIndex ~ from shader: TraceRay() * GeometryContributionToHitGroupIndex ~ system generated index of geometry in BLAS + InstanceContributionToHitGroupIndex ~ from BLAS instance desc

由于每种射线类型在 shader 中执行不同动作、每种几何也需要独立 shader 记录,再加上同类型下多个几何变体通过局部根签名传参(又各需一条 shader 记录),示例最终形成如下布局:

  • Miss shader 表:两条记录,每种射线类型一条:
Shader table - MissShaderTable | [0]: MyMissShader ~ Radiance ray | [1]: MyMissShader_ShadowRay ~ Shadow ray
  • Hit group shader 表:每条几何两条记录(每射线类型一条),示例会在调试输出中完整打印该表,作为设置索引参数的参考。前几条记录如下:
Shader table - HitGroupShaderTable: | [0]: MyHitGroup_Triangle ~ Triangle geometry in 1st BLAS | [1]: MyHitGroup_Triangle_ShadowRay | [2]: MyHitGroup_AABB_AnalyticPrimitive ~ 1st Analytic AABB geometry in 2nd BLAS | [3]: MyHitGroup_AABB_AnalyticPrimitive_ShadowRay | [4]: MyHitGroup_AABB_AnalyticPrimitive ~ 2nd Analytic AABB geometry in 2nd BLAS | [5]: MyHitGroup_AABB_AnalyticPrimitive_ShadowRay | [6]: MyHitGroup_AABB_VolumetricPrimitive ~ 1st Volumetric AABB geometry in 2nd BLAS | [7]: MyHitGroup_AABB_VolumetricPrimitive_ShadowRay | [8]: MyHitGroup_AABB_SignedDistancePrimitive ~ 1st Signed distance AABB geometry in 2nd BLAS | [9]: MyHitGroup_AABB_SignedDistancePrimitive_ShadowRay | ..

据此,各索引参数的取值为:

  • MissShaderIndex:radiance 射线为 0,shadow 射线为 1;
  • RayContributionToHitGroupIndex:radiance 与 shadow 分别为 0 和 1(同一几何的两种射线记录在表中相邻存储);
  • MultiplierForGeometryContributionToHitGroupIndex:为 2(每个几何 ID 后紧邻两条记录);
  • GeometryContributionToHitGroupIndex:系统为每个 BLAS 内几何生成的几何 ID,直接对应应用传入的GeometryDesc数组顺序,即{0, 1, 2, ...};
  • InstanceContributionToHitGroupIndex:BLAS 实例之间的偏移。三角形几何 BLAS 在最前,故为 0;AABB BLAS 设为 2(三角形 BLAS 的平面几何占用了 radiance 与 shadow 两条记录)。

索引示例:shadow 射线命中第二个 AABB 几何(GeometryContributionToHitGroupIndex = 1,位于第二个 BLAS,InstanceContributionToHitGroupIndex = 2),则命中组着色器表索引为 5:

1 // ~ RayContributionToHitGroupIndex - from TraceRay() + 2 // ~ MultiplierForGeometryContributionToHitGroupIndex - from TraceRay() * 1 // ~ GeometryContributionToHitGroupIndex - from runtime, 2nd geometry => ID:1 + 2 // ~ InstanceContributionToHitGroupIndex - from BLAS instance desc

控制:ALT+ENTER窗口/全屏;C开关相机动画;G开关几何动画;L开关光源动画。标题栏额外显示DispatchRays()的 GPU 执行时间。

4. Library Subobjects:在 DXIL 库中声明状态对象子对象

D3D12RaytracingLibrarySubobjects 是对 Simple Lighting 的改造,演示如何在DXIL 库的 HLSL 着色器代码中直接定义D3D12_STATE_SUBOBJECTS,并在 C++ 侧用其创建光追状态对象。这种子对象声明方式更简单、模块化且可复用——着色器与其属性封装在同一个 shader 内。

对 Simple Lighting 的改动思路

原示例除局部根签名外,都在状态对象作用域使用了隐式默认子对象。改为库子对象后,除 DXIL 库之外的所有子对象都可移除并移入库内(成为库子对象)。由于所有 shader 都在同一个 DXIL 库中,库作用域上现有的隐式默认子对象会作用于库内全部 shader。

在 HLSL 中声明子对象

库子对象(含局部根签名关联)在Raytracing.hlsl中用编译期内建语法定义:

GlobalRootSignature MyGlobalRootSignature = { "DescriptorTable( UAV( u0 ) )," // Output texture "SRV( t0 )," // Acceleration structure "CBV( b0 )," // Scene constants "DescriptorTable( SRV( t1, numDescriptors = 2 ) )" // Static index and vertex buffers. }; LocalRootSignature MyLocalRootSignature = { "RootConstants( num32BitConstants = 4, b1 )" // Cube constants }; TriangleHitGroup MyHitGroup = { "", // AnyHit "MyClosestHitShader", // ClosestHit }; SubobjectToExportsAssociation MyLocalRootSignatureAssociation = { "MyLocalRootSignature", // subobject name "MyHitGroup" // export association }; RaytracingShaderConfig MyShaderConfig = { 16, // max payload size 8 // max attribute size }; RaytracingPipelineConfig MyPipelineConfig = { 1 // max trace recursion depth };

C++ 侧导出子对象

库中子对象名需从库子对象中导出(不做重命名),之后所有导出子对象在CreateStateObject调用期间被处理、关联被解析。导出在D3D12RaytracingLibrarySubobjects.cpp中完成:

// Library subobject names const wchar_t* D3D12RaytracingLibrarySubobjects::c_globalRootSignatureName = L"MyGlobalRootSignature"; const wchar_t* D3D12RaytracingLibrarySubobjects::c_localRootSignatureName = L"MyLocalRootSignature"; const wchar_t* D3D12RaytracingLibrarySubobjects::c_localRootSignatureAssociationName = L"MyLocalRootSignatureAssociation"; const wchar_t* D3D12RaytracingLibrarySubobjects::c_shaderConfigName = L"MyShaderConfig"; const wchar_t* D3D12RaytracingLibrarySubobjects::c_pipelineConfigName = L"MyPipelineConfig"; { lib->DefineExport(c_globalRootSignatureName); lib->DefineExport(c_localRootSignatureName); lib->DefineExport(c_localRootSignatureAssociationName); lib->DefineExport(c_shaderConfigName); lib->DefineExport(c_pipelineConfigName); lib->DefineExport(c_hitGroupName); }

从库字节码直接创建根签名

由于库内已定义唯一的全局根签名,可直接用CreateRootSignatureAPI 从库字节码创建(此处当作计算根签名使用):

// A unique global root signature is defined in hlsl library g_pRaytracing. For such scenario we can create // compute root signature can directly from the library bytecode, using CreateRootSignature API. ThrowIfFailed(device->CreateRootSignature(1, g_pRaytracing, ARRAYSIZE(g_pRaytracing), IID_PPV_ARGS(&m_raytracingGlobalRootSignature)));

5. Hello Shader Execution Reordering:最小化 SER 演示

D3D12RaytracingHelloShaderExecutionReordering 是 Hello World 的微调版本,用于最小化演示Shader Execution Reordering(SER)。实现方式很直接:对全屏四边形做光追,射线执行人为构造的工作负载(一个循环),其中部分射线执行更昂贵的循环;做额外工作的射线渲染为白色。SER 被用来尝试按工作负载成本对线程重排序。

Raytracing.hlsl中有一批可调整的#define,便于实验不同行为。由于示例在启动时编译着色器,因此两次运行之间即可修改着色器做快速对比,无需重新构建应用。

6. Sakura Forest SER:复杂场景下的线程分组提示

D3D12RaytracingSakuraForestSER 在一个风格化的樱花森林场景中演示 SER,场景由多种几何类型组成:树干、花朵、灌木与地板立方体。SER 用于给 GPU 提供分组线程的提示以提升执行效率,支持三种模式:

  • Sort by HitObject:按命中对象分组;
  • Sort by reflectHint:按自定义键分组——该键源自对地板纹理的采样,用于识别反射区域(如形似水面的暗色缝隙与空间中的随机立方体);
  • Sort by Both:结合 HitObject 与 reflectHint。

reflectHint的算法是:在估计命中位置采样地板纹理,若采样颜色足够暗,则将该表面视为反射面,触发 Fresnel 反射等额外着色逻辑,以此模拟阴影区域的细微积水效果。

文档特别说明的性能观察(按原文记录):该场景对天空盒施加了重负载,使其比典型游戏场景更昂贵,这正是 SER 收益差异的重要来源。尤其是透过树冠缝隙能看到天空的视点,其着色成本变化很大,这种变化性正是 SER 能带来收益的场景。reflectHint上的MaybeReorderThread()用于演示另一种潜在的变化属性(例如反射远重于简单光照计算);官方在该示例上的测试显示:此属性在 RTX 5070 上收益甚微,但在更高端的老架构 GPU(如 RTX 4080、4090)上没有明显负面影响。该结论仅针对本示例与文中提到的测试环境,不应外推为普遍规律。

控制:P开关 SER;H/M/B分别切换 HitObject / reflectHint / 两者结合的MaybeReorderThread()依据;WASD移动(W 前、A 左、S 后、D 右);Q/E垂直升降相机(Q 下、E 上);方向键环视。标题栏额外显示SER: ON/OFF状态。

7. Opacity Micromaps:树叶镂空与透明度加速

D3D12RaytracingOpacityMicromaps 演示如何在 D3D12 中使用Opacity Micromaps(OMM),场景为一棵树木模型。

渲染方式

  • 主可见性经光追计算,每像素投一条阴影射线;
  • 模型由 3 个几何体构成:Trunk(树干)、Branches(树枝)、Leaves(树叶);只有 Leaves 应用 OMM,Trunk 与 Branches 为非 OMM 几何;
  • OMM 数组、BLAS 与 TLAS 在第 0 帧构建,此后仅在需要重建的帧上重建。

键盘控制(映射到 DXR 特性)

键作用底层机制
O开关 OMM以D3D12_RAYTRACING_INSTANCE_FLAG_DISABLE_OMMS重建 TLAS
Q/W在 1–12 之间切换最大细分级别OMM 数据离线构建了 24 次(2-state / 4-state × Subdiv 1–12);修改后下一帧重建 OMM 数组,连带重建 BLAS 与 TLAS(本示例未实现 OMM linkage update 替代方案)
F在 2-state 与 4-state OMM 间切换同上,会触发 OMM 数组、BLAS、TLAS 重建;示例刻意同时提供 2 位与 4 位 OMM,因为后者有额外内存开销
R开关相机环绕模型—
Z/X调整相机视场角(FOV),支持对几何极限放大—
A开关 Any-Hit Shader(AHS)动态增删主/阴影射线的D3D12_RAY_FLAG_FORCE_OPAQUE;高细分级别无需 AHS 即可获得高精度的树叶镂空
H将至少执行过一次 AHS 的像素/主射线染成品红色用于可视化 AHS 调用位置
B开关每帧重建 OMM/BLAS/TLAS便于对加速结构构建过程做 PIX 抓帧;否则仅在 Frame 0 或选项被切换的帧上构建

注意事项(按原文归纳)

  • NVIDIA OMM SDK 会自动将 OMM 折叠到表达所需图案的最低细分级别:允许使用细分级别 12 不代表一定会用满,因此细分级别 9–12 可能与 8 在视觉上难以区分;其他内容可能受益于更高细分;
  • OMM没有 mip 概念,远处会产生锯齿伪影,高细分级别尤其明显;
  • 该模型需要约120 个 OMM,数组成本仅约 3KB;OMM 索引数组每三角形 4 字节,全模型约 2.5MB。SDK 目前只输出 4 字节索引,但对少于约 65000 个 OMM 的模型 2 字节索引已足够,可省 50% 索引内存;
  • 本示例未演示OMM Linkage Updates,而是按需重建 OMM/BLAS/TLAS;
  • 示例沿用 Wavefront OBJ 的"位置、法线、纹理坐标分缓冲(含索引缓冲)"做法,官方注明这对游戏内容并非最佳实践,仅为便利。

已知问题:示例用射线/UV 导数计算纹理 mip 级别,但阴影射线始终采样 Mip 0,可能引发锯齿;且模型的纹理混合、UV 缩放/旋转未忠实复刻 Blender 原模型(对 OMM 演示不必要)。

五、高级示例

1. Real-Time Denoised Ambient Occlusion:实时降噪与镜面路径追踪

D3D12RaytracingRealTimeDenoisedAmbientOcclusion 实现一个用于 1 spp(每像素一条样本)光线追踪环境光遮蔽(AO)的实时降噪器,同时演示基于物理的镜面路径追踪实现与动态几何支持。它对应高级示例的定位:在单一场景中同时处理降噪、物理着色与几何动态更新三类工程难题,适合作为 DXR 落地到真实渲染器的参考。

2. MiniEngine Sample:光线追踪融入完整引擎

D3D12RaytracingMiniEngineSample 是MiniEngine 的改造版(MiniEngine 本体位于仓库 MiniEngine 目录),将 DirectX Raytracing 集成进其 Model Viewer,并演示一系列光追用途。按数字键1–7可循环切换模式(也可按 Backspace 打开 MiniEngine 调试菜单,经 Application/Raytracing/RaytraceMode 切换):

键模式说明
1Off全光栅化
2Bary Rays主射线返回命中三角形的重心坐标
3Refl Bary次级反射射线返回命中三角形的重心坐标
4Shadow Rays次级阴影射线,命中与否输出黑/白
5Diffuse & ShadowMaps主射线计算漫反射光照,并使用光栅化的阴影贴图
6Diffuse & ShadowRays全光追通道:主射线做漫反射光照并递归发射阴影射线
7Reflection Rays混合通道:光栅化渲染主漫反射,检测到地面平面时发射反射射线

控制:WASD/左摇杆移动;E/Q(或扳机键)升降相机;鼠标/右摇杆控制偏航与俯仰;F 冻结相机;左右键循环预设机位;左摇杆按下或 LShift 慢速移动;Backspace/返回键打开调试菜单。

已知限制(官方原文):

  • 阴影通道因射线生成不正确而存在 bug;
  • mipmap 计算不正确,远处物体使用的 LOD 过低;
  • 运行时输出一条可忽略的调试层错误:"D3D12 ERROR: ID3D12Device::CopyDescriptors: Source ranges and dest ranges overlap, which results in undefined behavior."——源于 SM 6.0 驱动上的调试层问题。

六、进一步学习路线与反馈

官方进一步资源指向 NVIDIA 的 DXR 教程系列(游戏行业常用补充材料)。集合作者欢迎一切关于 DXR 的反馈、提问与讨论,官方社区渠道为 DirectX 官方 Discord 服务器。需要再次强调:所有示例都以 Samples/Desktop/D3D12Raytracing/readme.md 为总入口,本文涉及的各子示例说明、源码工程与着色器文件均可在 Samples/Desktop/D3D12Raytracing/src 下对应目录中查阅;集合解决方案文件为src下的D3D12Raytracing.slnx。建议的学习顺序是:Hello World → Simple Lighting → Procedural Geometry → Library Subobjects → SER 两个示例 → Opacity Micromaps,再进入高级示例;运行时若遇 dxc 编译类报错,优先核对本文第三节列出的已知问题与修复方向。

  • 示例工程

【免费下载链接】DirectX-Graphics-Samples

This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.

项目地址:https://gitcode.com/gh_mirrors/di/DirectX-Graphics-Samples
点击查看免费下载
上一篇:5分钟搞定 Mac 到 Android 文件传输:OpenMTP 完整上手指南
下一篇:GHelper 完整指南:轻量化工具接管华硕笔记本的硬件控制与性能调校

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询