From 4e2261ad37e3a1c20fc1d3b137176eb8ac2c191d Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 17 Apr 2026 21:42:06 +0800 Subject: [PATCH] docs: fix MiniEngine Core guide encoding --- ...niengine_core_beginner_guide_2026-04-17.md | 1402 ++++++++--------- 1 file changed, 676 insertions(+), 726 deletions(-) diff --git a/docs/reference/miniengine_core_beginner_guide_2026-04-17.md b/docs/reference/miniengine_core_beginner_guide_2026-04-17.md index 1529d839..ca64f7c1 100644 --- a/docs/reference/miniengine_core_beginner_guide_2026-04-17.md +++ b/docs/reference/miniengine_core_beginner_guide_2026-04-17.md @@ -1,18 +1,30 @@ -# MiniEngine Core 鍏ラ棬瑙f瀽 +# MiniEngine Core 详解 -> 闈㈠悜璇昏€咃細鍥惧舰瀛﹀皬鐧姐€佸紩鎿庡紑鍙戞柊鎵嬨€佸垰寮€濮嬫帴瑙?DirectX 12 鐨勫紑鍙戣€? -> 闃呰鑼冨洿锛歚D:\Xuanchi\Main\Nyx-main\MiniEngine\Core` -> 鏂囨。鐩爣锛氫笉鏄畝鍗曠綏鍒楁枃浠讹紝鑰屾槸瑙i噴 `Core` 鍦ㄦ暣涓」鐩噷鐨勮亴璐c€佸唴閮ㄦā鍧楀浣曞崗浣溿€佷负浠€涔堣杩欐牱璁捐 +> 面向读者:图形学初学者、引擎开发新手、刚开始接触 DirectX 12 的开发者 +> 阅读范围:`D:\Xuanchi\Main\Nyx-main\MiniEngine\Core` +> 文档目标:用“工程视角”解释 Core 的结构、职责、执行流程和关键模块,而不是只做文件罗列 -## 1. 鍏堣缁撹锛歚Core` 鍒板簳鏄粈涔? -浠庝粨搴撴牴鐩綍 `readme.md` 鍜屽悇涓簮鏂囦欢澶撮儴鐨勭増鏉冧俊鎭彲浠ョ洿鎺ョ湅鍑猴紝杩欎釜椤圭洰鏉ヨ嚜 Microsoft Team Minigraph / MiniEngine 浣撶郴锛宍Core` 鏄暣涓?MiniEngine 鐨勫叕鍏卞簳搴с€? -鏍圭洰褰?README 涓€寮€澶村氨鏄細 +## 1. 先给结论:Core 是什么 + +如果只用一句话概括: + +**`Core` 是 MiniEngine 的公共底层。它负责程序启动、D3D12 设备初始化、命令系统、资源抽象、帧缓冲组织、后处理、相机输入、调试 UI 和性能分析。** + +它不是一个单独的“渲染功能模块”,而是一套完整的运行时基础设施。 + +你可以这样理解整个仓库: + +- `ModelViewer`、`SceneViewer`、`DeferredRenderer` 更像“具体应用” +- `Core` 则像这些应用共用的引擎底盘 + +根目录 `readme.md` 一开头就说明了出处: ```md # MiniEngine by Team Minigraph at Microsoft ``` -鑰?`Core` 鐩綍閲岀殑澶ч儴鍒嗘枃浠跺ご閮藉啓鐫€锛? +而 `Core` 里多数文件头部都写着: + ```cpp // // Copyright (c) Microsoft. All rights reserved. @@ -26,53 +38,57 @@ // ``` -鎵€浠ュ鏋滃彧鐢ㄤ竴鍙ヨ瘽姒傛嫭锛? -**`Core` 灏辨槸 MiniEngine 鐨勨€滃叕鍏辫繍琛屾椂 + D3D12 娓叉煋鎶借薄灞?+ 璋冭瘯鍒嗘瀽宸ュ叿绠扁€濄€?* +所以这份代码的气质很明确: -瀹冧笉鏄竴涓崟鐙殑鈥滄覆鏌撳姛鑳芥ā鍧椻€濓紝鑰屾槸涓€鏁村鍩虹璁炬柦锛岃礋璐hВ鍐宠繖浜涢棶棰橈細 +- 它是 Microsoft/Minigraph 体系下的工程化示例 +- 它不是最现代的商业引擎架构 +- 但它非常适合学习“一个 D3D12 引擎底层到底要负责什么” -- Win32 绋嬪簭鎬庝箞鍚姩銆佹€庝箞杩涗富寰幆 -- D3D12 璁惧銆佷氦鎹㈤摼銆佸懡浠ら槦鍒椼€佸懡浠ゅ垪琛ㄣ€丗ence 鎬庝箞绠$悊 -- 璧勬簮鐘舵€佸垏鎹€佹弿杩扮鍫嗐€丷oot Signature銆丳SO 鎬庝箞灏佽 -- 棰滆壊缂撳啿銆佹繁搴︾紦鍐层€佺粨鏋勫寲 Buffer銆佺汗鐞嗚繖浜?GPU 璧勬簮鎬庝箞鎶借薄 -- 鍚庡鐞嗐€乀AA銆丗XAA銆丼SAO銆乆eGTAO銆丏oF銆丮otion Blur 杩欎簺閫氱敤鏁堟灉鎬庝箞澶嶇敤 -- 鐩告満銆佽緭鍏ャ€佽皟鍙傘€両mGui銆佹€ц兘鍒嗘瀽杩欎簺杩愯鏃跺伐鍏锋€庝箞鎺ュ叆 +## 2. Core 里到底解决了哪些问题 -濡傛灉浣犳妸 MiniEngine 鎯虫垚涓€鍙版満鍣紝閭d箞锛? -- `ModelViewer`銆乣SceneViewer` 涔嬬被鐩綍鏇村儚鈥滃叿浣撳簲鐢ㄢ€?- `Core` 鍒欐槸鐢垫簮銆佸簳鐩樸€佹€荤嚎銆佷华琛ㄧ洏鍜屼紶鍔ㄧ郴缁? -## 2. `Core` 鐩綍搴旇鎬庝箞璇? -瀵瑰垵瀛﹁€呮潵璇达紝鏈€瀹规槗鐘殑閿欒鏄€滄寜鏂囦欢鍚嶄竴涓竴涓‖鍟冣€濄€傛洿楂樻晥鐨勮娉曟槸鎸夎亴璐e垎灞傦細 +对新手来说,先不要一上来盯着某个渲染算法。更重要的是先看清 Core 想解决的“底层问题”: -| 鍒嗗眰 | 鍏抽敭鏂囦欢 | 浣滅敤 | -| --- | --- | --- | -| 鍚姩涓庝富寰幆 | `GameCore.*`, `GraphicsCore.*`, `Display.*`, `SystemTime.*` | 寤虹獥鍙c€佸缓 D3D12 璁惧銆佽窇姣忓抚寰幆銆佹渶缁?Present | -| 鍛戒护鎻愪氦涓庡悓姝?| `CommandListManager.*`, `CommandContext.*`, `CommandAllocatorPool.*` | 绠″懡浠ら槦鍒椼€佸懡浠ゅ垪琛ㄣ€佸懡浠ゅ垎閰嶅櫒銆丗ence | -| D3D12 鐘舵€佹娊璞?| `DescriptorHeap.*`, `DynamicDescriptorHeap.*`, `RootSignature.*`, `PipelineState.*`, `CommandSignature.*`, `SamplerManager.*` | 灏佽鎻忚堪绗︺€佹牴绛惧悕銆丳SO銆侀潤鎬侀噰鏍峰櫒銆侀棿鎺ュ懡浠?| -| GPU 璧勬簮鎶借薄 | `GpuResource.h`, `PixelBuffer.*`, `ColorBuffer.*`, `DepthBuffer.*`, `GpuBuffer.*`, `Texture.*` | 鎶借薄绾圭悊銆丅uffer銆丷T銆丏S銆丼RV/UAV/RTV/DSV | -| 甯у唴璧勬簮姹?| `BufferManager.*`, `GraphicsCommon.*` | 缁熶竴鍒涘缓鎵€鏈夊父鐢ㄧ紦鍐层€侀粯璁ょ汗鐞嗐€侀粯璁ょ姸鎬併€佸叡浜?PSO | -| 鐩告満涓庤緭鍏?| `Camera.*`, `CameraController.*`, `ShadowCamera.*`, `GameInput.*` | 鐩告満鐭╅樀銆佹帶鍒跺櫒銆侀槾褰辩浉鏈恒€侀敭榧?鎵嬫焺杈撳叆 | -| 鍚庡鐞嗕笌楂樼骇鏁堟灉 | `PostEffects.*`, `TemporalEffects.*`, `FXAA.*`, `SSAO.*`, `XeGTAO.*`, `MotionBlur.*`, `DepthOfField.*`, `IBL.*` | 涓€鏁村灞忓箷鍚庡鐞嗗拰鐜鍏夌収棰勮绠?| -| 宸ュ叿涓庤皟璇?| `EngineTuning.*`, `EngineProfiling.*`, `GpuTimeManager.*`, `ImGuiManager.*`, `GraphRenderer.*`, `TextRenderer.*` | 璋冨弬 UI銆丆PU/GPU 璁℃椂銆両mGui 鎺ュ叆 | -| 鍐呭瓨涓庡垎閰?| `LinearAllocator.*`, `BuddyAllocator.*`, `UploadBuffer.*`, `ReadbackBuffer.*`, `EsramAllocator.h` | 涓婁紶鍐呭瓨銆佽鍥炲唴瀛樸€佸瓙鍒嗛厤鍣?| -| 鏁板涓庡伐鍏?| `Math/*`, `VectorMath.h`, `FileUtility.*`, `TextureManager.*`, `Util/CommandLineArg.*` | 鏁板鍩虹銆佹枃浠惰鍐欍€佺汗鐞嗙紦瀛樸€佸懡浠よ鍙傛暟 | +1. Win32 程序怎么启动,窗口怎么创建,主循环怎么跑 +2. D3D12 设备、SwapChain、命令队列、Fence 怎么初始化和管理 +3. 命令列表、资源状态切换、描述符堆、Root Signature、PSO 怎么封装 +4. 颜色缓冲、深度缓冲、结构化 Buffer、纹理这些 GPU 资源怎么抽象 +5. 一帧中要用到哪些中间缓冲,这些缓冲在什么时候创建、销毁和重建 +6. 场景渲染之后,HDR、Bloom、TAA、FXAA、SSAO、XeGTAO、景深、运动模糊怎么接起来 +7. 相机、输入、调参界面、ImGui、性能分析这些运行时工具怎么统一接入 + +所以你可以把 `Core` 看成三层: + +1. 平台与主循环层 +2. D3D12 抽象层 +3. 基于这些抽象搭起来的通用渲染与工具模块 + +## 3. 推荐的阅读顺序 + +如果你直接从 `SSAO.cpp` 或 `PostEffects.cpp` 开始读,很容易迷路。更高效的顺序是: -瀵规柊浜烘渶鎺ㄨ崘鐨勯槄璇婚『搴忎笉鏄€滀粠鏈€鐭殑鏂囦欢寮€濮嬧€濓紝鑰屾槸锛? 1. `GameCore.h/.cpp` 2. `GraphicsCore.h/.cpp` 3. `Display.h/.cpp` 4. `BufferManager.h/.cpp` -5. `GpuResource.h`, `PixelBuffer.*`, `ColorBuffer.*`, `DepthBuffer.*`, `GpuBuffer.*` -6. `CommandListManager.*`, `CommandContext.*` -7. `DescriptorHeap.*`, `DynamicDescriptorHeap.*`, `RootSignature.*`, `PipelineState.*` -8. `PostEffects.*`, `TemporalEffects.*` -9. `Camera.*`, `GameInput.*` -10. `EngineTuning.*`, `EngineProfiling.*` +5. `GpuResource.h`、`PixelBuffer.*`、`ColorBuffer.*`、`DepthBuffer.*`、`GpuBuffer.*` +6. `CommandListManager.*`、`CommandContext.*` +7. `DescriptorHeap.*`、`DynamicDescriptorHeap.*`、`RootSignature.*`、`PipelineState.*` +8. `Camera.*`、`CameraController.*`、`GameInput.*` +9. `PostEffects.*`、`TemporalEffects.*` +10. `EngineTuning.*`、`EngineProfiling.*` -杩欐牱璇伙紝浣犱細鍏堝缓绔嬧€滆繖濂楃郴缁熷湪骞插槢鈥濈殑澶у浘锛屽啀鍥炲幓璇荤粏鑺傘€? -## 3. 鏁翠釜绋嬪簭鏄€庝箞璺戣捣鏉ョ殑 +原因很简单: + +- 前三步先看清主干 +- 中间几步补齐 D3D12 抽象 +- 最后再看具体特效和工具 + +## 4. 程序是怎么跑起来的 + +### 4.1 应用层接口极薄 + +`GameCore.h` 定义了应用需要实现的接口: -### 3.1 搴旂敤鎺ュ彛闈炲父钖? -`GameCore.h` 閲屽畾涔変簡鏁翠釜搴旂敤灞傞渶瑕佸疄鐜扮殑鎺ュ彛锛? ```cpp namespace GameCore { @@ -81,39 +97,65 @@ namespace GameCore public: virtual void Startup( void ) = 0; virtual void Cleanup( void ) = 0; + virtual bool IsDone( void ); + virtual void Update( float deltaT ) = 0; virtual void RenderScene( void ) = 0; + virtual void RenderUI( class GraphicsContext& ) {}; + virtual bool RequiresRaytracingSupport() const { return false; } }; } ``` -杩欐鎺ュ彛闈炲父閲嶈锛屽洜涓哄畠鎶娾€滃簲鐢ㄥ啓浠€涔堚€濆拰鈥滃紩鎿庡簳灞傚仛浠€涔堚€濆垏寮€浜嗭細 +这段接口非常重要,因为它清楚地划分了职责: -- 搴旂敤灞傚彧璐熻矗鍚姩銆佹洿鏂般€佸満鏅覆鏌撱€乁I 娓叉煋 -- 涓诲惊鐜€佽緭鍏ャ€佸悗澶勭悊銆丳resent 閮界敱 `Core` 缁熶竴璋冨害 +- 应用负责:启动、更新、场景渲染、可选 UI +- 引擎框架负责:窗口、循环、输入、后处理、Present、调试系统 -杩欎篃鏄?MiniEngine 鐨勪竴涓牳蹇冭璁℃€濊矾锛? -**搴旂敤涓嶇洿鎺ユ懜浜ゆ崲閾惧拰绐楀彛娑堟伅寰幆锛岃€屾槸鎶婅嚜宸卞祵鍏ュ埌涓€涓浐瀹氭鏋堕噷銆?* +也就是说,应用不是自己写一个 while 循环去调 D3D12,而是把逻辑挂进 Core 提供的框架里。 -### 3.2 WinMain 鍒颁富寰幆 +### 4.2 `CREATE_APPLICATION` 宏帮你接到 WinMain -`CREATE_APPLICATION` 瀹忎細鐢熸垚 `wWinMain`锛岀劧鍚庢妸搴旂敤瀵硅薄浜ょ粰 `GameCore::RunApplication()`銆? -`RunApplication()` 閲屽仛鐨勪簨鎯呭彲浠ユ鎷垚锛? -1. 妫€鏌?CPU SIMD 鏀寔 -2. 鍒濆鍖?WinRT -3. 娉ㄥ唽 Win32 绐楀彛绫?4. 鍒涘缓绐楀彛 -5. 璋?`InitializeApplication(app)` -6. 杩涘叆娑堟伅寰幆 -7. 姣忔绌洪棽鏃惰皟 `UpdateApplication(app)` -8. 閫€鍑烘椂璋?`TerminateApplication(app)` 鍜?`Graphics::Shutdown()` +在 `GameCore.h` 里还有这个宏: -涔熷氨鏄锛?*鐪熸鐨勬瘡甯ч┍鍔ㄥ叆鍙d笉鏄簲鐢ㄨ嚜宸辩殑 while 寰幆锛岃€屾槸 `UpdateApplication()`銆?* +```cpp +#define CREATE_APPLICATION( app_class ) \ + int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ LPWSTR /*lpCmdLine*/, _In_ int nCmdShow) \ + { \ + return GameCore::RunApplication( app_class(), L#app_class, hInstance, nCmdShow ); \ + } +``` -### 3.3 姣忓抚閮藉仛浜嗕粈涔? -`GameCore.cpp` 鐨勬瘡甯ф祦绋嬮潪甯稿€煎緱鑳屼笅鏉ワ細 +它做的事很直接: + +- 创建应用对象 +- 把对象交给 `GameCore::RunApplication()` + +所以整个引擎真正的应用入口,其实是 `RunApplication()`。 + +### 4.3 `RunApplication()` 做了什么 + +`GameCore.cpp` 中,`RunApplication()` 会完成这些事: + +1. 检查 CPU SIMD 支持 +2. 初始化 WinRT +3. 注册 Win32 窗口类 +4. 创建窗口 +5. 调用 `InitializeApplication(app)` +6. 进入消息循环 +7. 在每一帧调用 `UpdateApplication(app)` +8. 退出时调用 `TerminateApplication(app)` 和 `Graphics::Shutdown()` + +这里最值得新手注意的是: + +**应用层并不负责窗口消息循环,Core 已经把这件事完全接管了。** + +### 4.4 每一帧到底发生了什么 + +`UpdateApplication()` 是最重要的主循环函数之一: ```cpp bool UpdateApplication( IGameApp& game ) @@ -149,23 +191,34 @@ bool UpdateApplication( IGameApp& game ) } ``` -杩欐浠g爜涓€鍙f皵璇存槑浜嗗緢澶氫簨鎯咃細 +这段代码基本就是整套 Core 的“总流程图”: -- 杈撳叆鏇存柊鍦ㄩ€昏緫涔嬪墠 -- ImGui / 璋冨弬绯荤粺鏄鏋剁骇鍔熻兘锛屼笉褰掑簲鐢ㄨ嚜宸辫皟搴?- `game.RenderScene()` 缁撴潫鍚庯紝妗嗘灦浼氳嚜鍔ㄥ仛 `PostEffects::Render()` -- UI 涓嶆槸鐩存帴鐢诲埌浜ゆ崲閾撅紝鑰屾槸鍏堢敾鍒?`g_OverlayBuffer` -- 鏈€鍚庣敱 `Display::Present()` 鎶婂満鏅鑹茬紦鍐插拰 UI 鍙犲姞鍚庨€佸埌浜ゆ崲閾? -鎵€浠ュ浜庡簲鐢ㄤ綔鑰呮潵璇达紝`RenderScene()` 鐨勭湡姝h亴璐f槸锛? -**鎶婁竴甯у満鏅覆鏌撳埌 Core 棰勬湡鐨勫唴閮ㄧ紦鍐查噷锛岃€屼笉鏄嚜宸辩洿鎺?Present銆?* +- 输入更新在逻辑更新前 +- ImGui 和调参系统是框架级能力 +- 应用只渲染场景本身 +- 后处理由框架统一调度 +- UI 不直接画到交换链,而是先进 `g_OverlayBuffer` +- 最终由 `Display::Present()` 统一合成并输出 -## 4. `GraphicsCore`锛氱湡姝g殑 D3D12 鍒濆鍖栦腑蹇? -`GraphicsCore.cpp` 鏄暣涓浘褰㈢郴缁熺殑鎬诲叆鍙c€傚畠璐熻矗锛? -- 寤虹珛 D3D12 璁惧 -- 鎵撳紑 Debug Layer / GPU Validation -- 閫夋嫨纭欢閫傞厤鍣ㄦ垨鍥為€€ WARP -- 鏌ヨ纭欢鐗规€?- 鍒濆鍖栧懡浠ょ郴缁?- 鍒濆鍖栧叕鍏辩姸鎬佸拰缂撳啿 -- 鍒濆鍖栧悗澶勭悊銆丄O銆佹椂闂村煙鏁堟灉銆佺矑瀛愮郴缁? -鏈€鑳借鏄庨棶棰樼殑鏄垵濮嬪寲鏈熬杩欎竴娈碉細 +这意味着应用层的 `RenderScene()` 有一个很重要的隐含约定: + +**它要把结果画到 Core 预期的内部缓冲上,而不是自己直接对 SwapChain 做最终输出。** + +## 5. `GraphicsCore`:图形系统总入口 + +`GraphicsCore.cpp` 不是一个“随便放初始化代码”的文件,它是图形层真正的中枢。 + +它负责: + +- 启用 D3D12 Debug Layer +- 根据参数选择硬件适配器或 WARP +- 创建设备 +- 检查硬件特性 +- 初始化命令管理器 +- 初始化公共图形状态 +- 初始化显示系统和后处理模块 + +最关键的初始化收尾代码如下: ```cpp g_CommandManager.Create(g_Device); @@ -182,54 +235,69 @@ XeGTAO::Initialize(); ParticleEffectManager::Initialize(3840, 2160); ``` -杩欒鏄?`GraphicsCore` 骞朵笉鏄彧鍋氣€滃垱寤鸿澶団€濊繖涔堢畝鍗曪紝瀹冩洿鍍忎竴涓€昏杞﹂棿锛? -- `g_CommandManager` 绠″懡浠ら槦鍒?- `InitializeCommonState()` 寤洪粯璁ら噰鏍峰櫒銆侀粯璁?PSO銆侀粯璁ょ汗鐞嗐€佸叡浜?Root Signature -- `Display::Initialize()` 寤轰氦鎹㈤摼鍜屾渶缁堟樉绀虹绾?- `GpuTimeManager` 缁?profiling 鐢?- `TemporalEffects`銆乣PostEffects`銆乣SSAO`銆乣XeGTAO` 杩欎簺閫氱敤鏁堟灉鍦ㄨ繖閲屾敞鍐? -### 4.1 瀹冧负浠€涔堜細鏌ヨ Typed UAV Load +这段代码说明了 Core 的组织方式: -`GraphicsCore.cpp` 閲屾湁涓€娈靛緢鍏抽敭鐨勭‖浠惰兘鍔涙鏌ワ紝鐩殑鏄湅鏄惧崱鏄惁鏀寔瀵?`R11G11B10_FLOAT` 鍜?`R16G16B16A16_FLOAT` 鍋?Typed UAV Load銆? -鍘熷洜寰堝疄闄咃細 +- `g_CommandManager` 负责命令队列体系 +- `InitializeCommonState()` 负责默认状态和共享 PSO +- `Display::Initialize()` 负责交换链和最终输出路径 +- 各种通用图形效果统一在这里挂载 -- 鍚庡鐞嗗笇鏈涚洿鎺ユ妸 HDR 棰滆壊缂撳啿褰?UAV 璇诲啓 -- 浣嗕笉鏄墍鏈夌‖浠堕兘鏀寔瀵规煇浜涙诞鐐规牸寮忓仛 UAV Typed Load -- 鎵€浠ュ紩鎿庨渶瑕佸噯澶囦竴涓吋瀹硅矾寰? -杩欏氨鏄负浠€涔?`BufferManager` 閲屼細鏈変竴涓緢濂囨€殑锛? -- `g_SceneColorBuffer`锛氱湡姝g殑 HDR 棰滆壊缂撳啿 -- `g_PostEffectsBuffer`锛歚R32_UINT` 鐨勫悗澶勭悊澶囩敤缂撳啿 +### 5.1 它还会主动检查硬件能力 -褰撶‖浠朵笉鏀寔鐩存帴璇诲啓 HDR buffer 鏃讹紝鍚庡鐞嗗厛鍐欏埌 `g_PostEffectsBuffer`锛屾渶鍚庡啀 copy back銆? -杩欑被浠g爜闈炲父鑳戒綋鐜?D3D12 寮曟搸寮€鍙戠殑鐜板疄锛? -**娓叉煋绠楁硶涓嶅彧鏄€滃叕寮忔纭€濓紝杩樿瀵圭‖浠惰兘鍔涘仛鍒嗗弶澶勭悊銆?* +例如 `GraphicsCore.cpp` 会查询 Typed UAV Load 支持: -## 5. 鍛戒护绯荤粺锛歁iniEngine 鎬庝箞鎶?D3D12 鐨勯夯鐑﹁棌璧锋潵 +- `g_bTypedUAVLoadSupport_R11G11B10_FLOAT` +- `g_bTypedUAVLoadSupport_R16G16B16A16_FLOAT` -濡傛灉浣犲垰瀛?D3D12锛屾渶澶寸柤鐨勪竴瀹氭槸杩欏嚑鏍蜂笢瑗匡細 +为什么这很重要? + +因为很多后处理想直接把 HDR 颜色缓冲当 UAV 读写,但并不是所有硬件都支持对这些格式做 Typed UAV Load。 + +所以引擎里才会存在这两个缓冲: + +- `g_SceneColorBuffer` +- `g_PostEffectsBuffer` + +前者是正常场景颜色缓冲,后者是硬件兼容路径用的 `R32_UINT` 缓冲。 + +这非常能体现图形工程里的现实: + +**一个渲染算法真正落地时,除了公式,还必须考虑显卡能力差异。** + +## 6. 命令系统:Core 怎么把 D3D12 的繁琐细节包起来 + +对 D3D12 新手来说,最痛苦的对象通常是: - `ID3D12CommandQueue` - `ID3D12CommandAllocator` - `ID3D12GraphicsCommandList` - `Fence` -- 璧勬簮鐘舵€佸垏鎹? -MiniEngine 鐨勬€濊矾鏄細 +- Resource Barrier -- `CommandListManager` 璐熻矗鈥滈槦鍒楃骇鍒€濈殑瀵硅薄 -- `CommandContext` 璐熻矗鈥滃綍鍛戒护鏃剁殑涓婁笅鏂団€?- `CommandAllocatorPool` 璐熻矗澶嶇敤 allocator +MiniEngine 的解决方案是三层: -### 5.1 涓変釜鍛戒护闃熷垪閮借缁熶竴鍖呰捣鏉ヤ簡 +- `CommandQueue` 管一个队列 +- `CommandListManager` 管所有队列 +- `CommandContext` 管“录命令时的上下文” + +### 6.1 三种命令队列都被统一管理 + +`CommandListManager` 内部维护三个队列: -`CommandListManager` 閲屾湁涓変釜闃熷垪锛? - Graphics Queue - Compute Queue - Copy Queue -鑰屾瘡涓?`CommandQueue` 鑷繁缁存姢锛? -- 涓€涓?`ID3D12CommandQueue` -- 涓€涓?`Fence` -- 涓€涓?`CommandAllocatorPool` -- 涓嬩竴涓?fence value -- 宸插畬鎴?fence value +而每个 `CommandQueue` 又维护: + +- `ID3D12CommandQueue` +- `ID3D12Fence` +- `CommandAllocatorPool` +- 下一个 Fence 值 +- 已完成 Fence 值 + +它的核心执行逻辑非常直接: -瀹冪殑鏍稿績鎻愪氦閫昏緫闈炲父鏈寸礌锛? ```cpp uint64_t CommandQueue::ExecuteCommandList( ID3D12CommandList* List ) { @@ -245,25 +313,34 @@ uint64_t CommandQueue::ExecuteCommandList( ID3D12CommandList* List ) } ``` -杩欐浠g爜鐨勬剰涔夋槸锛? -- 鍛戒护鍒楄〃褰曞畬鍚庡厛 `Close` -- 鎻愪氦缁?GPU 鎵ц -- 绔嬪埢鍦ㄩ槦鍒椾笂 signal 涓€涓?fence -- 鐢ㄨ繑鍥炵殑 fence value 琛ㄧず鈥滆繖鎵瑰懡浠や粈涔堟椂鍊欑湡鐨勬墽琛屽畬浜嗏€? -涓€涓緢鑱槑鐨勫皬缁嗚妭鏄紝fence 鐨勯珮浣嶇紪鐮佷簡闃熷垪绫诲瀷锛? +这段代码的意义是: + +- 录完命令后先 `Close` +- 提交到 GPU +- 立刻对 Fence `Signal` +- 返回一个 Fence 值,让后续系统知道“什么时候这批命令算真正完成” + +一个很聪明的小细节是,Fence 的高位编码了队列类型: + ```cpp m_NextFenceValue((uint64_t)Type << 56 | 1) ``` -杩欒绯荤粺鍚庨潰鐪嬪埌涓€涓?fence value 鏃讹紝灏辫兘鍙嶆帹鍑哄畠灞炰簬 graphics / compute / copy 鍝釜闃熷垪銆? -### 5.2 `CommandContext` 鏄€滃啓鍛戒护鐨勫伐浣滃彴鈥? -瀵瑰簲鐢ㄤ唬鐮佸拰娓叉煋鍔熻兘妯″潡鏉ヨ锛屾渶甯哥敤鐨勬槸 `CommandContext`銆乣GraphicsContext` 鍜?`ComputeContext`銆? -浣犲彲浠ユ妸瀹冪悊瑙f垚锛? -- `CommandQueue` 鏄繍杈撻槦 -- `CommandContext` 鏄璐у彴 +这样后面只看一个 Fence 值,就能反推出它属于哪个队列。 + +### 6.2 `CommandContext` 是写命令的工作台 + +`CommandContext`、`GraphicsContext`、`ComputeContext` 是 Core 里最常用的一组类。 + +你可以把它理解成: + +- `CommandQueue` 是运输系统 +- `CommandContext` 是装配工作台 + +它向上暴露了很多更像“引擎命令”的接口: -`CommandContext` 鎻愪緵浜嗗ぇ閲忛珮灞?API锛? - `TransitionResource` +- `BeginResourceTransition` - `ClearUAV` - `SetRootSignature` - `SetPipelineState` @@ -273,13 +350,11 @@ m_NextFenceValue((uint64_t)Type << 56 | 1) - `Dispatch` - `Finish` -杩欎簺 API 鏈€澶х殑浠峰€硷紝涓嶆槸鈥滃皯鎵撳嚑涓瓧鈥濓紝鑰屾槸鎶?D3D12 鏈€鍗遍櫓鐨勪袱浠朵簨鏀剁紪浜嗭細 +这样上层模块就不需要每次都手工处理 D3D12 的底层细节。 -1. 璧勬簮鐘舵€佺鐞?2. 涓存椂鎻忚堪绗﹀拰涓存椂涓婁紶鍐呭瓨绠$悊 +### 6.3 资源状态切换是显式管理的 -### 5.3 璧勬簮鐘舵€佸垏鎹㈡槸鎬庝箞鍋氱殑 - -涓嬮潰杩欐鏄?`CommandContext::TransitionResource()` 鐨勬牳蹇冿細 +`CommandContext::TransitionResource()` 的核心代码如下: ```cpp void CommandContext::TransitionResource(GpuResource& Resource, D3D12_RESOURCE_STATES NewState, bool FlushImmediate) @@ -297,14 +372,6 @@ void CommandContext::TransitionResource(GpuResource& Resource, D3D12_RESOURCE_ST BarrierDesc.Transition.StateBefore = OldState; BarrierDesc.Transition.StateAfter = NewState; - if (NewState == Resource.m_TransitioningState) - { - BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_END_ONLY; - Resource.m_TransitioningState = (D3D12_RESOURCE_STATES)-1; - } - else - BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - Resource.m_UsageState = NewState; } else if (NewState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS) @@ -315,47 +382,47 @@ void CommandContext::TransitionResource(GpuResource& Resource, D3D12_RESOURCE_ST } ``` -瀵逛簬鍒濆鑰咃紝杩欐浠g爜鏈€濂借繖鏍风悊瑙o細 +这段代码很能体现 MiniEngine 的风格: -- 姣忎釜 `GpuResource` 鑷繁璁颁綇鈥滄垜鐜板湪澶ф澶勪簬浠€涔堢姸鎬佲€?- 浣犺鍒囧埌鏂扮姸鎬佹椂锛屼笉鏄珛鍒昏皟涓€娆?D3D12 barrier锛岃€屾槸鍏堝杩涗竴涓皬鏁扮粍 -- 婊′簡鎴栬€呬綘瑕佹眰绔嬪埢 flush锛屾墠鐪熸鎻愪氦缁欏懡浠ゅ垪琛?- 濡傛灉鐩爣鐘舵€佸凡缁忔槸 `UAV`锛岃繕浼氳ˉ涓€涓?UAV barrier +- 状态是手工跟踪的 +- Barrier 先缓存,不是每次都立刻提交 +- UAV 状态会补 UAV Barrier -杩欏氨鏄吀鍨嬬殑鈥滃紩鎿庡眰鐘舵€佽窡韪€濄€? -瀹冪殑浼樼偣鏄畝鍗曠洿鎺ワ紝缂虹偣涔熷緢鏄庢樉锛? -- 杩欐槸鎵嬪伐鐘舵€佽窡韪紝涓嶆槸鑷姩璧勬簮璋冨害 -- 渚濊禆璋冪敤鑰呴伒瀹堢害瀹?- 涓嶅儚鐜颁唬 Render Graph 閭f牱鑳藉仛璺?pass 鐢熷懡鍛ㄦ湡鎺ㄥ +它不是现代 Render Graph 那种自动资源调度,而是典型的“显式引擎层管理”。 -浣嗗鏁欏鏉ヨ锛岃繖鎭版伆寰堝ソ锛屽洜涓轰綘鑳芥竻妤氱湅鍒版瘡涓€姝ャ€? -## 6. 鎻忚堪绗︺€丷oot Signature銆丳SO锛欴X12 鐨勫彟涓€澶ч毦鐐? -### 6.1 涓ょ鎻忚堪绗﹀垎閰嶆柟寮? -MiniEngine 鎶婃弿杩扮闂鍒嗘垚涓ょ被锛? -1. **闀挎湡瀛樺湪鐨?CPU 鍙鎻忚堪绗?* - - 鐢?`DescriptorAllocator` 鍒嗛厤 - - 璧勬簮鍒涘缓鏃舵嬁涓€娆? - 渚嬪 `ColorBuffer` 鐨?SRV / RTV / UAV +对初学者来说,这反而是优点,因为你能更清楚地看到资源状态是怎么变化的。 -2. **姣忓抚銆佹瘡娆?Draw/Dispatch 闇€瑕佺粦瀹氬埌 GPU 鐨?shader-visible 鎻忚堪绗?* - - 鐢?`DynamicDescriptorHeap` 鍔ㄦ€佹嫹璐? - 鐪熸缁戝畾鍓嶆墠濉炲埌 shader-visible heap +## 7. 描述符、Root Signature、PSO:D3D12 的另一大难点 -涓轰粈涔堣杩欎箞鍒嗭紵 +### 7.1 两套描述符分配体系 -鍥犱负 D3D12 涓細 +MiniEngine 把描述符分成两类: -- 璧勬簮鍒涘缓鏃剁敓鎴愮殑鎻忚堪绗︿笉涓€瀹氭斁鍦?shader-visible heap 閲?- GPU 鐪熸鑳界粦瀹氱殑鎻忚堪绗?heap 鏁伴噺鍜岀被鍨嬮兘鏈夐檺 -- 鎵€浠ュ父瑙佸仛娉曟槸鈥滈暱鏈?CPU 鎻忚堪绗?+ 涓存椂 GPU 鍙澶嶅埗鈥? -MiniEngine 鐨勫疄鐜版鏄繖涓璺€? -### 6.2 `DynamicDescriptorHeap` 鍦ㄥ共浠€涔? -瀹冪殑宸ヤ綔杩囩▼鍙互姒傛嫭涓猴細 +1. 长期存在的 CPU 可见描述符 +2. 每帧临时绑定到 GPU 的 shader-visible 描述符 -1. 鍏堟牴鎹?`RootSignature` 鎺ㄥ鍑烘瘡涓?descriptor table 闇€瑕佸嚑涓Ы浣?2. 璋冪敤 `SetDynamicDescriptor(s)` 鏃讹紝鍏堟妸 CPU handle 瀛樿繘涓€涓?cache -3. 鐪熸 `Draw` / `Dispatch` 鍓嶏紝濡傛灉鍙戠幇鏈夎剰琛紝灏辨妸杩欎簺鎻忚堪绗﹀鍒跺埌 shader-visible heap -4. 鐒跺悗鐢?`SetGraphicsRootDescriptorTable` / `SetComputeRootDescriptorTable` 缁戝畾 +前者由 `DescriptorAllocator` 管理,通常在资源创建时分配。 +后者由 `DynamicDescriptorHeap` 管理,真正 Draw/Dispatch 前再复制到 shader-visible heap。 -杩欎釜璁捐闈炲父鍏稿瀷锛屼篃闈炲父瀹炵敤銆? -瀹冨憡璇変綘涓€涓緢閲嶈鐨?DX12 浜嬪疄锛? -**Descriptor 涓嶆槸璧勬簮鏈綋锛岃€屾槸璧勬簮瑙嗗浘锛涘紩鎿庣湡姝i渶瑕佺鐞嗙殑鏄€滆鍥剧粦瀹氱殑鐢熷懡鍛ㄦ湡鈥濄€?* +这正是 D3D12 实际工程里非常典型的套路: + +**静态视图长期保存,动态绑定临时上传。** + +### 7.2 `DynamicDescriptorHeap` 的作用 + +它的流程可以概括为: + +1. 根据 Root Signature 推导每个 descriptor table 需要多大 +2. `SetDynamicDescriptor(s)` 时先把 CPU handle 存进缓存 +3. 真正 Draw/Dispatch 前,把脏表复制到 shader-visible heap +4. 调用 `SetGraphicsRootDescriptorTable` 或 `SetComputeRootDescriptorTable` + +所以它管理的不是“资源本体”,而是“资源视图绑定过程”。 + +### 7.3 `GraphicsCommon` 提供了共享绑定约定 + +`GraphicsCommon.cpp` 中,最重要的共享资源之一就是 `g_CommonRS`: -### 6.3 `GraphicsCommon` 缁熶竴浜嗏€滈€氱敤缁戝畾绾﹀畾鈥? -`GraphicsCommon.cpp` 閲屽畾涔変簡寰堝榛樿鐘舵€侊紝浣嗘渶鍏抽敭鐨勬槸杩欎釜鍏变韩 Root Signature锛? ```cpp g_CommonRS.Reset(4, 3); g_CommonRS[0].InitAsConstants(0, 4); @@ -368,28 +435,29 @@ g_CommonRS.InitStaticSampler(2, SamplerLinearBorderDesc); g_CommonRS.Finalize(L"GraphicsCommonRS"); ``` -杩欐剰鍛崇潃澶ч噺 compute pass 閮藉湪鍏变韩鍚屼竴濂楃粦瀹氱害瀹氾細 - -- Root 0锛氬皯閲忓父閲?- Root 1锛歋RV 琛?- Root 2锛歎AV 琛?- Root 3锛氬父閲忕紦鍐? -鎵€浠ヤ綘鐪嬪緢澶氭ā鍧楁椂浼氬彂鐜板畠浠€诲湪鍐欙細 +很多 compute pass 都基于这套 Root Signature,因此你会在很多模块里反复看到: - `SetConstants(0, ...)` - `SetDynamicDescriptor(1, ...)` - `SetDynamicDescriptor(2, ...)` - `SetDynamicConstantBufferView(3, ...)` -涓嶆槸宸у悎锛岃€屾槸鏁村 Core 鍦ㄥ鐢ㄨ繖濂楀叕鍏辨牴绛惧悕銆? -### 6.4 Root Signature 鍜?PSO 閮藉仛浜嗙紦瀛樺幓閲? -`RootSignature::Finalize()` 鍜?`GraphicsPSO::Finalize()` / `ComputePSO::Finalize()` 閮戒細鍏?hash 鐘舵€侊紝鍐嶆煡缂撳瓨銆? -濂藉鏄細 +这不是巧合,而是整个 Core 在复用一套公共约定。 -- 鐩稿悓閰嶇疆涓嶄細閲嶅鍒涘缓 D3D12 瀵硅薄 -- 澶氱嚎绋嬪満鏅笅涔熷敖閲忛伩鍏嶉噸澶嶇紪璇? -杩欐槸涓€绉嶅緢鏍囧噯鐨勫紩鎿庡啓娉曪紝閫傚悎鍊熼壌銆? -## 7. GPU 璧勬簮鎶借薄锛氳繖濂?Core 鏄€庝箞鈥滃懡鍚嶅拰鍖呰璧勬簮鈥濈殑 +### 7.4 Root Signature 和 PSO 都带缓存 + +`RootSignature::Finalize()` 和 `GraphicsPSO::Finalize()` / `ComputePSO::Finalize()` 都会先做哈希,再去缓存表里查找。 + +这样做的好处: + +- 相同配置不会重复创建对象 +- 多线程下也尽量避免重复编译 +- 更符合引擎的资源复用思路 + +## 8. GPU 资源抽象:Core 是怎么组织资源类型的 + +从类关系上看,资源层可以这样理解: -### 7.1 绫荤户鎵垮叧绯? -鍙互鍏堟妸璧勬簮灞傜湅鎴愪笅闈㈣繖妫垫爲锛? ```text GpuResource |- PixelBuffer @@ -403,23 +471,20 @@ GpuResource `- Texture ``` -### 7.2 `GpuResource`锛氭墍鏈?GPU 璧勬簮鐨勫叡鍚屽熀绫? -`GpuResource` 寰堝皬锛屼絾寰堝叧閿€傚畠闄や簡淇濆瓨 `ID3D12Resource` 涔嬪锛岃繕淇濆瓨浜嗭細 +### 8.1 `GpuResource`:所有 GPU 资源的公共基类 -- `m_UsageState` -- `m_TransitioningState` -- `m_GpuVirtualAddress` -- `m_VersionID` +`GpuResource` 里除了 `ID3D12Resource`,还保存了: -涔熷氨鏄锛屼粠寮曟搸瑙掑害鐪嬶紝涓€涓?GPU 璧勬簮涓嶅彧鏄€滀竴涓?COM 瀵硅薄鈥濓紝杩樺寘鎷細 +- 当前资源状态 `m_UsageState` +- 正在 transition 的状态 `m_TransitioningState` +- GPU 虚拟地址 `m_GpuVirtualAddress` +- 版本号 `m_VersionID` -- 褰撳墠鐘舵€?- 鏄惁鍦ㄥ仛 split barrier -- GPU 鍦板潃 -- 鏄惁琚噸寤鸿繃 +也就是说,在引擎层,一个资源不只是一个 COM 指针,它还附带状态和生命周期信息。 -### 7.3 `PixelBuffer`锛氱汗鐞嗙被璧勬簮鐨勫叕鍏卞眰 +### 8.2 `PixelBuffer`:纹理类资源的公共父类 -`PixelBuffer` 璐熻矗澶勭悊鍚勭 DXGI Format 鍙樹綋锛屾瘮濡傦細 +`PixelBuffer` 负责处理很多 D3D12 初学者很容易绕晕的格式问题: - Base Format - UAV Format @@ -427,52 +492,75 @@ GpuResource - Depth Format - Stencil Format -杩欑被鍑芥暟鐪嬭捣鏉ュ緢鐞愮锛屽叾瀹炴槸 D3D12 绾圭悊绯荤粺閲屾渶瀹规槗韪╁潙鐨勫湴鏂逛箣涓€銆傚洜涓烘繁搴︾汗鐞嗗父甯歌鐢?typeless resource锛屽啀娲剧敓鍑轰笉鍚岃鍥俱€? -### 7.4 `ColorBuffer`锛氶鑹茬汗鐞?+ 鍏ㄥ瑙嗗浘 +这很关键,因为深度资源经常需要 typeless resource 再配出不同视图。 + +### 8.3 `ColorBuffer` + +`ColorBuffer` 是最常见的纹理资源类之一,它会统一创建: -`ColorBuffer` 璐熻矗鍒涘缓锛? - RTV - SRV -- 姣忕骇 mip 鐨?UAV +- 每一级 Mip 对应的 UAV -杩樿兘鍋氾細 +它还能创建: -- `Create()` -- `CreateArray()` -- `CreateCube()` -- `GenerateMipMaps()` +- 普通 2D 颜色缓冲 +- 数组纹理 +- Cubemap +- 自动 Mipmap -瀹冪殑鎰忎箟闈炲父澶э紝鍥犱负鍦ㄨ繖濂?Core 閲岋細 +在 MiniEngine 中,以下很多核心资源都是 `ColorBuffer`: -- 涓诲満鏅鑹茬紦鍐叉槸 `ColorBuffer` -- GBuffer 鏄?`ColorBuffer` -- Bloom 姣忕骇缂撳啿鏄?`ColorBuffer` -- TAA 鍘嗗彶缂撳啿鏄?`ColorBuffer` -- IBL cubemap 鍜?LUT 涔熸槸 `ColorBuffer` +- `g_SceneColorBuffer` +- `g_GBufferA/B/C/D` +- `g_PostEffectsBuffer` +- `g_TemporalColor` +- `g_aBloomUAV*` +- `g_IBLCubeMap` +- `g_IBLDiffuseLDMap` +- `g_IBLSpecularLDMap` +- `g_IBLLut` -涔熷氨鏄锛?*瀹冩槸杩欏娓叉煋鍣ㄩ噷鏈€甯哥敤鐨勭汗鐞嗚祫婧愭娊璞°€?* +### 8.4 `DepthBuffer` -### 7.5 `DepthBuffer`锛氭繁搴︾紦鍐蹭笉鍙槸涓€涓?DSV +`DepthBuffer` 不只是创建一个 DSV,它还会创建: -`DepthBuffer` 涓嶅彧鍒涘缓榛樿 DSV锛岃繕鍒涘缓锛? -- 鍙娣卞害 DSV -- 鍙妯℃澘 DSV -- 娣卞害 SRV -- 妯℃澘 SRV +- 普通 DSV +- 只读深度 DSV +- 只读模板 DSV +- 深度 SRV +- 模板 SRV -杩欏緢閲嶈锛屽洜涓哄緢澶氬睆骞曞悗澶勭悊瑕侀噰鏍锋繁搴︼紝浣嗕富娓叉煋闃舵鍙堥渶瑕佸啓娣卞害銆? -MiniEngine 閫氳繃棰勫垱寤哄绉嶈鍥撅紝璁╀竴涓繁搴﹁祫婧愬湪涓嶅悓 pass 閲屽垏涓嶅悓鐢ㄦ硶銆? -### 7.6 `HierarchicalDepthBuffer`锛氫竴寮犫€滃绾ф繁搴︽瑙堝浘鈥? -`HierarchicalDepthBuffer` 涓嶆槸鏅€氭繁搴﹀浘锛岃€屾槸 HZB銆? -瀹冨垱寤烘椂浼氭妸鍘熷娣卞害灏哄鍚戜笂瀵归綈鍒?2 鐨勫箓锛岀劧鍚庡彇鍗婏細 +这就让同一份深度资源能在不同阶段切换用途: -```cpp -const uint32_t HZBWidth = std::max(Math::AlignPowerOfTwo(Width) >> 1, 1u); -const uint32_t HZBHeight = std::max(Math::AlignPowerOfTwo(Height) >> 1, 1u); -const int32_t NumMips = Math::Log2(std::max(HZBWidth, HZBHeight)) + 1; -``` +- 几何阶段写深度 +- 后处理阶段采样深度 +- 某些 pass 只读深度 + +### 8.5 `GpuBuffer` + +`GpuBuffer` 是缓冲区类资源的基类,往下分为: + +- `ByteAddressBuffer` +- `StructuredBuffer` +- `TypedBuffer` + +它们分别适合: + +- 原始字节访问 +- 结构化数组访问 +- 带固定格式访问 + +例如 `StructuredBuffer` 还内建了 Counter Buffer,这对 GPU 生成工作列表、粒子系统、AppendBuffer 风格算法很有用。 + +### 8.6 `HierarchicalDepthBuffer` + +这不是普通深度缓冲,而是 HZB。 + +它会按层级生成深度 mip 链,用来做更高效的深度查询和剔除。 + +`GenerateHZB()` 的核心结构如下: -鐒跺悗鍦?`GenerateHZB()` 閲岄€愮骇鐢熸垚锛? ```cpp for (uint32_t TopMip = 0; TopMip < m_NumMipMaps; ) { @@ -487,187 +575,227 @@ for (uint32_t TopMip = 0; TopMip < m_NumMipMaps; ) } ``` -HZB 鐨勪环鍊兼槸锛? -- 蹇€熷仛閬尅鍓旈櫎 -- 蹇€熷仛灞忓箷绌洪棿鏌ヨ -- 鎶娾€滈€愬儚绱犳繁搴︹€濆帇缂╂垚鈥滈€愬眰绾ф渶杩?鏈€杩戞繁搴︹€? -浣犲彲浠ユ妸瀹冪悊瑙f垚锛? -**鐢ㄤ竴寮?mip 閾炬潵琛ㄧず鈥滆繖鐗囧尯鍩熷ぇ姒傛湁澶氳繎/澶氳繙鐨勪笢瑗库€?*銆? -### 7.7 `GpuBuffer`锛欱uffer 涓栫晫鐨勬€诲叆鍙? -`GpuBuffer` 杩欐潯绾挎湇鍔′簬锛? -- 甯搁噺缂撳啿 -- 椤剁偣 / 绱㈠紩缂撳啿 -- 鍘熷瀛楄妭鍦板潃缂撳啿 -- 缁撴瀯鍖栫紦鍐?- Typed buffer +你可以把 HZB 理解成: -鍑犱釜瀛愮被鐨勮涔夊樊寮傚涓嬶細 +**一张“分层概括的深度图”,它不是记录单像素细节,而是记录一块区域的大概深度信息。** -| 绫诲瀷 | 鍏稿瀷鐢ㄩ€?| -| --- | --- | -| `ByteAddressBuffer` | 鍘熷 32-bit 瀛楀湴鍧€璁块棶 | -| `StructuredBuffer` | 缁撴瀯鍖栨暟鎹暟缁勶紝鏀寔 counter | -| `TypedBuffer` | 甯﹀浐瀹氭牸寮忕殑 buffer锛屼緥濡?`R11G11B10_FLOAT` | +## 9. `BufferManager`:把整帧资源显式写出来 + +`BufferManager.h` 是理解整个 Core 最有价值的文件之一,因为它几乎相当于一张“手写渲染图”。 + +它声明了大量全局缓冲: -`StructuredBuffer` 鐢氳嚦鑷繁甯︿簡涓€涓?`CounterBuffer`锛岃繖涔熸槸 Append/Consume 椋庢牸 GPU 绠楁硶甯歌鐨勯厤缃€? -## 8. `BufferManager`锛氳繖濂楀紩鎿庢妸涓€甯т細鐢ㄥ埌鐨勭紦鍐插叏閮ㄦ樉寮忓啓鍑烘潵浜? -杩欎竴灞傚鏂颁汉闈炲父鏈夋暀瀛︽剰涔夛紝鍥犱负瀹冨嚑涔庡氨鏄竴寮犫€滀汉宸ュ啓姝荤殑娓叉煋鍥锯€濄€? -`BufferManager.h` 閲屼竴鍙f皵澹版槑浜嗗嚑鍗佷釜鍏ㄥ眬缂撳啿锛? ```cpp -extern DepthBuffer g_SceneDepthBuffer; // D32_FLOAT_S8_UINT -extern ColorBuffer g_SceneColorBuffer; // R11G11B10_FLOAT -extern ColorBuffer g_GBufferA; // R16G16B16A16_FLOAT -extern ColorBuffer g_GBufferB; // R16G16B16A16_FLOAT -extern ColorBuffer g_GBufferC; // R16G16B16A16_FLOAT -extern ColorBuffer g_GBufferD; // R16G16B16A16_FLOAT -extern ColorBuffer g_PostEffectsBuffer; // R32_UINT -extern ColorBuffer g_OverlayBuffer; // R8G8B8A8_UNORM -extern ColorBuffer g_VelocityBuffer; // R10G10B10 -extern ColorBuffer g_SSAOFullScreen; // R8_UNORM +extern DepthBuffer g_SceneDepthBuffer; +extern ColorBuffer g_SceneColorBuffer; +extern ColorBuffer g_GBufferA; +extern ColorBuffer g_GBufferB; +extern ColorBuffer g_GBufferC; +extern ColorBuffer g_GBufferD; +extern ColorBuffer g_PostEffectsBuffer; +extern ColorBuffer g_OverlayBuffer; +extern ColorBuffer g_VelocityBuffer; +extern ColorBuffer g_SSAOFullScreen; extern ColorBuffer g_TemporalColor[2]; extern ColorBuffer g_aBloomUAV1[2]; ... ``` -杩欒鏄?MiniEngine Core 涓嶆槸鈥滃埌浜嗘煇涓?pass 鍐嶄复鏃舵兂瑕佷粈涔堣祫婧愨€濓紝鑰屾槸锛? -**鎻愬墠鎶婁竴鏁村抚闇€瑕佺殑璧勬簮甯冨眬鎯虫竻妤氾紝鐒跺悗闆嗕腑鍒涘缓銆?* +这说明 Core 的思路不是“哪个 pass 需要资源再临时想”,而是: -杩欑鍋氭硶鐨勪紭鐐癸細 +**先把整帧的资源布局想清楚,再集中创建。** -- 缁撴瀯娓呮櫚 -- 闈炲父閫傚悎鏁欏鍜?sample -- 濂借皟璇曪紝璧勬簮鍚嶄篃寰堢洿瑙? -缂虹偣锛? -- 鍏ㄥ眬鍙橀噺寰堝 -- 鎵╁睍鎬т竴鑸?- 涓嶅儚鐜颁唬 Render Graph 閭f牱鑷姩鎺ㄥ鐢熷懡鍛ㄦ湡 +### 9.1 为什么这对初学者特别有帮助 -### 8.1 鍒涘缓椤哄簭鍏跺疄灏辨槸娓叉煋娴佺▼椤哄簭 +因为当你看 `InitializeRenderingBuffers()` 时,你实际上是在看一张“这个渲染器一帧要用到什么资源”的清单。 -`InitializeRenderingBuffers()` 閲岃祫婧愬垱寤烘槸鎸夋覆鏌撻樁娈电粍缁囩殑锛? -- 鍦烘櫙涓婚鑹?/ 娉曠嚎 / GBuffer -- IBL 鐩稿叧璐村浘 -- 娣卞害銆丠ZB -- SSAO 鐩稿叧涓棿缂撳啿 -- 闃村奖璐村浘 -- 鏅繁涓棿缂撳啿鍜岄槦鍒?- TAA 鍘嗗彶棰滆壊 -- Motion Blur 棰勫鐞嗙紦鍐?- Bloom / Luma / Histogram -- FXAA 闃熷垪 -- Overlay / HorizontalBuffer +它按阶段组织资源: + +- 场景主颜色和法线 +- GBuffer +- IBL 资源 +- 深度和 HZB +- SSAO 中间资源 +- 阴影贴图 +- 景深资源 +- TAA 历史资源 +- Bloom 资源 +- FXAA 队列 +- Overlay 和显示相关资源 + +这非常适合理解“一个真实渲染器为什么会需要这么多中间缓冲”。 + +## 10. `Display`:最后一公里 + +`Display` 负责的不是场景本身,而是: + +- 创建 SwapChain +- Resize 时重建显示相关资源 +- HDR / SDR 输出 +- 分辨率缩放 +- UI 合成 +- 最终 Present + +### 10.1 为什么场景不直接画到 SwapChain + +因为场景渲染后通常还要做: + +- Tone Mapping +- Bloom +- FXAA/TAA +- UI 叠加 +- 分辨率缩放 + +所以正确流程是: + +1. 场景先画进内部颜色缓冲 +2. 后处理在内部缓冲上完成 +3. UI 先画到 `g_OverlayBuffer` +4. 最后由 `Display::Present()` 统一合成到交换链 + +### 10.2 它区分“原生分辨率”和“显示分辨率” + +`Display.cpp` 里有两组分辨率: -涔熷氨鏄锛岃繖涓嚱鏁拌櫧鐒跺彨鈥滃垱寤虹紦鍐测€濓紝鏈川涓婁篃鏄竴寮犫€滄覆鏌撲緷璧栧叧绯昏鏄庝功鈥濄€? -### 8.2 鍘熺敓鍒嗚鲸鐜囧拰鏄剧ず鍒嗚鲸鐜囪鍒嗗紑浜? -`Display.cpp` 涓湁涓や釜姒傚康锛? - `g_NativeWidth / g_NativeHeight` - `g_DisplayWidth / g_DisplayHeight` -鍓嶈€呮槸鍐呴儴娓叉煋鍒嗚鲸鐜囷紝鍚庤€呮槸鏈€缁堣緭鍑哄垎杈ㄧ巼銆? -杩欐剰鍛崇潃 Core 鏈韩鏀寔锛? -- 鍐呴儴鍒嗚鲸鐜囨覆鏌?- 杈撳嚭鍒嗚鲸鐜囩缉鏀?- UI 鍙犲姞 -- HDR / SDR 涓嶅悓鏄剧ず璺緞 +前者代表内部渲染分辨率,后者代表最终输出到窗口的分辨率。 -杩欐槸寰堝寮曟搸閮戒細鍋氱殑浜嬶紝浣?MiniEngine 鎶婂畠鍐欏緱寰堢洿鐧姐€? -## 9. `Display`锛氱湡姝h礋璐b€滄渶鍚庝竴鍏噷鈥濈殑妯″潡 +这意味着 Core 天生支持: -`Display` 璐熻矗鐨勪笉鏄満鏅覆鏌擄紝鑰屾槸锛? -- 鍒涘缓 SwapChain -- 鍦?Resize 鏃堕噸寤烘樉绀虹浉鍏宠祫婧?- 鎶婂唴閮ㄩ鑹茬紦鍐查€佸埌浜ゆ崲閾?- 鍋氬繀瑕佺殑缂╂斁銆乁I 鍚堟垚銆丠DR 杈撳嚭 +- 低于显示分辨率的内部渲染 +- 输出前缩放 +- HDR/SDR 不同 Present 路径 -### 9.1 涓轰粈涔堝満鏅笉鐩存帴娓叉煋鍒颁氦鎹㈤摼 +## 11. 相机系统:不只是 View/Proj -鍥犱负浜ゆ崲閾惧彧閫傚悎鍋氭渶缁堣緭鍑猴紝涓嶉€傚悎鍋氭暣鏉″悗澶勭悊閾剧殑宸ヤ綔缂撳啿銆? -MiniEngine 鐨勬€濊矾鏄細 +### 11.1 `BaseCamera` 每帧更新的不只是矩阵 -1. 鍏堟覆鏌撳埌 `g_SceneColorBuffer` -2. 鍚庡鐞嗛兘鍦ㄥ唴閮?buffer 涓婂仛 -3. UI 鍏堢敾鍒?`g_OverlayBuffer` -4. `Display::Present()` 鍐嶆妸鍦烘櫙鍜?UI 鍚堟垚鍚庤緭鍑? -杩欐牱鍋氱殑濂藉鏄細 +`BaseCamera::Update()` 会计算: -- 鏇撮€傚悎 HDR / SDR 鍙岃矾寰?- 鏇撮€傚悎缂╂斁鍜岄攼鍖?- 鏇撮€傚悎璋冭瘯鍜屾埅鍥? -### 9.2 Present 鍓嶄細鍋氫粈涔? -`PreparePresentSDR()` 鍜?`PreparePresentHDR()` 鍩烘湰閮介伒寰悓涓€涓璺細 +- `m_ViewMatrix` +- `m_ViewProjMatrix` +- `m_PreviousViewProjMatrix` +- `m_ReprojectMatrix` +- `m_FrustumVS` +- `m_FrustumWS` -- 璁?`g_SceneColorBuffer` 杩?SRV 鐘舵€?- 濡傛灉闇€瑕佺缉鏀撅紝灏辫皟鐢ㄤ笓闂ㄧ殑缂╂斁璺緞 -- 鎶?`g_OverlayBuffer` 褰?SRV 鍙犲姞 -- 杈撳嚭鍒?swapchain back buffer -- 鏈€鍚庡垏鍒?`PRESENT` +这意味着相机在 MiniEngine 里同时服务于: -杩欐剰鍛崇潃锛? -**鍦烘櫙棰滆壊缂撳啿涓嶆槸鈥滄渶鍚庣殑鐢婚潰鈥濓紝鑰屾槸鈥滄渶缁堟樉绀哄墠鐨勫師鏉愭枡鈥濄€?* +- 普通渲染 +- 剔除 +- TAA +- Motion Blur +- 其他时间域效果 -## 10. `GraphicsCommon`锛氭暣涓?Core 鐨勨€滃叕鍏辩姸鎬佷粨搴撯€? -杩欎竴灞傞潪甯稿儚寮曟搸鍐呴儴鐨勫叏灞€娓叉煋璇嶅吀銆? -瀹冪粺涓€鍒濆鍖栵細 +### 11.2 默认用了 Reverse-Z -- 榛樿閲囨牱鍣?- 榛樿鏍呮牸鍖栫姸鎬?- 榛樿娣峰悎鐘舵€?- 榛樿娣卞害鐘舵€?- 榛樿绾圭悊 -- 闂存帴鍛戒护绛惧悕 -- 鐢熸垚 mip / HZB / 娓?buffer 鐨勯€氱敤 PSO - -姣斿杩欓噷浼氬垱寤猴細 - -- `SamplerLinearWrap` -- `SamplerPointClamp` -- `BlendDisable` -- `BlendTraditional` -- `DepthStateReadWrite` -- `DepthStateReadOnly` -- `g_CommonRS` - -杩欐湁浠€涔堝ソ澶勶紵 - -- 姣忎釜妯″潡涓嶅繀鑷繁閲嶅瀹氫箟鈥滅嚎鎬?Clamp 閲囨牱鍣ㄢ€?- 澶ч噺 compute pass 鍙互鐩存帴鍏变韩 `g_CommonRS` -- 鏁翠釜宸ョ▼鐨勯粯璁ゆ覆鏌撶害瀹氫繚鎸佷竴鑷? -瀵瑰紩鎿庢潵璇达紝杩欏眰寰堥噸瑕侊紝鍥犱负瀹冩妸鈥滃叕鍏遍粯璁ゅ€尖€濋泦涓寲浜嗐€? -## 11. 鍚庡鐞嗙郴缁燂細MiniEngine Core 鏈€鍊煎緱瀛︿範鐨勪竴閮ㄥ垎涔嬩竴 - -`PostEffects` 鏄暣涓?Core 鐨勫睆骞曞悗澶勭悊涓灑銆? -瀹冨仛鐨勪簨鎯呭寘鎷細 - -- HDR / LDR 璺緞閫夋嫨 -- 鑷姩鏇濆厜 -- Bloom -- 浜害鎻愬彇 -- Histogram 缁熻 -- Tone Mapping -- 鍜?FXAA / TAA / DoF / Motion Blur 鐨勮鎺? -### 11.1 鍚庡鐞嗘牴绛惧悕闈炲父缁熶竴 - -`PostEffects::Initialize()` 閲屽缓绔嬬殑鏍圭鍚嶇粨鏋勫緢绠€鍗曪細 +`Camera::UpdateProjMatrix()` 中有一段经典注释: ```cpp -PostEffectsRS.Reset(4, 2); -PostEffectsRS.InitStaticSampler(0, SamplerLinearClampDesc); -PostEffectsRS.InitStaticSampler(1, SamplerLinearBorderDesc); -PostEffectsRS[0].InitAsConstants(0, 5); -PostEffectsRS[1].InitAsDescriptorRange(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 0, 4); -PostEffectsRS[2].InitAsDescriptorRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 4); -PostEffectsRS[3].InitAsConstantBuffer(1); -PostEffectsRS.Finalize(L"Post Effects"); +// ReverseZ puts far plane at Z=0 and near plane at Z=1. This is never a bad idea, and it's +// actually a great idea with F32 depth buffers to redistribute precision more evenly across +// the entire range. It requires clearing Z to 0.0f and using a GREATER variant depth test. ``` -杩欑被璁捐鐨勫ソ澶勬槸锛屽緢澶?compute pass 閮借兘鍏辩敤涓€濂楀弬鏁板竷灞€銆? -### 11.2 Bloom 鏄€庝箞缁勭粐鐨? -Bloom 鐢ㄤ簡浜旂骇涓嬮噰鏍风紦鍐诧細 +这是非常值得初学者认真理解的知识点: -- `g_aBloomUAV1` -- `g_aBloomUAV2` -- `g_aBloomUAV3` -- `g_aBloomUAV4` -- `g_aBloomUAV5` +- 普通深度:近处 0,远处 1 +- Reverse-Z:近处 1,远处 0 +- 配合浮点深度缓冲,精度分布更合理 -楂樿川閲忔ā寮忎細鍋氭洿澶氱骇 downsample + upsample + blur銆? -浠?`GenerateBloom()` 浣犱細鐪嬪埌涓€涓緢鍏稿瀷鐨勫悗澶勭悊濂楄矾锛? -1. 浠?HDR 鍦烘櫙閲屾彁浜儴 -2. 涓嬮噰鏍锋垚鏇村皬鐨勭汗鐞?3. 瀵瑰皬绾圭悊鍋氭ā绯?4. 鍐嶉€愮骇涓婇噰鏍峰悎鎴愬洖鍘? -杩欎篃鏄緢澶氬疄闄呴」鐩殑 bloom 鍩烘湰濂楄矾銆? -### 11.3 鑷姩鏇濆厜涓嶆槸鈥滄媿鑴戣璋冧寒搴︹€? -`PostEffects` 涓嶆槸鐩存帴鐢ㄤ竴涓父鏁版洕鍏夛紝鑰屾槸锛? -1. 浠庡満鏅彁鍙栦寒搴?2. 鐢熸垚 histogram -3. 鏍规嵁鐩爣浜害銆侀€傚簲閫熷害銆佹渶澶ф渶灏忔洕鍏夋洿鏂?`g_Exposure` +这也是为什么 `GraphicsCommon.cpp` 里的默认深度状态是 `GREATER_EQUAL`。 -鎵€浠ヨ繖閲屽叾瀹炲疄鐜扮殑鏄竴涓畝鍖栫増鐩告満鏇濆厜妯″瀷銆? -### 11.4 `Render()` 鏄暣鏉″悗澶勭悊涓荤嚎 +### 11.3 `ShadowCamera` 做了稳定化处理 + +阴影相机为了减少阴影抖动,会按 shadow map texel 尺度做量化: + +```cpp +ShadowCenter = ~GetRotation() * ShadowCenter; +if (BufferPrecision < 32) +{ + Vector3 QuantizeScale = Vector3((float)BufferWidth, (float)BufferHeight, (float)((1 << BufferPrecision) - 1)) * RcpDimensions; + ShadowCenter = Floor( ShadowCenter * QuantizeScale ) / QuantizeScale; +} +ShadowCenter = GetRotation() * ShadowCenter; +``` + +这段代码的目的非常实际: + +**尽量让阴影相机按整 texel 移动,避免阴影边缘不停抖。** + +## 12. 输入系统:`GameInput` + +`GameInput` 把不同来源的输入统一成一套接口: + +- 键盘 +- 鼠标 +- 手柄 + +它提供的能力包括: + +- 当前帧是否按下 +- 是否刚按下 +- 是否刚释放 +- 按住了多久 +- 模拟量输入 +- 按时间修正后的模拟量输入 + +最常用的接口是: + +- `IsPressed()` +- `IsFirstPressed()` +- `GetAnalogInput()` +- `GetTimeCorrectedAnalogInput()` + +这样相机控制器和调试系统就不用关心输入设备的具体来源。 + +## 13. `CameraController`:行为层 + +`Camera` 负责相机数据,`CameraController` 负责相机行为。 + +Core 里主要有两种控制器: + +- `FlyingFPSCamera` +- `OrbitCamera` + +它们会从 `GameInput` 读取: + +- 左右摇杆 +- 鼠标移动 +- 鼠标滚轮 +- WASD / QE + +然后做: + +- 视角旋转 +- 位移 +- 升降 +- 动量平滑 + +这说明 Core 的分层是合理的: + +- 相机数据和相机交互逻辑被分开 + +## 14. 后处理系统:Core 最值得学习的一部分 + +`PostEffects` 是整套后处理的主中枢。 + +它负责: + +- HDR/LDR 路径选择 +- 自动曝光 +- Bloom +- 亮度提取 +- Histogram +- Tone Mapping +- 与 FXAA、DoF、Motion Blur 的衔接 + +### 14.1 每帧后处理主流程 + +`PostEffects::Render()` 的骨架如下: -鐪熸鐨勮皟搴︽牳蹇冨湪杩欓噷锛? ```cpp void PostEffects::Render( void ) { @@ -689,27 +817,59 @@ void PostEffects::Render( void ) if (!g_bTypedUAVLoadSupport_R11G11B10_FLOAT) CopyBackPostBuffer(Context); - ... - Context.Finish(); } ``` -杩欐浠g爜璇存槑锛? -- 鍚庡鐞嗘暣浣撶敤 compute 瀹屾垚 -- HDR 鍜?LDR 鏄袱鏉′笉鍚岃矾寰?- FXAA 鏄悗澶勭悊閾剧殑涓€鐜紝鑰屼笉鏄嫭绔嬬郴缁?- 鍏煎璺緞闇€瑕佹妸 `g_PostEffectsBuffer` 鎷峰洖 `g_SceneColorBuffer` +这段代码很重要,因为它明确说明: -## 12. 鏃堕棿鍩熸晥鏋滐細TAA 鏄浣曟帴鍒颁富妗嗘灦閲岀殑 +- 后处理主要走 compute 路径 +- HDR 和 LDR 是两条不同流程 +- FXAA 是整条链的一部分 +- 硬件兼容路径最终可能需要 copy back -`TemporalEffects` 鏄彟涓€鍧楅潪甯搁€傚悎瀛︿範鐨勪唬鐮併€? -瀹冨仛浜嗕笁浠朵簨锛? -1. 缁存姢姣忓抚 jitter 鍋忕Щ -2. 缁存姢鍘嗗彶棰滆壊缂撳啿 -3. 鍦ㄥ綋鍓嶅抚鍜屽巻鍙插抚涔嬮棿鍋?temporal blend 鍜?sharpen +### 14.2 Bloom 的实现很典型 -### 12.1 Jitter 浣跨敤浜?Halton 搴忓垪 +Bloom 使用了多个下采样缓冲: + +- `g_aBloomUAV1` +- `g_aBloomUAV2` +- `g_aBloomUAV3` +- `g_aBloomUAV4` +- `g_aBloomUAV5` + +大体流程是: + +1. 从场景中提取亮部 +2. 多级下采样 +3. 模糊 +4. 逐级上采样并合成回去 + +这是非常经典的工程做法。 + +### 14.3 自动曝光不是固定常数 + +Core 里不是简单用一个曝光常量,而是: + +1. 先提取亮度 +2. 生成 Histogram +3. 根据目标亮度和适应速度更新曝光缓冲 `g_Exposure` + +这就让亮度调节更接近真实相机的适应行为。 + +## 15. 时间域效果:`TemporalEffects` + +`TemporalEffects` 负责: + +- 维护帧号 +- 维护 jitter 偏移 +- 管理历史颜色缓冲 +- 做 temporal blend 和 sharpen + +### 15.1 Jitter 使用 Halton 采样 + +`TemporalEffects::Update()` 中有固定采样序列: -`TemporalEffects::Update()` 閲屾湁涓€缁勫浐瀹氶噰鏍风偣锛? ```cpp static const float Halton23[8][2] = { @@ -720,92 +880,72 @@ static const float Halton23[8][2] = }; ``` -鐒跺悗鏍规嵁甯у彿寰幆浣跨敤杩欎簺鍋忕Щ銆? -杩欎欢浜嬬殑鎰忎箟鏄細 +这意味着每一帧都会轻微偏移采样位置,配合历史累积实现 TAA。 -- 姣忎竴甯ч兘鐣ュ井鍋忕Щ閲囨牱浣嶇疆 -- 澶氬抚绱鍚庯紝鐩稿綋浜庡仛浜嗘洿瀵嗙殑绌洪棿閲囨牱 -- 鍐嶇粨鍚堥€熷害缂撳啿鍜屽巻鍙叉贩鍚堬紝灏辫兘寰楀埌 TAA 鏁堟灉 +### 15.2 历史颜色是双缓冲 -### 12.2 鍘嗗彶缂撳啿鏄弻缂撳啿 +资源上有: -`BufferManager` 閲屾湁锛? - `g_TemporalColor[0]` - `g_TemporalColor[1]` -姣忓抚 `Src` 鍜?`Dst` 浜ゆ浛浣跨敤銆? -杩欐槸鏃堕棿鍩熺畻娉曠殑甯歌鍐欐硶锛屽洜涓轰綘闇€瑕侊細 +每帧交替读写。 -- 涓€寮犱綔涓轰笂涓€甯?history -- 涓€寮犱綔涓哄綋鍓嶅抚杈撳嚭 history +这也是时间域算法最典型的结构。 -### 12.3 鐩告満浼氭彁渚?reprojection matrix +## 16. 其他屏幕空间模块 -`BaseCamera::Update()` 浼氭洿鏂帮細 +### 16.1 FXAA -- `m_ViewMatrix` -- `m_ViewProjMatrix` -- `m_PreviousViewProjMatrix` -- `m_ReprojectMatrix` +FXAA 作为后处理链的一部分,在最终画面上处理高对比边缘。 -杩欎娇寰?TAA銆丮otion Blur 绛夋晥鏋滃彲浠ユ妸鈥滃綋鍓嶅儚绱犫€濇槧灏勫洖鈥滀笂涓€甯у儚绱犫€濄€? -杩欎篃鏄负浠€涔?`BaseCamera` 涓嶅彧鏄€滃瓨浣嶇疆鍜屾柟鍚戔€濓紝鑰屾槸鏁翠釜鏃堕棿鍩熸覆鏌撶殑鍩虹璁炬柦銆? -## 13. 鍏朵粬灞忓箷绌洪棿鏁堟灉锛氳繖濂?Core 宸茬粡杩滀笉姝竴涓熀纭€妗嗘灦 +它是屏幕空间抗锯齿中实现较轻的一种,适合放在链路末尾。 -### 13.1 FXAA +### 16.2 SSAO -`FXAA` 鏄渶鍚庨樁娈电殑灞忓箷绌洪棿鎶楅敮榻匡紝璐熻矗澶勭悊鏈€缁堢敾闈㈤噷鐨勯珮瀵规瘮杈圭紭銆? -瀹冧笉鏄崟鐙殑妗嗘灦锛岃€屾槸鐢?`PostEffects::Render()` 璋冪敤锛? -- 濡傛灉宸茬粡鏈夐璁$畻浜害锛屽氨澶嶇敤 -- 娌℃湁灏辫嚜宸辩敓鎴? -杩欒鏄?MiniEngine 鐨?FXAA 鏄綔涓衡€滃悗澶勭悊鏈妯″潡鈥濇寕鍦ㄤ富閾句笂鐨勩€? -### 13.2 SSAO +SSAO 依赖很多中间缓冲,例如: -`SSAO` 涓昏鍥寸粫杩欎簺璧勬簮灞曞紑锛? - `g_LinearDepth` - `g_MinMaxDepth8/16/32` - `g_DepthDownsize*` - `g_DepthTiled*` - `g_AOMerged*` - `g_AOSmooth*` -- `g_AOHighQuality*` -- `g_SSAOFullScreen` -鍏夌湅杩欎簺鍚嶅瓧锛屼綘灏辫兘澶ф鐚滃埌瀹冩祦绋嬫槸锛? -- 娣卞害绾挎€у寲 -- 鍒嗗潡 / 鍘讳氦閿?- AO 浼拌 -- 閲嶇粍 -- 骞虫粦 +只看这些命名,你就能大概推断它的工程结构: -### 13.3 XeGTAO +- 深度线性化 +- 降采样 +- 分块/去交错 +- AO 估计 +- 重组与平滑 -Core 閲岃繕闆嗘垚浜嗘洿鐜颁唬鐨?`XeGTAO` 瀹炵幇銆傚畠浼氾細 +### 16.3 XeGTAO -- 鍏堟妸娣卞害棰勮繃婊ゆ垚 mip 閾?- 涓?pass 璁$畻 AO 鍜?edge -- 鍋氳嫢骞叉 denoise -- 鏈€缁?resolve 鍒?`g_SSAOFullScreen` +Core 里还集成了更现代的 `XeGTAO`: -瀹冪敋鑷充細鎶?TAA 鐨勫抚鍙锋嬁鏉ュ仛 temporal noise锛? -```cpp -const bool useTemporalNoise = (bool)TemporalEffects::EnableTAA; -outConstants.NoiseIndex = (XeGTAO::g_DenoisePasses > 0 && useTemporalNoise) ? (frameIndex % 64) : 0; -``` +- 深度预过滤 +- 主 AO Pass +- Denoise +- Resolve 到 `g_SSAOFullScreen` -杩欒鏄?MiniEngine Core 涓嶆槸鈥滃嚑涓簰涓嶇浉骞茬殑灏忎緥瀛愨€濓紝鑰屾槸妯″潡涔嬮棿宸茬粡瀛樺湪鍗忎綔鍏崇郴銆? -### 13.4 Motion Blur +这说明 Core 不只是“基础框架”,还已经包含较现代的屏幕空间 AO 实现。 -`MotionBlur` 鏃㈡敮鎸侊細 +### 16.4 Motion Blur -- 鍩轰簬鐩告満 reprojection 鐨?camera blur -- 鍩轰簬閫熷害缂撳啿鐨?object blur +支持: -杩欎篃鏄竴涓緢濂界殑瀛︿範鐐癸細 +- 基于相机重投影的模糊 +- 基于速度缓冲的对象模糊 -**鏃堕棿鍩熺壒鏁堢殑鍏抽敭涓嶆槸 blur 鏈韩锛岃€屾槸鈥滃儚绱犺繍鍔ㄤ俊鎭粠鍝噷鏉モ€濄€?* +这很适合帮助新手理解一个关键点: -### 13.5 Depth of Field +**时间域特效的核心不是“怎么模糊”,而是“怎么得到像素运动信息”。** + +### 16.5 Depth of Field + +DoF 相关缓冲包括: -DoF 鐩稿叧璧勬簮鍖呮嫭锛? - `g_DoFTileClass` - `g_DoFPresortBuffer` - `g_DoFPrefilter` @@ -815,30 +955,29 @@ DoF 鐩稿叧璧勬簮鍖呮嫭锛? - `g_DoFFastQueue` - `g_DoFFixupQueue` -浠庡悕瀛楀氨鑳界湅鍑猴紝杩欎笉鏄渶鏈寸礌鐨勫叏灞忔ā绯婏紝鑰屾槸鍋氫簡锛? -- tile classification -- work queue -- fast path / fixup path +从这些名字就能看出,它不是最朴素的全屏模糊,而是带有 tile classification 和 work queue 的工程实现。 -涔熷氨鏄 MiniEngine 鐨?DoF 宸茬粡鏄€滃伐绋嬪疄鐜扳€濓紝涓嶆槸璇惧爞婕旂ず鐗堛€? -## 14. IBL锛氱幆澧冨厜鐓ч璁$畻鏄€庝箞钀藉湴鐨? -`IBL.cpp` 寰堥€傚悎鍏ラ棬鑰咃紝鍥犱负瀹冩妸鈥滀粠 HDRI 鍒板彲鐢ㄤ簬 PBR 鐨勭幆澧冭创鍥锯€濆啓寰楀緢鐩淬€? -瀹冧細棰勮绠楀洓绫昏祫婧愶細 +## 17. IBL:环境光照预计算 + +`IBL.cpp` 很适合教学,因为它把“从 HDRI 到 PBR 可用贴图”的过程写得很清楚。 + +它会预计算: - `g_IBLCubeMap` - `g_IBLDiffuseLDMap` - `g_IBLSpecularLDMap` - `g_IBLLut` -`Precompute()` 閲岀殑鍥涙鍒嗗埆鏄細 +主要流程是: -1. 浠?HDRI 鐢熸垚 cubemap -2. 缁?cubemap 鐢熸垚 mip -3. 棰勭Н鍒?diffuse lighting distribution -4. 棰勭Н鍒?specular lighting distribution -5. 鐢熸垚 BRDF LUT +1. 把 HDRI 转成 Cubemap +2. 给 Cubemap 生成 Mip +3. 预积分 diffuse lighting +4. 预积分 specular lighting +5. 生成 BRDF LUT + +关键尺寸是写死的: -鐩稿叧甯搁噺灏哄鍦ㄤ唬鐮侀噷鏄啓姝荤殑锛? ```cpp const uint32_t g_IBLCubeMapSize = 1024; const uint32_t g_IBLDiffuseLDMapSize = 128; @@ -846,341 +985,152 @@ const uint32_t g_IBLSpecularLDMapSize = 512; const uint32_t g_IBLLutSize = 1024; ``` -杩欒鏄?MiniEngine 鐨?IBL 涓嶆槸杩愯鏃舵瘡鍍忕礌涓存椂绠楋紝鑰屾槸鍏堝仛棰勮绠楋紝鍐嶅湪鏉愯川鐫€鑹查樁娈甸噰鏍风粨鏋溿€? -瀵瑰垵瀛﹁€呮潵璇达紝杩欓噷鏈€璇ヨ浣忕殑涓€鐐规槸锛? -**鈥滃熀浜庡浘鍍忕殑鍏夌収鈥濆苟涓嶆槸鐩存帴鎷?HDR 璐村浘褰撳弽灏勯鑹诧紝鑰屾槸瑕佸厛鍋氫笉鍚?roughness / BRDF 鏉′欢涓嬬殑棰勭Н鍒嗐€?* +这说明 Core 的 IBL 不是运行时每像素硬算,而是先预计算,再在材质着色时采样结果。 -## 15. 鐩告満绯荤粺锛氫笉浠呬粎鏄?ViewMatrix +## 18. 调试与运行时工具 -### 15.1 `BaseCamera` 鏄墍鏈夋椂闂村煙鐗规晥鐨勫熀纭€ +### 18.1 `EngineTuning` -寰堝鏂版墜浼氭妸鐩告満鐞嗚В鎴愨€滀綅缃?+ 鏈濆悜 + 鎶曞奖鐭╅樀鈥濄€傚湪 MiniEngine 閲岋紝鐩告満杩滀笉姝㈣繖浜涖€? -`BaseCamera::Update()` 姣忓抚浼氶噸寤猴細 +Core 里很多参数都不是硬编码,而是注册成: -- ViewMatrix -- ViewProjMatrix -- PreviousViewProjMatrix -- ReprojectionMatrix -- View-space Frustum -- World-space Frustum - -杩欐剰鍛崇潃瀹冨悓鏃舵湇鍔′簬锛? -- 鏅€氭覆鏌?- 鍙鎬ф祴璇?- TAA -- Motion Blur -- 闃村奖 - -### 15.2 榛樿灏辨槸 Reverse-Z - -`Camera::UpdateProjMatrix()` 閲屾湁涓€娈甸潪甯哥粡鍏哥殑娉ㄩ噴锛? -```cpp -// ReverseZ puts far plane at Z=0 and near plane at Z=1. This is never a bad idea, and it's -// actually a great idea with F32 depth buffers to redistribute precision more evenly across -// the entire range. It requires clearing Z to 0.0f and using a GREATER variant depth test. -// Some care must also be done to properly reconstruct linear W in a pixel shader from hyperbolic Z. -``` - -杩欐璇濇槸鍥惧舰瀛﹀叆闂ㄨ€呭繀椤荤湡姝g悊瑙g殑鐭ヨ瘑鐐癸細 - -- 鏅€氭繁搴﹂€氬父 near=0, far=1 -- Reverse-Z 鍙嶈繃鏉ワ紝璁?near=1, far=0 -- 閰嶅悎娴偣娣卞害缂撳啿锛岃繙澶勭簿搴﹀垎甯冩洿鍚堢悊 -- 鍚屾椂娣卞害娴嬭瘯涔熻鏀规垚 `GREATER` 绯诲垪 - -杩欎篃鏄负浠€涔?`GraphicsCommon.cpp` 閲岄粯璁ょ殑娣卞害鐘舵€佹槸锛? -- `DepthStateReadWrite.DepthFunc = D3D12_COMPARISON_FUNC_GREATER_EQUAL` - -### 15.3 闃村奖鐩告満鍋氫簡鈥滅ǔ瀹氬寲鈥? -`ShadowCamera::UpdateMatrix()` 鐨勪竴娈典唬鐮侀潪甯告湁浠峰€硷細 - -```cpp -ShadowCenter = ~GetRotation() * ShadowCenter; -if (BufferPrecision < 32) -{ - Vector3 QuantizeScale = Vector3((float)BufferWidth, (float)BufferHeight, (float)((1 << BufferPrecision) - 1)) * RcpDimensions; - ShadowCenter = Floor( ShadowCenter * QuantizeScale ) / QuantizeScale; -} -ShadowCenter = GetRotation() * ShadowCenter; -``` - -瀹冨湪鍋氱殑浜嬫儏鏄細 - -- 鎶?shadow center 杞埌 light view space -- 鎸?texel 灏哄害閲忓寲 -- 鍐嶈浆鍥炰笘鐣岀┖闂? -鐩殑寰堟槑纭細 - -**璁╅槾褰辩浉鏈哄敖閲忔寜鏁?texel 绉诲姩锛屽噺灏戦槾褰辨姈鍔ㄥ拰 crawling銆?* - -### 15.4 鐩告満鎺у埗鍣ㄧ洿鎺ュ悆 `GameInput` - -`FlyingFPSCamera` 鍜?`OrbitCamera` 涓嶆槸鏁板绫伙紝鑰屾槸鈥滅浉鏈鸿涓哄眰鈥濄€? -瀹冧滑浠?`GameInput` 涓鍙栵細 - -- 宸﹀彸鎽囨潌 -- 榧犳爣绉诲姩 -- 榧犳爣婊氳疆 -- WASD / QE -- Shift / Thumb Click - -鐒跺悗鍋氾細 - -- heading / pitch 鏇存柊 -- 骞崇Щ / 涓婂崌 / 鏃嬭浆 -- 鍔ㄩ噺骞虫粦 - -鎵€浠ヨ繖濂?Core 鎶娾€滅浉鏈烘暟鎹€濆拰鈥滅浉鏈轰氦浜掓柟寮忊€濆垎寮€浜嗭紝杩欐槸姝g‘鐨勫垎灞傘€? -## 16. 杈撳叆绯荤粺锛歚GameInput` 鎶婂钩鍙板樊寮傛姽骞充簡 - -`GameInput` 鍋氫簡鍑犱欢寰堝疄闄呯殑浜嬶細 - -- 鎵嬫焺璧?XInput / GameInput -- 閿洏榧犳爣璧?DirectInput / WinRT -- 鎶婃墍鏈夎緭鍏ョ粺涓€鎴愪竴濂楁灇涓?- 淇濆瓨涓婁竴甯у拰褰撳墠甯х姸鎬?- 鎻愪緵 time-corrected analog input - -瀵瑰簲鐢ㄦ潵璇达紝瀹冩渶鏂逛究鐨勭偣鏄繖浜?API锛? -- `IsPressed()` -- `IsFirstPressed()` -- `IsReleased()` -- `GetAnalogInput()` -- `GetTimeCorrectedAnalogInput()` - -杩欒鐩告満鍜岃皟璇曡彍鍗曢兘涓嶅繀鍏冲績璁惧鏉ユ簮銆? -鍚屾椂锛岃繖涔熸毚闇插嚭 MiniEngine 鐨勫勾浠f劅锛? -- 浠嶇劧浣跨敤浜?DirectInput -- 鏄亸 sample / desktop demo 椋庢牸鐨勮緭鍏ュ眰 -- 涓嶅儚鐜颁唬寮曟搸閭f牱鏈夋洿瀹屾暣鐨勮緭鍏ユ槧灏勭郴缁? -浣嗕綔涓烘暀瀛︿唬鐮侊紝瀹冮潪甯哥洿鎺ャ€? -## 17. `EngineTuning`銆乣ImGuiManager`銆乣EngineProfiling`锛氳繍琛屾椂宸ュ叿閾? -### 17.1 `EngineTuning`锛氬叏灞€鍙橀噺鏍? -MiniEngine 寰堝鍙傛暟閮戒笉鏄?hardcode锛岃€屾槸鍐欐垚鍏ㄥ眬鍙橀噺锛? - `BoolVar` - `NumVar` - `ExpVar` - `IntVar` - `EnumVar` -杩欎簺鍙橀噺鐢ㄨ矾寰勬敞鍐岋紝渚嬪锛? +比如: + - `"Graphics/HDR/Enable"` - `"Graphics/Bloom/Strength"` - `"Camera/CameraSpeed"` -杩欎細鑷姩褰㈡垚涓€妫佃皟鍙傛爲銆? -杩欏璁捐寰堝€煎緱瀛︿範锛屽洜涓哄畠闈炲父杞婚噺锛? -- 涓嶉渶瑕佸鏉傜紪杈戝櫒 -- 涓嶉渶瑕侀澶栧簭鍒楀寲妗嗘灦 -- 鍙鍏ㄥ眬鍙橀噺瀛樺湪锛屽氨鑳藉嚭鐜板湪璋冭瘯 UI 閲? -### 17.2 鐜板湪鐨勮皟鍙?UI 宸茬粡鍋忓悜 ImGui +这样它们可以自动组织成一棵调参树,直接出现在调试界面中。 -鍘熷 MiniEngine 鏈夎緝澶?text overlay 椋庢牸宸ュ叿锛屼絾杩欎釜鐗堟湰閲屾槑鏄炬洿鍋忓悜 ImGui锛? -- `GameCore` 姣忓抚浼?`ImGuiManager::NewFrame()` -- `EngineTuning::Display()` 鍐呴儴鐩存帴鏋勫缓 ImGui 绐楀彛 -- `EngineProfiling::RenderImGui()` 涔熻蛋 ImGui +这是一个非常实用、也非常值得借鉴的设计。 -涔熷氨鏄锛岃繖涓増鏈凡缁忔妸鑰佸紡璋冭瘯鐣岄潰鍚?ImGui 杩佺Щ浜嗐€? -### 17.3 Profiling 涓嶅彧鏄?CPU 璁℃椂 +### 18.2 `ImGuiManager` -`ScopedTimer` 鏃㈠彲浠ュ寘 CPU 鑼冨洿锛屼篃鑳藉寘 GPU 鑼冨洿锛? -- `EngineProfiling::BeginBlock(name)` -- `EngineProfiling::BeginBlock(name, Context)` +这个模块负责: -褰撲紶浜?`CommandContext` 鏃讹紝瀹冧細锛? -- 鍐?PIX marker -- 鍐?GPU timestamp +- 初始化 ImGui +- 创建 ImGui 专用的 SRV Heap +- 每帧 `NewFrame` +- 把 ImGui DrawData 提交到当前图形上下文 -`GpuTimeManager` 鍒欒礋璐o細 +也就是说,这个版本的 Core 已经把 ImGui 当成正式的运行时工具基础设施,而不是临时外挂。 -- 寤?query heap -- 寤?readback buffer -- 姣忓抚 resolve query data -- 鎶?GPU ticks 鍙樻垚姣 +### 18.3 `EngineProfiling` -鎵€浠ヨ繖濂?profiler 鏄湡姝g殑 CPU + GPU 鍙岃建璁℃椂锛岃€屼笉鏄崟绾祴 CPU銆? -## 18. 鍒嗛厤鍣細MiniEngine 鎬庢牱绠$悊鈥滀复鏃?GPU 鍐呭瓨鈥? -### 18.1 `LinearAllocator`锛氭瘡甯т复鏃跺唴瀛樼殑涓诲姏 +`EngineProfiling` 会记录: -瀹冩湇鍔′簬锛? -- 鍔ㄦ€佸父閲忕紦鍐?- 鍔ㄦ€?SRV 鏁版嵁 -- 涓存椂涓婁紶鏁版嵁 -- 涓€浜涘彧鍦ㄥ綋鍓?command context 鐢熷懡鍛ㄦ湡鍐呮湁鏁堢殑鏁版嵁 +- CPU 时间 +- GPU 时间 +- PIX Marker -瀹冪殑妯″紡鏄細 +`ScopedTimer` 在 Debug/Profile 版本下会自动插入 profiling scope。 -- 鍚戝叏灞€ page manager 鐢宠 page -- 褰撳墠 context 鍦?page 涓婄嚎鎬?bump pointer 鍒嗛厤 -- `Finish()` 鍚庢牴鎹?fence 鍥炴敹椤甸潰 +而 `GpuTimeManager` 则负责: -杩欐槸涓€绉嶅緢缁忓吀銆佸緢楂樻晥鐨勫仛娉曘€? -### 18.2 `BuddyAllocator`锛氬浐瀹氬ぇ鍧楅噷鐨勫瓙鍒嗛厤 +- 建 Query Heap +- 建 Readback Buffer +- Resolve GPU Timestamp +- 把 GPU Tick 转成毫秒 -`BuddyAllocator` 鐨勬敞閲婂啓寰楀緢娓呮锛屽畠鐢?Buddy Allocation 鍋氬瓙鍒嗛厤锛屾敮鎸佷袱绉嶇瓥鐣ワ細 +这意味着它不是简单的 CPU profiling,而是完整的 CPU/GPU 双轨分析。 -- `kPlacedResourceStrategy` -- `kManualSubAllocationStrategy` +## 19. 内存分配器 -瀹冨瓨鍦ㄧ殑浠峰€兼槸锛? -- 璁╀竴澶у潡鍐呭瓨琚媶鎴愬ぇ灏忎笉鍚岀殑鍧?- 灏介噺鎺у埗纰庣墖 -- 璁╁潡鍦ㄩ噴鏀炬椂鑳藉拰 buddy 鍚堝苟 +### 19.1 `LinearAllocator` -瀹冧笉鍍?`LinearAllocator` 閭d箞甯歌浜庢瘡甯ц矾寰勶紝鏇村儚鏌愪簺 buffer 姹犵殑搴曞眰宸ュ叿銆? -## 19. 绾圭悊鍜屾枃浠剁郴缁燂細`TextureManager` 涓?`FileUtility` +这是帧内临时内存的主力。 -### 19.1 `TextureManager` 涓嶆槸绠€鍗曠殑鈥淟oadTexture鈥? -瀹冨仛浜嗕袱浠跺緢瀹炵敤鐨勪簨锛? -1. 绾圭悊缂撳瓨 -2. 寮曠敤璁℃暟 +适合: -涔熷氨鏄锛? -- 鍚屼竴璺緞绾圭悊涓嶄細閲嶅鍔犺浇 -- 澶氬寮曠敤鍚屼竴绾圭悊鏃讹紝鍏变韩涓€涓?`ManagedTexture` -- 鏈€鍚庝竴涓?`TextureRef` 閿€姣佹椂锛岀汗鐞嗘墠浼氳绉婚櫎 +- 动态常量缓冲 +- 临时上传数据 +- 只在当前命令上下文生命周期内有效的数据 -杩欏 sample 宸ョ▼寰堥噸瑕侊紝鍥犱负鑳芥樉钁楀噺灏戦噸澶?IO 鍜岄噸澶嶆樉瀛樺崰鐢ㄣ€? -### 19.2 `FileUtility` 鏄緢杞婚噺鐨勬枃浠惰鍙栧皝瑁? -`FileUtility` 鍙仛涓€浠朵簨锛? -- 璇绘暣涓枃浠跺埌 `ByteArray` +它的工作方式是: -瀹冩敮鎸佸悓姝ュ拰寮傛涓ょ褰㈠紡锛屽凡缁忚冻澶熸敮鎾戞潗璐ㄣ€佺汗鐞嗙瓑璧勬簮鍔犺浇銆? -## 20. 鏁板灞傦細杩欎笉鏄€滈檮璧犵殑灏忓伐鍏封€濓紝鑰屾槸鏁翠釜 Core 鐨勮瑷€鍩虹 +- 按页分配 +- 当前页线性增长 +- 命令执行完成后按 Fence 回收 -`Math` 鏂囦欢澶瑰拰 `VectorMath.h` 杩欏浠g爜鏄暣涓?Core 鐨勫簳灞傝〃杈炬柟寮忥細 +这是图形引擎里非常经典也非常高效的做法。 -- `Vector3` -- `Vector4` -- `Matrix3` -- `Matrix4` -- `Quaternion` -- `AffineTransform` -- `Frustum` -- `BoundingSphere` +### 19.2 `BuddyAllocator` -濡傛灉浣犲彂鐜板緢澶氫唬鐮佸湪鍐欙細 +`BuddyAllocator` 更适合做大块内存的子分配。 -- `Cross` -- `Normalize` -- `Recip` -- `AffineTransform` -- `~GetRotation()` +它能把一大块空间拆成不同大小的块,并在释放时尝试与 buddy 合并。 -涓嶈鎶婂畠褰撴垚鈥滆娉曞鎬€濓紝瀹冨叾瀹炴槸鍦ㄦ瀯閫犱竴濂楀亸 SIMD 椋庢牸鐨勬暟瀛?DSL銆? -瀵规柊浜烘潵璇达紝涓€涓緢鐜板疄鐨勫缓璁槸锛? -**璇?MiniEngine Core 鏃讹紝涓嶈鍏堢籂缁撴瘡涓暟瀛︽搷浣滅閲嶈浇鐨勫疄鐜帮紝鍏堟妸瀹冧滑褰撴垚鈥滃悜閲忔暟瀛﹁鍙モ€濈湅銆?* +它不像 `LinearAllocator` 那样偏向“每帧临时内存”,更像某些资源池的底层工具。 -## 21. 杩欏 Core 鏈夊摢浜涘緢鍊煎緱瀛︼紝鍝簺涓嶈鐩茬洰鐓ф妱 +## 20. 纹理与文件系统 -### 21.1 寰堝€煎緱瀛︾殑鐐? -- 涓诲惊鐜拰鍥惧舰鍒濆鍖栧垎宸ユ竻妤?- `CommandContext` 鎶?D3D12 甯歌绻佺悙鎿嶄綔灏佸緱姣旇緝椤烘墜 -- 璧勬簮鍛藉悕闈炲父鐩磋锛屼究浜?RenderDoc / PIX 璋冭瘯 -- `GraphicsCommon` 鎶婂叕鍏辩姸鎬侀泦涓寲 -- `EngineTuning` 杩欑鈥滆矾寰勫紡璋冨弬鍙橀噺鏍戔€濋潪甯稿疄鐢?- `EngineProfiling` 鎶?CPU銆丟PU銆丳IX marker 鎺ュ湪浜嗕竴璧?- `BufferManager` 铏界劧鍘熷锛屼絾闈炲父閫傚悎鏂版墜鐞嗚В涓€甯у埌搴曡鐢ㄥ灏戜腑闂磋祫婧? -### 21.2 涓嶈鐩茬洰鐓ф妱鐨勭偣 +### 20.1 `TextureManager` -- 鍏ㄥ眬鍙橀噺澶锛屾墿灞曞埌澶у瀷椤圭洰浼氭瘮杈冪棝鑻?- 璧勬簮鐢熷懡鍛ㄦ湡鏄汉宸ョ鐞嗭紝涓嶆槸 Render Graph -- 寰堝妯″潡寮轰緷璧栧叏灞€鐘舵€侊紝瑙h€︾▼搴︿竴鑸?- 淇濈暀浜嗕笉灏?Xbox/ESRAM 鏃朵唬閬楃暀缁撴瀯锛屽湪 PC 涓婂緢澶氬彧鏄吋瀹瑰澹?- 杈撳叆灞傚亸 sample 椋庢牸锛屼笉鏄幇浠e晢涓氬紩鎿庨偅绉嶅畬鏁?Action Mapping 绯荤粺 +`TextureManager` 不是单纯的“读文件”函数,它还做了: -鏇村噯纭湴璇达紝杩欏 Core 鐨勪环鍊间笉鍦ㄤ簬鈥滄嬁鏉ョ洿鎺ュ綋鐜颁唬寮曟搸鏋舵瀯妯℃澘鈥濓紝鑰屽湪浜庯細 +- 纹理缓存 +- 引用计数 -**瀹冩妸 D3D12 寮曟搸鍩虹璁炬柦鎷嗗緱瓒冲娓呮锛岄€傚悎瀛︿範鈥滃紩鎿庡簳灞傚埌搴曡澶勭悊鍝簺浜嬫儏鈥濄€?* +这意味着: -## 22. 缁欏垵瀛﹁€呯殑鎺ㄨ崘闃呰璺嚎 +- 同一路径不会重复加载 +- 多处引用共享一份底层纹理对象 +- 最后一个引用释放后才真正销毁 -濡傛灉浣犲噯澶囩湡姝f妸杩欎釜 `Core` 鐪嬫噦锛屾垜寤鸿鎸変笅闈㈤『搴忚婧愮爜锛? -### 绗?1 杞細鍙缓绔嬪叏灞€鎰? -1. `readme.md` -2. `Core/GameCore.h` -3. `Core/GameCore.cpp` -4. `Core/GraphicsCore.h` -5. `Core/GraphicsCore.cpp` -6. `Core/Display.cpp` +### 20.2 `FileUtility` -杩欎竴杞殑鐩爣鍙湁涓€涓細鎼炴竻妤氫竴甯ф€庝箞浠庡惎鍔ㄨ蛋鍒?Present銆? -### 绗?2 杞細琛?D3D12 鎶借薄灞? -1. `Core/CommandListManager.*` -2. `Core/CommandContext.*` -3. `Core/DescriptorHeap.*` -4. `Core/DynamicDescriptorHeap.*` -5. `Core/RootSignature.*` -6. `Core/PipelineState.*` +`FileUtility` 提供了简单直接的文件读取能力: -杩欎竴杞殑鐩爣锛氭悶娓呮 MiniEngine 鎬庝箞鎶?DX12 API 鍖呰捣鏉ャ€? -### 绗?3 杞細琛ヨ祫婧愮郴缁? -1. `Core/GpuResource.h` -2. `Core/PixelBuffer.*` -3. `Core/ColorBuffer.*` -4. `Core/DepthBuffer.*` -5. `Core/GpuBuffer.*` -6. `Core/Texture.*` -7. `Core/BufferManager.*` +- 同步读取整个文件 +- 异步读取整个文件 -杩欎竴杞殑鐩爣锛氭悶娓呮鈥滃紩鎿庨噷涓€甯у埌搴曟湁鍝簺璧勬簮鈥濄€? -### 绗?4 杞細琛ユ晥鏋滃拰宸ュ叿 +对于 sample 风格的引擎底层,这样的封装已经足够实用。 -1. `Core/PostEffects.*` -2. `Core/TemporalEffects.*` -3. `Core/FXAA.*` -4. `Core/SSAO.*` -5. `Core/XeGTAO.*` -6. `Core/MotionBlur.*` -7. `Core/DepthOfField.*` -8. `Core/IBL.*` -9. `Core/EngineTuning.*` -10. `Core/EngineProfiling.*` +## 21. Core 的优点和局限 -杩欎竴杞殑鐩爣锛氭悶娓呮 Core 涓嶅彧鏄€滄覆鏌撴鏋垛€濓紝瀹冭繕鏄竴濂楀畬鏁寸殑杩愯鏃跺伐鍏烽摼銆? -## 23. 杩樻湁涓夊潡鍊煎緱鐗瑰埆鐣欐剰鐨勫唴瀹? -### 23.1 `ParticleEffectManager`锛欳ore 閲屽叾瀹炶繕濉炰簡涓€濂?GPU 绮掑瓙绯荤粺 +### 21.1 很值得学习的地方 -寰堝浜虹涓€娆$湅 `Core` 鏃讹紝浼氫互涓鸿繖閲屽彧鏈夆€滃紩鎿庡熀纭€璁炬柦鈥濄€傚叾瀹炰笉鏄€? -`ParticleEffectManager` 璇存槑杩欏 Core 宸茬粡鍐呭缓浜嗙浉褰撳畬鏁寸殑 GPU 绮掑瓙娴佺▼锛屽寘鎷細 +- 主循环和图形初始化分工清楚 +- D3D12 抽象层比较完整 +- 资源命名清晰,调试友好 +- 把很多通用特效都接进统一框架 +- 调参和性能分析系统整合得很好 +- `BufferManager` 很适合用来理解一帧资源布局 -- 绮掑瓙瀹炰緥鍖?- 绮掑瓙妯℃嫙鏇存柊 -- 鍩轰簬鐩告満鐨勬覆鏌?- 鍙€夌殑 tiled rendering +### 21.2 不要盲目照抄的地方 -瀹冧箣鎵€浠ユ斁鍦?`Core`锛屾槸鍥犱负瀹冨己渚濊禆锛? -- `CommandContext` -- 鍚勭被 `ColorBuffer` / `DepthBuffer` -- 鍏变韩 Root Signature / PSO -- GPU 鎺掑簭鍜岄棿鎺ョ粯鍒惰兘鍔? -鎹㈠彞璇濊锛岃繖涓€灞傚凡缁忎笉鏄€滄渶浣庨檺搴︾殑妗嗘灦鈥濓紝鑰屾槸鈥滃缓绔嬪湪搴曞眰妗嗘灦涔嬩笂鐨勯珮绾у彲澶嶇敤鍔熻兘鈥濄€? -### 23.2 `BitonicSort`锛氳繖閲岃繛 GPU 鎺掑簭閮界粰浣犲噯澶囧ソ浜? -`BitonicSort.h` 鐨勬敞閲婃湰韬氨鏄竴浠藉緢濂界殑瀛︿範璧勬枡銆傚畠鏄庣‘璇存槑锛? -- Bitonic Sort 寰堥€傚悎 GPU -- 鏃堕棿澶嶆潅搴﹁櫧鐒朵笉鏄渶浼橈紝浣嗗苟琛屾€у緢寮?- 閫傚悎绮掑瓙鎺掑簭杩欑鈥淕PU 鑷繁浜х敓宸ヤ綔锛屽啀鍦?GPU 涓婄户缁鐞嗏€濈殑鍦烘櫙 +- 全局变量较多 +- 资源生命周期管理主要靠人工维护,不是 Render Graph +- 模块之间耦合度不低 +- 有一些历史遗留设计更偏 sample,不一定适合现代大型商业项目 -杩欎竴鐐瑰緢鍊煎緱鏂版墜娉ㄦ剰锛? -**鐜颁唬娓叉煋寮曟搸閲岀殑寰堝鈥滄覆鏌撳墠鍑嗗宸ヤ綔鈥濅篃浼氭斁鍒?GPU 涓婂仛锛屼笉鍙槸鏈€缁堢潃鑹层€?* +所以它最适合的定位不是“直接拿来当现代引擎模板”,而是: -### 23.3 `CommandSignature`锛歁iniEngine 杩為棿鎺ュ懡浠や篃涓€璧峰寘浜? -`CommandSignature` 瀵瑰簲鐨勬槸 D3D12 鐨勯棿鎺ユ墽琛岃兘鍔涳紝涔熷氨鏄細 +**学习一个 D3D12 引擎底层应该有哪些组成部分,以及它们如何协作。** -- `ExecuteIndirect` -- `DrawIndirect` -- `DispatchIndirect` -- `DispatchMesh` 瀵瑰簲鐨勯棿鎺ュ弬鏁版弿杩? -杩欐剰鍛崇潃 `Core` 鐨勮璁$洰鏍囧苟涓嶅彧鏄€滆兘鐢诲嚭鏉モ€濓紝鑰屾槸宸茬粡鑰冭檻浜嗭細 +## 22. 给初学者的学习建议 -- GPU 鑷繁鐢熸垚鍛戒护鍙傛暟 -- GPU 椹卞姩鐨勭粯鍒?/ 鍒嗗彂 -- 鏇撮珮绾х殑鍙墿灞曟覆鏌撹矾寰? -濡傛灉浣犳湭鏉ヨ鐮旂┒锛? -- GPU Culling -- Cluster / Meshlet 娴佹按绾?- GPU-Driven Rendering +如果你接下来真的要深入读这套 Core,我建议你带着下面几个问题去看源码: -閭h繖閮ㄥ垎浠g爜涔熷緢鍊煎緱鍗曠嫭鍐嶇湅涓€閬嶃€? -## 24. 鏈€鍚庣敤涓€鍙ヨ瘽鎬荤粨 `Core` +1. 一帧的入口在哪里,出口在哪里 +2. 场景颜色、深度、UI、后处理缓冲分别是谁创建的 +3. 资源状态是谁负责维护的 +4. 描述符什么时候是 CPU 可见,什么时候变成 GPU 可见 +5. 后处理为什么大量使用 Compute +6. TAA、Motion Blur 为什么必须依赖上一帧数据 +7. 为什么引擎要有一整套调参与分析工具,而不是只把画面画出来 -濡傛灉浣犳槸鍥惧舰瀛﹀垵瀛﹁€咃紝鍙互鎶?`MiniEngine/Core` 鐞嗚В鎴愶細 +当你能用这几个问题把 `Core` 串起来时,你就不是“看过代码”,而是真正开始理解它了。 -**涓€濂楁妸 DirectX 12 瑁?API 鍖呰鎴愨€滃彲浠ュ啓寮曟搸鍔熻兘鈥濈殑涓棿灞傘€?* +## 23. 一句话总结 -瀹冩渶閲嶈鐨勪环鍊间笉鏄煇涓€涓?SSAO銆乀AA 鎴?Bloom 绠楁硶锛岃€屾槸瀹冩妸杩欎簺绠楁硶鏀捐繘浜嗕竴鏉″畬鏁寸殑宸ョ▼娴佹按绾块噷锛? -- 鏈夊惎鍔?- 鏈夎澶?- 鏈夊懡浠ゆ彁浜?- 鏈夎祫婧愭娊璞?- 鏈夊悗澶勭悊 -- 鏈夋樉绀鸿緭鍑?- 鏈夎皟鍙?- 鏈夋€ц兘鍒嗘瀽 +如果要用一句最简洁的话结束这份文档: -杩欐鏄€滃浘褰?sample鈥濆拰鈥滃紩鎿庡簳灞備唬鐮佲€濅箣闂存渶澶х殑鍖哄埆銆? -濡傛灉浣犳妸杩欎唤鏂囨。璇诲畬锛屽啀鍥炲幓鐪?`Core` 婧愮爜锛屼綘搴旇浼氬紑濮嬫妸瀹冪湅鎴愪笁灞傦細 +**MiniEngine 的 `Core` 不是某个单独渲染算法,而是一套把 DirectX 12 裸 API 组织成“可写引擎、可做调试、可扩展功能模块”的公共底层。** -1. **骞冲彴鍜屽抚寰幆灞?*锛歚GameCore`銆乣GraphicsCore`銆乣Display` -2. **D3D12 鎶借薄灞?*锛歚CommandContext`銆乣DescriptorHeap`銆乣RootSignature`銆乣PSO`銆乣GpuResource` -3. **鍔熻兘妯″潡灞?*锛氬悗澶勭悊銆佺浉鏈恒€佽緭鍏ャ€佽皟璇曘€佹€ц兘銆佺矑瀛愩€両BL - -褰撲綘鑳界敤杩欎笁灞傚幓瑙i噴浠绘剰涓€涓?`Core` 鏂囦欢鏃讹紝浣犲氨宸茬粡涓嶅啀鏄€滃彧鏄湅杩囦唬鐮佲€濓紝鑰屾槸鐪熸寮€濮嬬悊瑙h繖濂?MiniEngine 浜嗐€? +这也是它最值得学习的地方。