DirectX11--HLSL编译着色器的三种方法

前言

本文不考虑Effects11(FX11),而是原始的HLSL语言。

该文章从教程02单独抽离出来作为单独的教程。

目前编译与加载着色器的方法如下:

  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数组来记录

编译期产生对象文件,并在运行期加载

现在以Rendering a Triangle项目为例,现在我们已经编写好的着色器文件有Triangle.hlsli, Triangle_VS.hlsl, Triangle_PS.hlsl这三个,可以将它拉进项目当中。

其中Triangle.hlsli作为HLSL的头文件默认不参与项目的编译过程。

而对于Triangle_VS.hlslTriangle_PS.hlsl,则在项目属性要这样设置:

生成项目后,需要留意在输出窗口(生成)中是否出现了下面的内容:

只有出现了上述内容,才说明成功编译出对象文件,否则说明没有被编译出来。如果你之前已经编译出对象文件,再编译时没有出现该输出结果,可能需要先删除之前编译出来的对象文件再试一次。

D3DReadFileToBlob函数--读取编译好的着色器二进制信息

对着色器代码或文件的相关操作位于头文件d3dcompiler.h

接下来,我们使用下面的函数来读取编译好的着色器二进制信息:

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, mVertexShader.GetAddressOf()));
// 创建顶点布局
HR(md3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
    blob->GetBufferPointer(), blob->GetBufferSize(), mVertexLayout.GetAddressOf()));

然后就可以拿获取到的ID3DBlob来创建着色器了。创建着色器和顶点布局的部分在本文不进行讨论,请回到教程02继续查看。

该方法的特点是会在你的项目文件夹中产生编译好的着色器二进制文件,并且需要你在程序运行的时候直接读进来。

编译器产生头文件,并在项目中包含该文件

对于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(md3dDevice->CreateVertexShader(gTriangle_VS, sizeof(gTriangle_VS), nullptr, mVertexShader.GetAddressOf()));
// 创建并绑定顶点布局
HR(md3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
    gTriangle_VS, sizeof(gTriangle_VS), mVertexLayout.GetAddressOf()));

接下来就可以生成整个项目了,需要留意是否有红色部分的输出,否则可能没有成功编译出.inc文件(这可能会在已有.inc文件再次编译的时候导致出现问题,需要删除原来的.inc文件)。

由于上述两个头文件的产生(即着色器的编译)先于项目的编译,在没有产生这两个头文件的时候,你也可以忍着编译错误先把上述代码添加进去,然后编译的时候就一切正常了。

该方法的特点是所有的过程均在编译期完成,着色器字节码镶嵌在了你的应用程序内部,可能会导致应用程序变大。

运行期间编译着色器代码,生成字节码

现在你需要了解这些函数

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和标准库的filesystem

// 该函数需要包含filesystem头文件,并using namespace std::experimental;(C++11/14)

// ------------------------------
// CreateShaderFromFile函数
// ------------------------------
// [In]objFileNameInOut 编译好的着色器二进制文件(.cso),若有指定则优先寻找该文件并读取
// [In]hlslFileName     着色器代码,若未找到着色器二进制文件则编译着色器代码
// [In]entryPoint       入口点(指定开始的函数)
// [In]shaderModel      着色器模型,格式为"*s_5_0",*可以为c,d,g,h,p,v之一
// [Out]ppBlobOut       输出着色器二进制信息
HRESULT CreateShaderFromFile(const WCHAR * objFileNameInOut, const WCHAR * hlslFileName, LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob ** ppBlobOut)
{
    HRESULT hr = S_OK;

    // 寻找是否有已经编译好的顶点着色器
    if (objFileNameInOut && filesystem::exists(objFileNameInOut))
    {
        HR(D3DReadFileToBlob(objFileNameInOut, ppBlobOut));
    }
    else
    {
        DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
        // 设置 D3DCOMPILE_DEBUG 标志用于获取着色器调试信息。该标志可以提升调试体验,
        // 但仍然允许着色器进行优化操作
        dwShaderFlags |= D3DCOMPILE_DEBUG;

        // 在Debug环境下禁用优化以避免出现一些不合理的情况
        dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
        ComPtr<ID3DBlob> errorBlob = nullptr;
        hr = D3DCompileFromFile(hlslFileName, nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entryPoint, shaderModel,
            dwShaderFlags, 0, ppBlobOut, errorBlob.GetAddressOf());
        if (FAILED(hr))
        {
            if (errorBlob != nullptr)
            {
                OutputDebugStringA(reinterpret_cast<const char*>(errorBlob->GetBufferPointer()));
            }
            return hr;
        }

        // 若指定了输出文件名,则将着色器二进制信息输出
        if (objFileNameInOut)
        {
            HR(D3DWriteBlobToFile(*ppBlobOut, objFileNameInOut, FALSE));
        }
    }

    return hr;
}

使用方式如下:

