DirectX11--HLSL编译着色器的三种方法
前言
本教程不考虑Effects11(FX11),而是基于原始的HLSL。
目前编译与加载着色器的方法如下:
- 使用Visual Studio中的HLSL编译器,随项目编译期间一同编译,并生成
.cso
(Compiled Shader Object)对象文件,在运行期间加载该文件以读取字节码。 - 使用Visual Studio中的HLSL编译器,随项目编译期间一同编译,并生成
.inc
或.h
的头文件,着色器字节码在编译期间就可以确定。 - 在程序运行期间编译着色器代码,并读取生成的字节码。
在个人的DX11项目中,使用的是方法1(优先)和方法3的混合形式。尽管方法2是最近了解到的,但个人目前并不考虑更换为该方法。
DirectX11 With Windows SDK完整目录
欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。
与着色器相关的文件扩展名
为了符合微软的约定,需要为你的着色器代码使用下面的扩展名(有所修改):
- 扩展名为
.hlsl
的文件用于编写HLSL的源代码,参与编译 - 扩展名为
.hlsli
的文件作为HLSL的标头文件,不参与编译 - 扩展名为
.cso
的文件作为已编译的着色器对象(Compiled Shader Object) - 扩展名为
.inc
或.h
的文件是C++的头文件,但它的内部包含了着色器的字节码,使用BYTE
数组来记录
方法1:编译期产生对象文件,并在运行期加载
现在以Rendering a Triangle项目为例,现在我们已经编写好的着色器文件有Triangle.hlsli
, Triangle_VS.hlsl
, Triangle_PS.hlsl
这三个,它们存放项目在HLSL文件夹内。现在你可以将它拉进项目当中。
其中Triangle.hlsli
作为HLSL的头文件默认不参与项目的编译过程。
而对于Triangle_VS.hlsl
和Triangle_PS.hlsl
,则在项目属性要这样设置:
其中入口点名称指的是该着色器阶段最先开始调用的函数名。比如在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.hlsl
和Triangle_PS.hlsl
,在项目属性要这样设置:
这里关于头文件的名称以及内部的全局变量名可以自行决定。
头文件 经过编译后会在HLSL文件夹产生Triangle_VS.inc
和Triangle_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()));
参考文章:
DirectX11 With Windows SDK完整目录
欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。
DirectX11--HLSL编译着色器的三种方法的更多相关文章
- 【Android】Eclipse自动编译NDK/JNI的三种方法
[Android]Eclipse自动编译NDK/JNI的三种方法 SkySeraph Sep. 18th 2014 Email:skyseraph00@163.com 更多精彩请直接访问SkySer ...
- 【Android】Eclipse自己主动编译NDK/JNI的三种方法
[Android]Eclipse自己主动编译NDK/JNI的三种方法 SkySeraph Sep. 18th 2014 Email:skyseraph00@163.com 一.Eclipse关联cy ...
- 编写Unity3D着色器的三种方式
不管你会不会写Unity3D的shader,估计你会知道,Unity3D编写shader有三种方式,这篇东西主要就是说一下这三种东西有什么区别,和大概是怎样用的. 先来列一下这三种方式: fixed ...
- 反编译APK文件的三种方法(转)
因为学习Android编程的需要,有时我们需要对网络上发布的应用项目进行学习,可是Android项目一般是通过APK文件进行发布的,我们看不到源代码,嘿嘿,办法总会有的,而且不止一个... ps:对于 ...
- Eclipse自动编译NDK/JNI的三种方法
一.Eclipse关联cygwin 1. 工程->右击选择Properties->选择Builders,在Builders中选择New创建一个Program 2. 参数配置 二.Eclip ...
- WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码
原文:WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码 HLSL,High Level Shader Language,高级着色器语言,是 Di ...
- HLSL像素着色器
原文:HLSL像素着色器 昨日不可追, 今日尤可为.勤奋,炽诚,不忘初心 手机淘宝二维码 扫描 或者打开连接:程序设计开发 ,掌声鼓励,欢迎光临. 像素着色器替代了固定渲染管线的 ...
- 服务器文档下载zip格式 SQL Server SQL分页查询 C#过滤html标签 EF 延时加载与死锁 在JS方法中返回多个值的三种方法(转载) IEnumerable,ICollection,IList接口问题 不吹不擂,你想要的Python面试都在这里了【315+道题】 基于mvc三层架构和ajax技术实现最简单的文件上传 事件管理
服务器文档下载zip格式 刚好这次项目中遇到了这个东西,就来弄一下,挺简单的,但是前台调用的时候弄错了,浪费了大半天的时间,本人也是菜鸟一枚.开始吧.(MVC的) @using Rattan.Co ...
- C#中??和?分别是什么意思? 在ASP.NET开发中一些单词的标准缩写 C#SESSION丢失问题的解决办法 在C#中INTERFACE与ABSTRACT CLASS的区别 SQL命令语句小技巧 JQUERY判断CHECKBOX是否选中三种方法 JS中!=、==、!==、===的用法和区别 在对象比较中,对象相等和对象一致分别指的是什么?
C#中??和?分别是什么意思? 在C#中??和?分别是什么意思? 1. 可空类型修饰符(?):引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空.例如:string str=null; ...
随机推荐
- 深入浅出ES6教程模块化
大家好,本人名叫苏日俪格,大家叫我 (格格) 就好,在上一章节中我们学到了Promise的用法,下面我们一起来继续学习模块化: JavaScript本身是不支持模块化的,只不过后来一些社区的大佬制定了 ...
- Asp.Net中virtual、override理解
virtual关键字用于指定属性或方法在派生类中重写.默认情况下,派生类从其基类继承属性和方法,如果继承的属性或方法需要在派生类中有不同的行为,则可以重写它,即可以在派生类中定义该属性或方法的新实现, ...
- Python进阶教程001内置数据类型
关于Python的基础知识已经告一段落了,我们接下来深入的研究Python的使用方法,以及以后将要使用到的类库. 格式化字符串 Python是支持字符串的格式化输出的,在之前的学习中我们也遇到过和使用 ...
- html元素禁用disable or enable
场景说明 ajax提交数据,防止收到服务端相应前用户重复点击. 1.用户点击按钮,禁用当前按钮,发起ajax请求. 2.收到ajax请求,还原当前按钮. html解决方案 参考地址:http://ww ...
- SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍
SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍 本篇博文将介绍几种如何处理url中的参数的注解@PathVaribale/@Requ ...
- rank() partition by 排名次
rank()排名 partition by分组与group by相比各有优势,在这里就省略100字.... 以下为案例: create table student -- 学生表(sid integer ...
- C++ 精英化趋势
精英化趋势 C++ 是一门引起无数争议的语言.眼下最常听到的声音则是 C++ 将趋于没落,会被某某语言取代.我很怀疑这种论调的起点是商业宣传,C++ 的真实趋势应该是越来越倾向于精英化. 精英化是指在 ...
- 第二章 Python基本图形绘制
2.1 深入理解Python语言 Python语言是通用语言 Python语言是脚本语言 Python语言是开源语言 Python语言是跨平台语言 Python语言是多模型语言 Python的特点与优 ...
- vscode 打开多个标签页
默认只打开2个,按如下设置可以支持多开: 路径C:\Users\admin\AppData\Roaming\Code\User下的settings.json添加一条配置:"workbench ...
- PHP 面试知识点整理归纳
基础篇了解大部分数组处理函数 array_chunk — 将一个数组分割成多个 array_column — 返回数组中指定的一列 array_combine — 创建一个数组,用一个数组 ...