戰(zhàn)指南)
最近在幫一個(gè)跨平臺(tái)渲染項(xiàng)目搭底層的著色器管線正好把 bgfx 這一套 shader 編譯流程完整走了一遍。說實(shí)話第一次接觸 bgfx 的時(shí)候最讓人抓狂的不是引擎 API而是它的 shader 編譯機(jī)制你要在 Windows 下寫 HLSL跑 shaderc 工具得到一堆 .bin 文件然后才能交給 bgfx 加載使用。中間任何一個(gè)環(huán)節(jié)出錯(cuò)排查起來都非常痛苦。這篇就以“Windows 下 HLSL 為例”把 bgfx shader 編譯這條鏈路從工具鏈搭建到最終產(chǎn)出 .bin 全流程講清楚。內(nèi)容包括shaderc 的編譯方式、一張最小可跑的 HLSL shader、每條編譯參數(shù)的含義、編譯產(chǎn)物的結(jié)構(gòu)以及我踩過的各類坑和排查思路。適合正在用 bgfx 做渲染、但又不想在 shader 工具鏈上浪費(fèi)太多時(shí)間的開發(fā)者直接參考。1. 先把 bgfx 的著色器體系看懂1.1 為什么 bgfx 需要自建 shader 編譯鏈路bgfx 的核心設(shè)計(jì)目標(biāo)是跨平臺(tái)渲染后端覆蓋 Direct3D、OpenGL、Vulkan、Metal 等一大堆圖形 API。如果每個(gè)渲染后端都要單獨(dú)維護(hù)一套 shader 源碼項(xiàng)目規(guī)模會(huì)迅速失控而且很難保證同一份視覺效果的輸出完全一致。所以 bgfx 參考了傳統(tǒng)游戲引擎的做法定義了一套自己的著色器語言再通過專門的工具編譯成不同后端能識(shí)別的字節(jié)碼。這套語言本身的語法看上去更像 GLSL但官方也保留了 HLSL 風(fēng)格的寫入口。也就是說你在 Windows 下完全可以寫 .hlsl 后綴的源文件shaderc 工具會(huì)在編譯階段把它轉(zhuǎn)換成交給 D3D 后端真正使用的 DXBC 字節(jié)碼。對(duì)使用方的好處很明顯游戲邏輯和渲染資源完全解耦不同后端不需要維護(hù)多份 shader只需要在發(fā)布時(shí)用 shaderc 按目標(biāo)平臺(tái)重新生成一份 .bin 即可。關(guān)鍵是這個(gè)編譯動(dòng)作并不在引擎運(yùn)行時(shí)發(fā)生而是在構(gòu)建期完成。bgfx 只負(fù)責(zé)加載編譯好的二進(jìn)制字節(jié)碼因此運(yùn)行時(shí)的開銷很小也不依賴外部 shader 編譯器。這種“構(gòu)建期編譯 運(yùn)行時(shí)加載”的模式對(duì)游戲這種對(duì)加載速度、內(nèi)存占用、可控性要求都很高的場(chǎng)景非常友好。1.2 bgfx_shader.sh 與 varying.def.sc 扮演什么角色在真正寫 shader 之前建議先理解 bgfx 著色器體系里兩個(gè)必見的文件bgfx_shader.sh和varying.def.sc。bgfx_shader.sh是官方提供的一個(gè)公共頭文件里面用宏定義了 bgfx 著色器語言需要的各種類型和語義比如vec3、mat4、vec4以及紋理采樣器、uniform 聲明方式、頂點(diǎn)和片元輸出的約定變量名。你可以把它理解成一套“方言頭文件”只要源文件里 include 它寫出來的代碼就是 bgfx 能識(shí)別的著色器語言而不是純 HLSL 或純 GLSL。varying.def.sc則是用來描述著色器頂點(diǎn)輸入和片元輸入輸出結(jié)構(gòu)的配置文件。它不參與代碼邏輯而是給 shaderc 編譯器和渲染管線的頂點(diǎn)聲明vertex decl做對(duì)接用的。shaderc 會(huì)讀取這個(gè)文件知道頂點(diǎn)著色器輸出了哪些 varying、片元著色器輸入了哪些 varying然后生成對(duì)應(yīng)的字節(jié)碼資源綁定信息。渲染端則需要用同一份聲明創(chuàng)建頂點(diǎn)布局兩者必須完全對(duì)齊否則渲染結(jié)果會(huì)出現(xiàn)頂點(diǎn)數(shù)據(jù)錯(cuò)位、花屏等非常難排查的問題。所以寫 bgfx shader 的第一步不是直接開寫而是同時(shí)規(guī)劃好varying.def.sc和源文件的 include 關(guān)系。很多新人第一次編譯失敗就是因?yàn)橥俗?shaderc 讀取 varying 定義或者 varying 里寫錯(cuò)了成員順序。2. Windows 下編譯 shaderc 工具兩個(gè)方案任選2.1 方案一直接用官方倉庫里的預(yù)編譯版本如果只是想在 Windows 上快速用起來最簡(jiǎn)單的做法是直接使用 bgfx 倉庫里自帶的 shaderc 可執(zhí)行文件。官方倉庫的tools/bin/windows目錄下通常直接放著編譯好的shaderc.exe省去了自己編譯工具鏈的麻煩。但這里有一個(gè)非常重要的前提工具的版本必須和引擎代碼的版本保持一致。bgfx 的 .bin 格式并沒有做嚴(yán)格的向后兼容舊版本工具編譯出來的字節(jié)碼新版引擎不一定能正確加載反過來也一樣。尤其當(dāng)你是通過 git submodule 拉取 bgfx 代碼時(shí)一定要用 submodule 對(duì)應(yīng)提交里自帶的那份 shaderc或者重新編譯一份出來不要隨手從網(wǎng)上某個(gè)舊包里拿一個(gè) exe 就開始用。預(yù)編譯版本的好處是開箱即用缺點(diǎn)是如果官方倉庫沒更新或者你想給 shaderc 自己加一些定制邏輯比如自定義 include 搜索路徑、特殊宏那就需要源碼編譯。2.2 方案二用 CMake 從源碼編譯 shaderc源碼編譯在 Windows 下并不復(fù)雜我一般用 CMake Visual Studio 的生成器git clone --recursive bgfx倉庫地址 cd bgfx cmake -S . -B build -G Visual Studio 17 2022 -A x64 cmake --build build --config Release --target shaderc編譯完成后shaderc.exe會(huì)生成在build/Release或者類似目錄下。注意 bgfx 依賴 bx 和 bimg 兩個(gè)子模塊如果你的 clone 沒有帶--recursive會(huì)直接編譯失敗錯(cuò)誤信息通常是一堆“找不到 bx/bx.h”之類的 include 錯(cuò)誤。這種問題在第一次編譯時(shí)特別常見先檢查 submodule 是否完整往往比改代碼更快。如果選擇不開 CMake也可以直接用倉庫里的scripts目錄生成 Visual Studio 工程再在 IDE 里選中 shaderc 項(xiàng)目生成。兩種方式本質(zhì)一樣只是操作習(xí)慣不同。我個(gè)人的建議是優(yōu)先 CMake方便后續(xù)把 shaderc 集成進(jìn)自己的構(gòu)建管線。2.3 編譯期間要注意的依賴項(xiàng)在 Windows 平臺(tái)下shaderc 編譯 HLSL 源文件時(shí)最終需要借助 DirectX 的 D3DCompile 函數(shù)來產(chǎn)出 DXBC 字節(jié)碼。因此編譯 shaderc 時(shí)需要確保系統(tǒng)里安裝了 Windows SDK并且 CMake 能正確找到 d3dcompiler 相關(guān)庫。如果你遇到類似undefined reference to D3DCompile或cannot open d3dcompiler.lib的報(bào)錯(cuò)基本就是 Windows SDK 沒裝完整或者 CMake 沒有正確識(shí)別 SDK 路徑。另外你最后把 shaderc.exe 拷貝出來單獨(dú)使用的時(shí)候最好把它和d3dcompiler_47.dll放在同一個(gè)目錄或者保證 DLL 在系統(tǒng) PATH 里。否則 shaderc 編譯 HLSL 時(shí)會(huì)報(bào)“unable to create D3D compiler”一類的運(yùn)行時(shí)錯(cuò)誤。這個(gè)問題很容易被忽略因?yàn)榫幾g shaderc 本身是成功的一運(yùn)行就報(bào)錯(cuò)。驗(yàn)證工具是否可用很簡(jiǎn)單在命令行執(zhí)行shaderc.exe --help這時(shí)候應(yīng)該會(huì)輸出完整參數(shù)列表。如果這一步正常說明工具鏈已經(jīng)通了可以進(jìn)入寫 shader 的階段。3. 準(zhǔn)備一個(gè)最小可運(yùn)行的 HLSL 著色器并編譯出 .bin3.1 文件結(jié)構(gòu)與環(huán)境約定我在這個(gè)項(xiàng)目里的實(shí)際目錄結(jié)構(gòu)如下shaders/ ├── varying.def.sc ├── vs_simple.hlsl ├── fs_simple.hlsl └── out/ ├── vs_simple.bin └── fs_simple.binvarying.def.sc的內(nèi)容是最基礎(chǔ)的兩段式定義vec4 v_position : POSITION; vec2 v_texcoord0 : TEXCOORD0;第一行表示頂點(diǎn)著色器需要輸出一個(gè)位置變量語義綁定到POSITION第二行表示傳遞一個(gè)二維紋理坐標(biāo)到片元著色器語義是TEXCOORD0。bgfx 在編譯頂點(diǎn)著色器時(shí)會(huì)把這個(gè)文件里定義的內(nèi)容映射到頂點(diǎn)布局的對(duì)應(yīng) attribute 位置因此后面在 C 端創(chuàng)建bgfx::vertexLayout時(shí)成員的順序和關(guān)鍵字必須和這個(gè)文件對(duì)得上。3.2 編寫頂點(diǎn)著色器和片元著色器頂點(diǎn)著色器vs_simple.hlsl#include bgfx_shader.sh void main() { vec4 modelPos vec4(0.0, 0.0, 0.0, 1.0); vec4 worldPos mul(u_model[0], modelPos); vec4 viewPos mul(u_view, worldPos); vec4 clipPos mul(u_proj, viewPos); gl_Position clipPos; v_texcoord0 vec2(0.0, 0.0); }這里mul是 bgfx 著色器語言里規(guī)定的矩陣乘法函數(shù)u_model[0]、u_view、u_proj是 bgfx 預(yù)置的 uniform 常量。gl_Position是頂點(diǎn)著色器輸出位置的固定名稱即使在 HLSL 源碼中也是這么寫bgfx 會(huì)在編譯階段把它轉(zhuǎn)換成 D3D 后端需要的SV_POSITION語義。片元著色器fs_simple.hlsl#include bgfx_shader.sh void main() { gl_FragColor vec4(1.0, 0.0, 0.0, 1.0); }這個(gè)片元著色器非常簡(jiǎn)單就是把所有像素輸出成純紅色。gl_FragColor同樣是 bgfx 約定俗成的輸出變量名shaderc 會(huì)幫你完成到目標(biāo)平臺(tái)的語義轉(zhuǎn)換。需要注意雖然文件后綴是.hlsl但這里用的語法是 bgfx 著色器語言而不是嚴(yán)格的 HLSL 語法。bgfx 的解析器是根據(jù) include 的bgfx_shader.sh和變量約定來識(shí)別代碼風(fēng)格后綴更多是為了編輯器高亮?xí)r能選對(duì)語言模式。如果你確實(shí)想寫純 HLSL 風(fēng)格比如直接用POSITION0之類的語義也不是不行但跨平臺(tái)移植時(shí)會(huì)很頭疼所以我始終推薦用 bgfx 自己的這套寫法。3.3 shaderc 編譯參數(shù)詳解與一次實(shí)測(cè)進(jìn)入shaders目錄執(zhí)行以下兩條命令shaderc.exe -f vs_simple.hlsl -o out/vs_simple.bin --type vertex --platform windows --profile vs_5_0 --varyingdef varying.def.sc -i . shaderc.exe -f fs_simple.hlsl -o out/fs_simple.bin --type fragment --platform windows --profile ps_5_0 --varyingdef varying.def.sc -i .下面這張表把常用的參數(shù)挨個(gè)說明參數(shù)作用示例-f指定輸入著色器源文件-f vs_simple.hlsl-o指定輸出 .bin 文件路徑-o out/vs_simple.bin--type著色器類型vertex、fragment、compute--type vertex--platform目標(biāo)平臺(tái)windows / android / linux / ios 等--platform windows--profile目標(biāo)著色器 profile對(duì)應(yīng) D3D 的 shader model--profile vs_5_0--varyingdef指定 varying 定義文件--varyingdef varying.def.sc-i添加 include 搜索路徑可多次使用-i .--verbose輸出詳細(xì)編譯日志排查問題時(shí)很常用--verbose--define在編譯時(shí)注入宏定義--define DEBUG_SHADER1實(shí)際操作里我最常踩的坑有這些一是--platform windows配合--profile vs_5_0的寫法如果--profile寫了ps_5_0而--type是 vertexshaderc 會(huì)報(bào)錯(cuò)說 profile 與著色器類型不匹配二是--varyingdef路徑必須正確如果 shaderc 找不到這個(gè)文件它會(huì)直接阻止編譯提示缺少 varying 定義信息三是 include 搜索路徑如果你的 shader 里 include 了其他公共頭文件-i必須覆蓋到對(duì)應(yīng)目錄否則會(huì)報(bào)無法打開頭文件。3.4 .bin 文件里到底是什么東西編譯完成后可以找一個(gè)十六進(jìn)制編輯器打開out/vs_simple.bin看一眼。文件開頭會(huì)有幾個(gè)固定字段緊跟著的一串字節(jié)碼就是 DXBC 格式的數(shù)據(jù)塊。DXBC 是 Direct3D 的二進(jìn)制著色器格式wrapped 在 bgfx 的 .bin 容器里文件內(nèi)部還會(huì)記錄這個(gè) shader 編譯時(shí)的 profile、字節(jié)碼長(zhǎng)度、varying 數(shù)量等信息。bgfx 引擎加載 .bin 后會(huì)根據(jù)自己記錄的格式信息把這些字節(jié)碼直接交給 D3D 驅(qū)動(dòng)使用。理解這層結(jié)構(gòu)對(duì)排查著色器加載問題有幫助如果引擎跑起來顯示 shader 句柄無效除了代碼 API 寫錯(cuò)之外很有可能是 .bin 是舊版 shaderc 編譯的文件內(nèi)部格式字段跟當(dāng)前引擎版本不一致。3.5 在 C 程序里加載編譯產(chǎn)物bgfx 沒有提供一個(gè)叫l(wèi)oadShader的獨(dú)立 API加載 shader 的正確姿勢(shì)是自己讀取 .bin 文件的數(shù)據(jù)然后用bgfx::createShader創(chuàng)建 ShaderHandle。下面是一段簡(jiǎn)單封裝bgfx::ShaderHandle loadShader(const char* filePath) { std::ifstream file(filePath, std::ios::binary | std::ios::ate); std::streamsize size file.tellg(); file.seekg(0, std::ios::beg); std::vectoruint8_t data(size); file.read(reinterpret_castchar*(data.data()), size); const bgfx::Memory* mem bgfx::copy(data.data(), static_castuint32_t(size)); return bgfx::createShader(mem); }創(chuàng)建完成后再通過bgfx::createProgram(vertexShader, fragmentShader, true)創(chuàng)建一個(gè) program 句柄之后就可以在 draw call 中使用了。要注意的是createProgram的第三個(gè)參數(shù)表示是否在程序創(chuàng)建后銷毀輸入 shader如果傳true那你就不再需要手動(dòng)調(diào)用bgfx::destroy去釋放 shader 句柄避免重復(fù)釋放造成崩潰。4. 核心原理拆解從 HLSL 源文件到 DXBC 的幾條路徑4.1 shaderc 的編譯管線是怎么工作的bgfx 的 shaderc 工具本質(zhì)是一個(gè)前端 多后端輸出器。前端負(fù)責(zé)解析著色器源碼生成內(nèi)部的中間表示后端則面向不同圖形 API 輸出對(duì)應(yīng)字節(jié)碼。當(dāng)輸入文件是 HLSL 語法、目標(biāo)平臺(tái)是 Windows 時(shí)前端解析完成后會(huì)調(diào)用 DirectX 提供的 D3DCompile 系列函數(shù)生成 DXBC然后封裝進(jìn) .bin 容器。但如果你把目標(biāo)平臺(tái)切到 Vulkan 或 OpenGLshaderc 則會(huì)走另一條輸出路徑生成 SPIR-V 字節(jié)碼或者 GLSL 源碼格式的字節(jié)碼。也就是說同一份 HLSL 源文件理論上你可以通過更換--platform和--profile生成多份不同后端的 .bin從而覆蓋整個(gè)跨平臺(tái)需求。這也是為什么說 shaderc 選型好的原因開發(fā)者只需要維護(hù)一套 shader 源碼發(fā)布時(shí)跑一遍不同參數(shù)的編譯命令就能得到配套各平臺(tái)的二進(jìn)制文件。如果項(xiàng)目里的平臺(tái)很多這個(gè)過程完全可以通過腳本自動(dòng)化。4.2 varying 定義與渲染管線的對(duì)應(yīng)關(guān)系varying.def.sc 之所以重要是因?yàn)樗苯記Q定了頂點(diǎn)著色器的輸出和片元著色器的輸入是否對(duì)齊也決定了 C 端頂點(diǎn)布局的 attribute 順序。bgfx 內(nèi)部有一套固定的 attribute 語義枚舉比如POSITION對(duì)應(yīng)bgfx::Attrib::PositionTEXCOORD0對(duì)應(yīng)bgfx::Attrib::TexCoord0。如果你在 varying.def.sc 中寫了vec4 v_position : POSITION那么在 C 端創(chuàng)建頂點(diǎn)布局時(shí)就應(yīng)該第一個(gè)聲明begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)之類。順序或者類型不一致的話GPU 會(huì)把錯(cuò)誤的字節(jié)流解釋成位置數(shù)據(jù)最典型的畫面表現(xiàn)是模型嚴(yán)重拉伸變形或者三角形亂飛。要特別注意的是bgfx 的頂點(diǎn)布局設(shè)計(jì)并不要求 attribute 順序必須和 varying.def.sc 完全一致因?yàn)?bgfx 內(nèi)部有自動(dòng)映射機(jī)制。但如果數(shù)值類型對(duì)不上比如位置上聲明了 4 分量頂點(diǎn)數(shù)據(jù)實(shí)際只有 3 分量就很容易踩坑。所以穩(wěn)妥做法是讓 varying.def.sc、C vertexLayout、頂點(diǎn)數(shù)據(jù)三者永遠(yuǎn)保持同步。4.3 以 HLSL 為例的 profile 選擇邏輯--profile參數(shù)在 Windows 平臺(tái)下直接對(duì)應(yīng) DirectX 的 shader model 等級(jí)。我們最常用的組合是vs_5_0和ps_5_0對(duì)應(yīng) D3D11 的默認(rèn)能力。如果你的項(xiàng)目需要考慮 D3D9 兼容可以寫成vs_3_0、ps_3_0但 shader 指令集和寄存器數(shù)量都會(huì)受限很多現(xiàn)代寫法編譯不過。從 bgfx 自身的角度來說如果你完全沒有老的平臺(tái)包袱建議直接上 vs_5_0。理由很簡(jiǎn)單首先 D3D11 現(xiàn)在幾乎是 Windows 上的默認(rèn)圖形層其次更高級(jí)的 profile 對(duì)應(yīng)更多可用的著色器指令和紋理采樣器避免在做稍微復(fù)雜一點(diǎn)的效果時(shí)被編譯器限制卡住。profile 參數(shù)不只影響能否編譯通過也影響著色器實(shí)際運(yùn)行的硬件兼容范圍選擇時(shí)要綜合考慮目標(biāo)設(shè)備。5. 實(shí)操中的常見問題與排查技巧實(shí)錄5.1 shaderc 找不到頭文件或者輸出目錄不存在錯(cuò)誤信息通常類似Error: unable to open input file vs_simple.hlsl常見原因有三個(gè)路徑寫錯(cuò)、include 路徑?jīng)]有用-i指定、-o指向的目錄不存在。尤其是最后一種很多人第一次編譯時(shí)直接把-o out/xxx.bin但 out 目錄還沒創(chuàng)建導(dǎo)致 shaderc 直接報(bào)錯(cuò)。解決辦法很簡(jiǎn)單先把輸出目錄創(chuàng)建好再編譯。也可以在腳本里用mkdir -p或者批處理里if not exist out mkdir out保證目錄存在。5.2 HLSL 編譯時(shí)報(bào) dependency 找不到Windows 下最容易出現(xiàn)的運(yùn)行時(shí)錯(cuò)誤是類似error: Failed to create D3D compiler. d3dcompiler_47.dll not found.這種情況多半是執(zhí)行 shaderc.exe 的機(jī)器上缺少 DirectX 運(yùn)行庫或者 DLL 搜索路徑?jīng)]覆蓋到 shaderc 所在目錄。修復(fù)方式是安裝對(duì)應(yīng)版本的 DirectX 運(yùn)行庫或者把d3dcompiler_47.dll放到 shaderc.exe 旁個(gè)目錄下。我自己習(xí)慣直接把 DLL 和 shaderc.exe 打包在一起這樣不管放到哪個(gè)環(huán)境都能跑比較省事。5.3 profile 和 type 不匹配導(dǎo)致編譯失敗報(bào)錯(cuò)信息很直觀error: type vertex and profile ps_5_0 mismatch這類錯(cuò)誤完全是自己粗心把頂點(diǎn)著色器輸出寫成了 ps profile或者反過來。排查時(shí)先看一眼命令行參數(shù)不要把--type和--profile混淆。另外即使 type 和 profile 匹配shader 模型版本過低也可能導(dǎo)致編譯失敗。比如在用了大量 64 位浮點(diǎn)運(yùn)算或者雙精度指令時(shí)vs_4_0 級(jí)別往往不支持需要提升到 vs_5_0。如果代碼邏輯里沒用到特殊指令但編譯還是失敗優(yōu)先提高 profile 等級(jí)再試。5.4 加載 .bin 后 bgfx::isValid 返回 false這種問題在運(yùn)行時(shí)出現(xiàn)shader 文件本身可能已經(jīng)編譯成功但引擎加載時(shí)卻判定為無效。最常見原因是 shaderc 工具版本和 bgfx 引擎版本不一致導(dǎo)致 .bin 文件的內(nèi)部版本字段不能被當(dāng)前引擎識(shí)別。判斷方法很簡(jiǎn)單使用與引擎代碼同版本的 shaderc 重新編譯 shader再加載一次。如果恢復(fù)正常說明就是版本錯(cuò)位。另外如果你在從外部資料或示例項(xiàng)目里拷貝 .bin尤其要留意這一點(diǎn)因?yàn)閯e人項(xiàng)目的 bin 文件幾乎不可能和你本地的引擎版本剛好匹配。5.5 頂點(diǎn)數(shù)據(jù)錯(cuò)亂、模型撕裂如果 .bin 編譯和加載都沒問題但渲染出來的模型明顯錯(cuò)亂優(yōu)先懷疑 varying 定義和頂點(diǎn)布局不匹配。我遇到過一種很典型的情況模型數(shù)據(jù)里有位置、法線、UV 3 個(gè)屬性但varying.def.sc里把 UV 寫到了第二個(gè) attributeC 端 vertexLayout 卻按位置、UV、法線的順序聲明結(jié)果著色器拿到的 v_texcoord0 實(shí)際是法線數(shù)據(jù)。排查方式是先用最簡(jiǎn)單的純色 shader 測(cè)試頂點(diǎn)位置是否正確再逐步添加 varying 項(xiàng)。只要把一組屬性分開測(cè)試就能很快定位是哪個(gè) attribute 順序出了問題。渲染調(diào)試器如 RenderDoc也能直接查看頂點(diǎn)緩沖和 shader 輸入比靠眼睛猜高效得多。5.6 一鍵編譯腳本避免每改一次敲一遍命令實(shí)操時(shí)我不會(huì)每次都手動(dòng)敲 shaderc 命令而是寫了一個(gè)簡(jiǎn)單的批處理腳本放在 shaders 目錄下echo off set SHADERC..\tools\shaderc.exe set COMMON_INC..\src if not exist out mkdir out %SHADERC% -f vs_simple.hlsl -o out/vs_simple.bin ^ --type vertex --platform windows --profile vs_5_0 ^ --varyingdef varying.def.sc -i %COMMON_INC% -i . || goto :error %SHADERC% -f fs_simple.hlsl -o out/fs_simple.bin ^ --type fragment --platform windows --profile ps_5_0 ^ --varyingdef varying.def.sc -i %COMMON_INC% -i . || goto :error echo [OK] all shaders compiled. exit /b 0 :error echo [FAILED] shader compile failed. exit /b 1這段腳本里把 include 路徑參數(shù)-i .和公共頭目錄-i ..\src都帶上避免因?yàn)檎也坏筋^文件而失敗。日常修改 shader 后直接雙擊腳本一份新 .bin 就生成好了。如果項(xiàng)目比較大還可以把 shaderc 集成進(jìn) CMake 的 post-build 事件在構(gòu)建引擎時(shí)自動(dòng)重編譯所有 shader進(jìn)一步縮短迭代周期。5.7 常見問題速查表現(xiàn)象可能原因排查要點(diǎn)shaderc 打開文件失敗路徑錯(cuò)誤 / include 目錄缺失檢查-f、-i路徑找不到 D3DCompile缺少 d3dcompiler 運(yùn)行庫放置 d3dcompiler_47.dllprofile 與 type 不匹配參數(shù)寫錯(cuò)重新核對(duì)--type和--profile加載 .bin 后句柄無效工具版本與引擎不匹配同版本重新編譯工具模型渲染扭曲varying / vertexLayout 順序不一致對(duì)比 varying.def.sc、布局和頂點(diǎn)數(shù)據(jù)紋理采樣不出來uniform 類型或命名不匹配檢查統(tǒng)一命名和紋理單元綁定6. 一些來自實(shí)戰(zhàn)的體會(huì)用過幾輪之后我對(duì) bgfx 的 shader 編譯流程有了幾個(gè)很深的感受。第一版本一致性永遠(yuǎn)是最大的坑。如果你從舊引擎里拿了一個(gè) shaderc.exe再去編譯新引擎的 shader就算它能正常編譯出 .bin加載時(shí)也可能靜默失敗。最穩(wěn)妥的辦法是把 bgfx、bx、bimg 三個(gè)倉庫放在同一個(gè)提交點(diǎn)然后用這個(gè)提交點(diǎn)對(duì)應(yīng)的工具。第二盡量把 shader 源碼、varying.def.sc、C 頂點(diǎn)布局三者放在同一個(gè)碼農(nóng)能同時(shí)看到的地方。它們之間的關(guān)聯(lián)性非常強(qiáng)如果散落在不同目錄某次改動(dòng)忘記同步其中一處排查起來會(huì)非常耗時(shí)。我一般會(huì)把它們放在同一個(gè)資源目錄并在代碼注釋里寫好對(duì)應(yīng)關(guān)系。第三Windows 下聯(lián)調(diào)時(shí)建議配合圖形調(diào)試器使用。RenderDoc 可以直接看到最終傳給驅(qū)動(dòng)的字節(jié)碼也能看到每個(gè) draw call 使用的 shader 資源。當(dāng)你懷疑自己的 shader 編譯有問題時(shí)先看調(diào)試器里是否正常加載再回頭看編譯腳本往往能快速定位是編譯環(huán)節(jié)還是運(yùn)行時(shí)環(huán)節(jié)出了問題。最后再說一個(gè)小心得如果你只是做 Windows 平臺(tái)的原型驗(yàn)證也可以直接在 bgfx 示例工程里跑起來然后用官方 example 自帶的 shader 文件替換成自己的輸出驗(yàn)證流程能通了再擴(kuò)展跨平臺(tái)。這樣能最大程度減少“工具鏈配置問題”和“引擎 API 使用問題”混在一起的可能性。這篇文章寫到的內(nèi)容基本覆蓋了我從零開始接觸 bgfx shader 編譯到現(xiàn)在積累下來的絕大部分經(jīng)驗(yàn)。照著這個(gè)流程走一遍Windows 下的 HLSL 編譯鏈路應(yīng)該能順利跑通剩下的就是各種平臺(tái)之間的參數(shù)切換問題了。