// 创建顶点着色器
HR(CreateShaderFromFile(L"HLSL\\Triangle_VS.cso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
HR(md3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mVertexShader.GetAddressOf()));
// 创建并绑定顶点布局
HR(md3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
    blob->GetBufferPointer(), blob->GetBufferSize(), mVertexLayout.GetAddressOf()));

参考文章:

Compiling Shaders

How To: Compile a Shader

DirectX11 With Windows SDK完整目录

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

原文地址:https://www.cnblogs.com/X-Jun/p/10066282.html

时间: 2024-10-11 22:19:05

DirectX11--HLSL编译着色器的三种方法的相关文章

【Android】Eclipse自己主动编译NDK/JNI的三种方法

[Android]Eclipse自己主动编译NDK/JNI的三种方法 SkySeraph Sep. 18th  2014 Email:[email protected] 一.Eclipse关联cygwin 1. project->右击选择Properties->选择Builders,在Builders中选择New创建一个Program 2. 參数配置 二.Eclipse关联ndk-build(自建Builder方法)  1. Project->Properties->Builder

【Android】Eclipse自动编译NDK/JNI的三种方法

[Android]Eclipse自动编译NDK/JNI的三种方法 SkySeraph Sep. 18th  2014 Email:[email protected] 一.Eclipse关联cygwin 1. 工程->右击选择Properties->选择Builders,在Builders中选择New创建一个Program 2. 参数配置 二.Eclipse关联ndk-build(自建Builder方法)  1. Project->Properties->Builders->N

编写Unity3D着色器的三种方式

不管你会不会写Unity3D的shader,估计你会知道,Unity3D编写shader有三种方式,这篇东西主要就是说一下这三种东西有什么区别,和大概是怎样用的. 先来列一下这三种方式: fixed function shader vertex and fragment shader surface shader 为什么Unity3D要提供三种shader的编写方式呢?那是因为三种方式的编写的难易度有区别,对应着不同的使用人群.其实我觉得这是Uniy3D想得有点多了,着色器不单止是为了实现效果,

反编译APK文件的三种方法(转)

因为学习Android编程的需要,有时我们需要对网络上发布的应用项目进行学习,可是Android项目一般是通过APK文件进行发布的,我们看不到源代码,嘿嘿,办法总会有的,而且不止一个... ps:对于软件开发人员来说,保护代码安全也是比较重要的因素之一,不过目前来说Google Android平台选择了Java Dalvik VM的方式使其程序很容易破解和被修改,首先APK文件其实就是一个MIME为ZIP的压缩包,我们修改ZIP后缀名方式可以看到内部的文件结构,类似Sun JavaMe的Jar压

Directx 中HLSL高级着色器语言 脑补一下吧

HLSL初级教程 作者:trcj 目录 前言 1.HLSL入门 1.1什么是着色器 1.2什么是HLSL 1.3怎么写HLSL着色器 1.4怎么用HLSL着色器 2.顶点着色器 2.1可编程数据流模型 2.2顶点声明 2.3用顶点着色器实现渐变动画 3.像素着色器 3.1多纹理化 3.2多纹理效果的像素着色器 3.3应用程序 4.HLSL Effect(效果框架) 4.1Effect代码结构 4.2用Effect实现多纹理化效果 结语 参考资料 前言 本教程针对HLSL(High Level S

HLSL像素着色器

原文:HLSL像素着色器 昨日不可追,?今日尤可为.勤奋,炽诚,不忘初心 手机淘宝二维码?扫描?????? 或者打开连接:程序设计开发?,掌声鼓励,欢迎光临. ? ? 像素着色器替代了固定渲染管线的?多纹理化?阶段(书上说的) 这是片面的,不完善的,??其实像素着色器,只要渲染到屏幕上,那就有像素这个东西,就要有像素着色器. 实现步骤: 1.编写和编译像素着色器文件 2.创建像素着色器 3.设置像素着色器 //文本文件代码 //--------------------------begim ps

WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码

原文:WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码 HLSL,High Level Shader Language,高级着色器语言,是 Direct3D 着色器模型所必须的语言.WPF 支持 Direct3D 9,也支持使用 HLSL 来编写着色器.你可以使用任何一款编辑器来编写 HLSL,但 Shazzam Shader Editor 则是专门为 WPF 实现像素着色器而设计的一款编辑器,使用它来编写像素着色器,可以省去像素着色器接入

第二天 命令行编译的三种方法

先进入Start Command Prompt with Ruby ,在编辑器中创建好 scss 文件 进行命令操作: 第一种方法   单个文件编译: 1 .在命令行输入 cd (1)如果默认文件就在 C盘 就直接进行命令操作 Start Command Prompt with Ruby: 编辑器: (2)如过默认文件不在 C盘 就要先进入到文件所在的盘内,在进行命令操作    2 .在命令行输入 文件路径 以及  sass css/one.scss:css/one.css Start Comm

布局填充器的三种写法

布局填充器的三种写法:  1.layoutInflater=layoutInflater.from(this);  2.layoutInflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);  3.layoutInflater=this.getLayoutInflater();