DirectX11--HLSL編譯着色器的三種方法

前言

本教程不考慮Effects11(FX11),而是基於原始的HLSL。html

目前編譯與加載着色器的方法以下:windows

  1. 使用Visual Studio中的HLSL編譯器,隨項目編譯期間一同編譯,並生成.cso(Compiled Shader Object)對象文件,在運行期間加載該文件以讀取字節碼。
  2. 使用Visual Studio中的HLSL編譯器,隨項目編譯期間一同編譯,並生成.inc.h的頭文件,着色器字節碼在編譯期間就能夠肯定。
  3. 在程序運行期間編譯着色器代碼,並讀取生成的字節碼。

在我的的DX11項目中,使用的是方法1(優先)和方法3的混合形式。儘管方法2是最近了解到的,但我的目前並不考慮更換爲該方法。數組

DirectX11 With Windows SDK完整目錄安全

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。函數

與着色器相關的文件擴展名

爲了符合微軟的約定,須要爲你的着色器代碼使用下面的擴展名(有所修改):佈局

  1. 擴展名爲.hlsl的文件用於編寫HLSL的源代碼,參與編譯
  2. 擴展名爲.hlsli的文件做爲HLSL的標頭文件,不參與編譯
  3. 擴展名爲.cso的文件做爲已編譯的着色器對象(Compiled Shader Object)
  4. 擴展名爲.inc.h的文件是C++的頭文件,但它的內部包含了着色器的字節碼,使用BYTE數組來記錄

方法1:編譯期產生對象文件,並在運行期加載

如今以Rendering a Triangle項目爲例,如今咱們已經編寫好的着色器文件有Triangle.hlsli, Triangle_VS.hlsl, Triangle_PS.hlsl這三個,它們存放項目在HLSL文件夾內。如今你能夠將它拉進項目當中。大數據

其中Triangle.hlsli做爲HLSL的頭文件默認不參與項目的編譯過程。優化

而對於Triangle_VS.hlslTriangle_PS.hlsl,則在項目屬性要這樣設置:3d

其中入口點名稱指的是該着色器階段最早開始調用的函數名。好比在C/C++/新建的.hlsl文件中,默認的入口點名稱是main。而上面的例子中,咱們但願讓頂點着色器從VS函數開始運行,則須要指定入口點爲VS調試

關於着色器模型,由於假定用戶的顯卡已經支持特性等級11.0,這裏使用的是Shader Model 5.0,若是你的顯卡不支持特性等級11.0,則須要將特性等級降爲10.0/10.1,分別對應能使用的着色器模型爲4.0/4.1

生成項目後,須要留意在輸出窗口(生成)中是否出現了下面的內容:

只有出現了上述內容,才說明成功編譯出對象文件,不然說明沒有被編譯出來。若是你以前已經編譯出對象文件,再編譯時沒有出現該輸出結果,可能須要先刪除以前編譯出來的對象文件再試一次。

D3DReadFileToBlob函數--讀取編譯好的着色器二進制信息

對着色器代碼或文件的相關操做位於頭文件d3dcompiler.h,並且你還須要添加靜態庫d3dcompiler.lib

接下來,咱們使用下面的函數來讀取編譯好的着色器二進制信息:

HRESULT D3DReadFileToBlob(LPCWSTR pFileName,    // [In].cso文件名
                  ID3DBlob** ppContents);       // [Out]獲取二進制大數據塊

注意:若是你的項目中不存在該函數,說明你可能預先包含了DX SDK,然而該教程使用的是Windows SDK,該函數位於D3DCompiler >= 46的版本,所以你須要剔除DX SDK的包含路徑和庫路徑。

使用方式也十分簡單(以建立頂點着色器和頂點佈局爲例):

ComPtr<ID3DBlob> blob;
HR(D3DReadFileToBlob(L"HLSL\\Triangle_VS.cso", blob.GetAddressOf()));
HR(md3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader.GetAddressOf()));
// 建立頂點佈局
HR(md3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
    blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout.GetAddressOf()));

而後就能夠拿獲取到的ID3DBlob來建立着色器了。建立着色器和頂點佈局的部分在本文不進行討論,請回到教程02繼續查看。

該方法的特色是會在你的項目文件夾中產生編譯好的着色器二進制文件,而且須要你在程序運行的時候直接讀進來。

方法2:編譯器產生頭文件,並在項目中包含該文件

對於Triangle_VS.hlslTriangle_PS.hlsl,在項目屬性要這樣設置:

這裏關於頭文件的名稱以及內部的全局變量名能夠自行決定。

頭文件 通過編譯後會在HLSL文件夾產生Triangle_VS.incTriangle_PS.inc兩個文件,觀察裏面的代碼你能夠發現裏面有彙編部分(不會包含進代碼中)和一個全局變量,在Triangle_VS.inc中產生的是全局變量gTriangle_VS,而在Triangle_PS.inc中產生的是全局變量gTriangle_PS。這兩個變量都是BYTE數組,裏面的內容正是編譯好的字節碼。

如今須要在你須要編寫建立着色器相關代碼的源文件上面包含這兩個頭文件:

#include "HLSL/Triangle_VS.inc"
#include "HLSL/Triangle_PS.inc"

而後建立頂點着色器和頂點佈局的代碼變成了這樣:

// 建立頂點着色器
HR(m_pd3dDevice->CreateVertexShader(gTriangle_VS, sizeof(gTriangle_VS), nullptr, m_pVertexShader.GetAddressOf()));
// 建立並綁定頂點佈局
HR(m_pd3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
    gTriangle_VS, sizeof(gTriangle_VS), m_pVertexLayout.GetAddressOf()));

