- 示例工程
【免费下载链接】DirectX-Graphics-Samples
This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.
本篇指南以 DirectX-Graphics-Samples 仓库中Samples/Desktop/D3D12PredicationQueries示例为蓝本,系统讲解 Direct3D 12 中**二进制遮挡查询(Binary Occlusion Query)与预测(Predication)**的完整用法:从查询堆、查询结果缓冲等底层资源搭建,到BeginQuery/EndQuery/ResolveQueryData/SetPredication的逐帧调用链,再到 Root Signature 1.1 静态描述符优化与构建运行方式。读完本文,你将能够在自己项目中复现"上一帧查询、本帧条件绘制"的经典遮挡剔除流程。
示例总览:两个四边形如何演示查询与预测
该示例(说明见 readme.md)演示了 Direct3D 12 中二进制遮挡查询与预测的结合使用。场景非常简单但直击要点:渲染两个四边形,其中一个会来回动画并周期性遮挡另一个。每一帧执行一次二进制遮挡查询,并通过SetPredicationAPI 条件性地绘制可能被遮挡的四边形;当查询判定该四边形被完全遮挡时,利用透明度混合直观地展示这一结果。
从 Main.cpp 可以看到示例以 1280×720 窗口启动:
D3D12PredicationQueries sample(1280, 720, L"D3D12 Predication and Queries sample"); return Win32Application::Run(&sample, hInstance, nCmdShow);场景中共有三份几何数据(见 D3D12PredicationQueries.cpp 的LoadAssets):
| 几何体 | 位置/尺寸 | 颜色 | 用途 |
|---|---|---|---|
| 远四边形(Far quad) | ±0.25 × ±0.25,z = 0.5 | 白色不透明 | 实际场景中代表"复杂几何体",是可能被遮挡的绘制对象 |
| 近四边形(Near quad) | ±0.5 × ±0.35,z = 0.0 | 红色/黄色,alpha 0.65 | 半透明动画物体,周期性遮挡远四边形 |
| 远四边形包围盒 | ±0.25 × ±0.25,z = 0.4999 | 黑色 | 只用于遮挡查询的几何,z 值略微偏移以避免与远四边形 z-fighting |
注释明确说明"Geometry will be rendered back-to-front to support transparency in the scene",即为了支持透明度,几何按从后到前的顺序绘制。
前置概念:二进制遮挡查询与 Predication
- 遮挡查询(Occlusion Query):向 GPU 提交一段几何体绘制,由硬件统计该几何体在深度测试中有多少像素通过。二进制查询只关心"有没有任何像素通过":结果0 表示被完全遮挡(没有任何样本通过深度测试),1 表示可见(至少一个样本通过)。
- 预测(Predication):与查询"问问题"不同,Predication 是"用答案做决定"——
SetPredication把一个查询结果缓冲区绑定到命令列表中,其后的绘制/复制命令会根据谓词值决定是否执行。D3D12_PREDICATION_OP_EQUAL_ZERO表示当谓词值等于 0 时执行后续命令。 - 一帧延迟:查询结果需要 GPU 执行完后才能回读,因此示例在上一帧执行查询并解析到缓冲区,当前帧用其结果驱动远四边形的条件绘制(代码注释:"Draw the far quad conditionally based on the result of the occlusion query from the previous frame")。
从零搭建查询基础设施
1. 创建查询堆(Query Heap)
在LoadPipeline中,示例创建了一个容量为 1 的遮挡类型查询堆:
D3D12_QUERY_HEAP_DESC queryHeapDesc = {}; queryHeapDesc.Count = 1; queryHeapDesc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION; ThrowIfFailed(m_device->CreateQueryHeap(&queryHeapDesc, IID_PPV_ARGS(&m_queryHeap)));Count为查询槽位数量,Type决定了可用的查询类型——D3D12_QUERY_HEAP_TYPE_OCCLUSION支持二进制遮挡查询(D3D12_QUERY_TYPE_BINARY_OCCLUSION)和整型遮挡查询。对应成员声明见 D3D12PredicationQueries.h 中的ComPtr<ID3D12QueryHeap> m_queryHeap;。
2. 创建查询结果缓冲
查询结果必须落在一个8 字节的缓冲中(二进制遮挡查询结果占 1 个BOOL,但缓冲需按 8 字节对齐创建),并且创建时直接置于D3D12_RESOURCE_STATE_PREDICATION状态——这正是SetPredication读取该缓冲所需的资源状态:
D3D12_RESOURCE_DESC queryResultDesc = CD3DX12_RESOURCE_DESC::Buffer(8); ThrowIfFailed(m_device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &queryResultDesc, D3D12_RESOURCE_STATE_PREDICATION, nullptr, IID_PPV_ARGS(&m_queryResult)));3. 两个 PSO:普通管线与查询专用管线
同一个 PSO 描述在创建后会被复用两次:先创建m_pipelineState(正常绘制,开启 alpha 混合以便可视化查询结果),随后关闭颜色写入和深度写入再创建m_queryState——查询绘制只需要"让深度测试发生",不需要产生任何可见像素:
psoDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = 0; psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; ThrowIfFailed(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_queryState)));这里还体现了两个值得注意的实践:
- 顶点输入布局包含
POSITION(R32G32B32_FLOAT)与COLOR(R32G32B32A32_FLOAT,偏移 12 字节); - 混合状态采用标准源 alpha 混合
D3D12_BLEND_SRC_ALPHA / D3D12_BLEND_INV_SRC_ALPHA,这正是半透明近四边形能"透过"看到远处几何的原因。
核心渲染流程逐行拆解
整个查询与预测逻辑集中在PopulateCommandList(D3D12PredicationQueries.cpp),可以分为三个紧密衔接的阶段。
阶段一:用上一帧查询结果条件绘制远四边形
// Draw the far quad conditionally based on the result of the occlusion query // from the previous frame. m_commandList->SetGraphicsRootDescriptorTable(0, cbvFarQuad); m_commandList->SetPredication(m_queryResult.Get(), 0, D3D12_PREDICATION_OP_EQUAL_ZERO); m_commandList->DrawInstanced(4, 1, 0, 0);SetPredication的三个参数分别为:谓词缓冲资源、缓冲内字节偏移(此处为 0)、谓词操作。EQUAL_ZERO意味着:当缓冲中值等于 0(远四边形完全被遮挡)时执行随后的DrawInstanced——配合半透明近四边形,被遮挡时白色远四边形会在其后透出,形成混色视觉效果,这正是 readme 所说"transparency effect is used to illustrate when the query determines that the quad is fully occluded"。
阶段二:解除预测,无条件绘制近四边形
// Disable predication and always draw the near quad. m_commandList->SetPredication(nullptr, 0, D3D12_PREDICATION_OP_EQUAL_ZERO); m_commandList->SetGraphicsRootDescriptorTable(0, cbvNearQuad); m_commandList->DrawInstanced(4, 1, 4, 0);把谓词缓冲设为nullptr即关闭预测,此后命令无条件执行。注意两个DrawInstanced的起始顶点索引不同(0 与 4),对应顶点缓冲中不同段的四边形。
阶段三:执行查询并解析结果
// Run the occlusion query with the bounding box quad. m_commandList->SetPipelineState(m_queryState.Get()); m_commandList->BeginQuery(m_queryHeap.Get(), D3D12_QUERY_TYPE_BINARY_OCCLUSION, 0); m_commandList->DrawInstanced(4, 1, 8, 0); m_commandList->EndQuery(m_queryHeap.Get(), D3D12_QUERY_TYPE_BINARY_OCCLUSION, 0);先切换为"不写颜色/深度"的查询管线,在BeginQuery与EndQuery之间绘制包围盒四边形(起始顶点索引 8)。深度测试在此刻真实发生,其结果即为远四边形本帧的可见性。
随后通过资源屏障把查询结果缓冲从D3D12_RESOURCE_STATE_PREDICATION切换到D3D12_RESOURCE_STATE_COPY_DEST,调用ResolveQueryData将 GPU 查询结果写入缓冲,再切回PREDICATION状态供下一帧SetPredication读取:
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition( m_queryResult.Get(), D3D12_RESOURCE_STATE_PREDICATION, D3D12_RESOURCE_STATE_COPY_DEST)); m_commandList->ResolveQueryData(m_queryHeap.Get(), D3D12_QUERY_TYPE_BINARY_OCCLUSION, 0, 1, m_queryResult.Get(), 0); m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition( m_queryResult.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PREDICATION));至此形成完整闭环:本帧查询 → 解析 → 下一帧条件绘制。此外,三个阶段分别用PIXBeginEvent("Draw potentially occluded geometry"、"Draw animating geometry"、"Execute occlusion query")做了标注,配合 PIX 可逐段分析 GPU 工作负载。
动画逻辑:近四边形周期性遮挡
OnUpdate中,近四边形的 x 偏移每帧增加0.01f,到达1.5f边界后回绕到-1.5f,实现往复移动;随后把对应常量缓冲内容拷入上传堆中的帧专属区域(每个几何体一份独立常量缓冲,CbvCountPerFrame = 2):
const float translationSpeed = 0.01f; const float offsetBounds = 1.5f; // Animate the near quad. m_constantBufferData[1].offset.x += translationSpeed; if (m_constantBufferData[1].offset.x > offsetBounds) { m_constantBufferData[1].offset.x = -offsetBounds; } UINT cbvIndex = m_frameIndex * CbvCountPerFrame + 1; UINT8* destination = m_pCbvDataBegin + (cbvIndex * sizeof(SceneConstantBuffer)); memcpy(destination, &m_constantBufferData[1], sizeof(SceneConstantBuffer));近四边形在-1.5 ~ 1.5区间内掠过远四边形(±0.25范围),从而周期性触发"完全遮挡 → 查询结果为 0 → 谓词命中"的演示状态。常量缓冲结构在 D3D12PredicationQueries.h 中定义,注意其FLOAT padding[60]注释:常量缓冲需 256 字节对齐,填充是为了让多个缓冲可按数组索引寻址。
对应的 shaders.hlsl 非常精简——顶点着色器把常量缓冲中的offset叠加到顶点位置上,像素着色器直接输出顶点色:
cbuffer SceneConstantBuffer : register(b0) { float4 offset; }; PSInput VSMain(float4 position : POSITION, float4 color : COLOR) { PSInput result; result.position = position + offset; result.color = color; return result; } float4 PSMain(PSInput input) : SV_TARGET { return input.color; }配合 Root Signature 1.1 的静态描述符优化
readme 的Optional features一节说明了本示例的另一个亮点:示例已更新为基于Windows 10 Anniversary Update SDK构建,该 SDK 为 Direct3D 12 提供了新版 Root Signature。Root Signature 1.1允许应用声明"描述符堆中的描述符不会改变"或"描述符指向的数据不会改变",从而给驱动提供在"描述符或其所指内存在一段时间内保持静态"这一前提下进行优化的机会。
源码中这一机制落地为一段完整的版本协商 + 静态标志代码:
D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {}; featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1; if (FAILED(m_device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData)))) { featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0; } CD3DX12_DESCRIPTOR_RANGE1 ranges[1]; CD3DX12_ROOT_PARAMETER1 rootParameters[1]; ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC); rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_VERTEX);要点拆解:
- 能力协商:先请求
D3D_ROOT_SIGNATURE_VERSION_1_1,通过CheckFeatureSupport检测失败则回退到D3D_ROOT_SIGNATURE_VERSION_1_0,保证在旧驱动上也能运行; - 静态声明:
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC告诉驱动"该描述符指向的数据在整个根签名有效期内不会改变",这是 1.1 新增的优化信号(示例中常量缓冲每帧更新,但根签名本身持续复用,因此数据静态声明是安全的); - 版本化描述:用
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC::Init_1_1构造描述,再经D3DX12SerializeVersionedRootSignature序列化为字节码创建根签名; - 管线阶段收敛:根签名标志同时声明
DENY_HULL/DOMAIN/GEOMETRY/PIXEL_SHADER_ROOT_ACCESS,仅允许顶点着色器访问,配合D3D12_SHADER_VISIBILITY_VERTEX限制描述符表可见性,减少驱动需要维护的状态。
这是把"新版根签名 + 静态数据声明"用于实际渲染管线的完整范例,也是 readme 中 optional feature 的源码级印证。
构建与运行
示例工程文件为 D3D12PredicationQueries.vcxproj,支持Debug/Release × x64/ARM64四种配置,链接dxgi.lib; d3d12.lib; d3dcompiler.lib; dxguid.lib并延迟加载d3d12.dll。构建层面的几个关键点:
- 着色器编译:工程通过自定义构建步骤调用
dxc.exe(来自 NuGet 包Microsoft.Direct3D.DXC.1.8.2505.32),分别以vs_6_0和ps_6_0目标编译VSMain/PSMain,输出shaders_VSMain.cso与shaders_PSMain.cso,随后由应用在运行时通过ReadDataFromFile读取:
dxc.exe -nologo -Tvs_6_0 -E"VSMain" -Zi -Qembed_debug -Fo"$(OutDir)shaders_VSMain.cso" shaders.hlsl dxc.exe -nologo -Tps_6_0 -E"PSMain" -Zi -Qembed_debug -Fo"$(OutDir)shaders_PSMain.cso" shaders.hlsl- D3D12 SDK 版本:源文件顶部导出了
D3D12SDKVersion = 618与D3D12SDKPath = u8".\\D3D12\\",指示 D3D12 SDK 加载器使用本地.\D3D12\目录下对应版本的 SDK 运行库;NuGet 包Microsoft.Direct3D.D3D12.1.618.3提供配套的构建属性与目标。 - PIX 集成:工程引用
WinPixEventRuntime.1.0.240308001,配合代码中的PIXBeginEvent/PIXEndEvent可在 PIX 中分析渲染事件。 - 运行参数:应用支持命令行加
/warp(或-warp)强制使用 WARP 软件设备(解析逻辑见 DXSample.cpp 的ParseCommandLineArgs),无 D3D12 硬件时也可观察行为;设备创建请求的功能级别为D3D_FEATURE_LEVEL_11_0。
环境前提方面:readme 说明示例面向 Windows 10 Anniversary Update SDK(Root Signature 1.1 在该 SDK 中可用);仓库主分支 README.md 要求 Windows 10 2004 及以上版本搭配 Visual Studio 2019 与 Windows 10 SDK 19041 构建。Debug 构建下代码会启用 D3D12 调试层与DXGI_CREATE_FACTORY_DEBUG(需要系统安装 Graphics Tools 可选功能),并调用DXGIDeclareAdapterRemovalSupport;运行期若遇到DXGI_ERROR_DEVICE_REMOVED/RESET,示例会通过RestoreD3DResources重建全部 D3D 资源。
关键文件索引
- 示例说明文档:二进制遮挡查询 + Predication 概述与 Root Signature 1.1 optional feature 说明
- 主实现:查询堆/结果缓冲创建、双 PSO、谓词绘制与查询解析的完整调用链
- 类声明:资源成员、
SceneConstantBuffer对齐结构、帧计数常量 - 着色器:常量缓冲偏移驱动的顶点变换与纯色输出
- 工程文件:dxc 编译命令、NuGet 依赖与链接配置
- 应用入口 与 通用框架:窗口创建、适配器枚举、
/warp参数解析 - 仓库顶层说明:各 API 示例的组织方式与构建环境要求
- 示例工程
【免费下载链接】DirectX-Graphics-Samples
This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.
相关推荐
Yii 2 RESTful API 快速开始:用最小代码构建完整的用户资源接口
Yii 2 RESTful API 快速开始:用最小代码构建完整的用户资源接口 本篇技术指南围绕 Yii 2 官方指南的 REST 快速开始章节展开,演示如何以
示例工程微信聊天记录导出完整指南:用 WeChatMsg 免费把历史对话转成 Word、CSV 和年度聊天报告
微信聊天记录导出完整指南:用 WeChatMsg 免费把历史对话转成 Word、CSV 和年度聊天报告 手机一换、系统一重装,很多微信群里的老消息就再也找不回来
示例工程深入 Pydantic AI 的 `pydantic_ai.ext`:如何把 LangChain 工具无缝桥接为 Pydantic AI 工具
深入 Pydantic AI 的 pydantic_ai.ext :如何把 LangChain 工具无缝桥接为 Pydantic AI 工具 Pydantic
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考