接下來就能夠生成整個項目了,須要留意是否有紅色部分的輸出,不然可能沒有成功編譯出.inc文件(這可能會在已有.inc文件再次編譯的時候致使出現問題,須要刪除原來的.inc文件)。

因爲上述兩個頭文件的產生(即着色器的編譯)先於項目的編譯,在沒有產生這兩個頭文件的時候,你也能夠忍着編譯錯誤先把上述代碼添加進去,而後編譯的時候就一切正常了。

該方法的特色是全部的過程均在編譯期完成,着色器字節碼鑲嵌在了你的應用程序內部,可能會致使應用程序變大。

方法3:運行期間編譯着色器代碼,生成字節碼

如今你須要瞭解這些函數

D3DCompileFromFile函數--運行期編譯.hlsl文件

HRESULT D3DCompileFromFile(
    LPCWSTR pFileName,                  // [In]要編譯的.hlsl文件
    CONST D3D_SHADER_MACRO* pDefines,   // [In_Opt]忽略
    ID3DInclude* pInclude,              // [In_Opt]如何應對#include宏
    LPCSTR pEntrypoint,                 // [In]入口函數名
    LPCSTR pTarget,                     // [In]使用的着色器模型
    UINT Flags1,                        // [In]D3DCOMPILE系列宏
    UINT Flags2,                        // [In]D3DCOMPILE_FLAGS2系列宏
    ID3DBlob** ppCode,                  // [Out]得到着色器的二進制塊
    ID3DBlob** ppErrorMsgs);            // [Out]可能會得到錯誤信息的二進制塊

再次注意:若是你的項目中不存在該函數,說明你可能預先包含了DX SDK,然而該教程使用的是Windows SDK,該函數位於D3DCompiler >= 46的版本,所以你須要剔除DX SDK的包含路徑和庫路徑。

其中pInclude用於決定如何處理包含文件。若是設爲nullptr,則編譯的着色器代碼包含#include時會引起編譯器報錯。若是你須要使用#include,能夠傳遞D3D_COMPILE_STANDARD_FILE_INCLUDE宏,這是一個默認的包含句柄,能夠按該着色器代碼所處的相對路徑去搜索對應的頭文件幷包含進來。

#define D3D_COMPILE_STANDARD_FILE_INCLUDE ((ID3DInclude*)(UINT_PTR)1)

D3DWriteBlobToFile函數--將編譯好的着色器二進制信息寫入文件

HRESULT D3DWriteBlobToFile(
    ID3DBlob* pBlob,    // [In]編譯好的着色器二進制塊
    LPCWSTR pFileName,  // [In]輸出文件名
    BOOL bOverwrite);   // [In]是否容許覆蓋

對於bOverwrite來講,不管是TRUE仍是FALSE都可有可無,由於咱們只有在檢測到沒有編譯好的着色器文件時纔會啓動運行期編譯,而後再保存到文件。

具體用法已經集成在下面的CreateShaderFromFile函數中了

CreateShaderFromFile函數的實現

下面是CreateShaderFromFile函數的實現,如今該函數已經放到了d3dUtil.h中,須要依賴dxerr,你也能夠本身修改這個函數的實現:

// 安全COM組件釋放宏
#define SAFE_RELEASE(p) { if ((p)) { (p)->Release(); (p) = nullptr; } }

// ------------------------------
// CreateShaderFromFile函數
// ------------------------------
// [In]csoFileNameInOut 編譯好的着色器二進制文件(.cso),如有指定則優先尋找該文件並讀取
// [In]hlslFileName     着色器代碼,若未找到着色器二進制文件則編譯着色器代碼
// [In]entryPoint       入口點(指定開始的函數)
// [In]shaderModel      着色器模型,格式爲"*s_5_0",*能夠爲c,d,g,h,p,v之一
// [Out]ppBlobOut       輸出着色器二進制信息
HRESULT CreateShaderFromFile(const WCHAR * csoFileNameInOut, const WCHAR * hlslFileName,
    LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob ** ppBlobOut)
{
    HRESULT hr = S_OK;

    // 尋找是否有已經編譯好的頂點着色器
    if (csoFileNameInOut && D3DReadFileToBlob(csoFileNameInOut, ppBlobOut) == S_OK)
    {
        return hr;
    }
    else
    {
        DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
        // 設置 D3DCOMPILE_DEBUG 標誌用於獲取着色器調試信息。該標誌能夠提高調試體驗,
        // 但仍然容許着色器進行優化操做
        dwShaderFlags |= D3DCOMPILE_DEBUG;

        // 在Debug環境下禁用優化以免出現一些不合理的狀況
        dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
        ID3DBlob* errorBlob = nullptr;
        hr = D3DCompileFromFile(hlslFileName, nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entryPoint, shaderModel,
            dwShaderFlags, 0, ppBlobOut, &errorBlob);
        if (FAILED(hr))
        {
            if (errorBlob != nullptr)
            {
                OutputDebugStringA(reinterpret_cast<const char*>(errorBlob->GetBufferPointer()));
            }
            SAFE_RELEASE(errorBlob);
            return hr;
        }

        // 若指定了輸出文件名,則將着色器二進制信息輸出
        if (csoFileNameInOut)
        {
            return D3DWriteBlobToFile(*ppBlobOut, csoFileNameInOut, FALSE);
        }
    }

    return hr;
}

使用方式以下:

ComPtr<ID3DBlob> blob;

// 建立頂點着色器
HR(CreateShaderFromFile(L"HLSL\\Triangle_VS.cso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
HR(m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader.GetAddressOf()));
// 建立並綁定頂點佈局
HR(m_pd3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
    blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout.GetAddressOf()));

參考文章:

Compiling Shaders

How To: Compile a Shader

DirectX11 With Windows SDK完整目錄

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。

相關文章
相關標籤/搜索