D3D11计算着色器配置与编程

本文深入探讨D3D11计算着色器的使用,从HLSL文件编写到资源设置,详细讲解如何处理图像的暗通道。通过实际案例,帮助读者掌握计算着色器在图像处理中的应用。

如果开始研究计算着色器了,说明读者已经有一定的D3D11基础,自己也跑过几个程序,那么我希望看完的人能够达到自己完成编写计算着色器文件,完成自己的项目任务。由于我学习D3D11是直接跳过其他着色器的(项目无关),所以有很多基础很差,本文也只提供计算着色器相关的内容,和如何使用计算着色器处理图像的一点经验

这篇笔记很短,主要记录了我在使用计算着色器的一些经验和我认为必须的点。

1. 计算着色器hlsl文件编写(以求解图像的暗通道为例)

2. 着色器文件的编译

3. 计算着色器资源设置

 

计算着色器HLSL文件编写

本文使用的代码是在X_JUN的代码上面修改的,他的源码可以在这里下载

hlsl文件通过VS,在项目内创建一个着色器文件夹,直接创建源文件,然后修改后缀为hlsl即可。创建好文件后,记得修改属性页的入口点和hlsl文件主函数名一致,否则无法编译。选择着色器类型和模型,一般D3D11都支持5.0,不同的设备可以查询支持的模型然后选择。

 

下面是求解图像的暗通道的代码,hlsl代码主要由指定输入输出线程声明,和主函数组成。

Texture2D g_TexA : register(t0);    //输入纹理

RWTexture2D<float4> g_Output : register(u0);   //输出纹理

输入输出

        着色器的输入输出如上图所示,在这份程序中,输入输出是2D纹理,所以我们需要声明其类型及变量名。后面的resgiter,则是着色器文件的注册机制,如果是纹理(texture),则使用t加上输入序号,输入纹理1则为register(t0)。值得注意的是输出,尽管也是2D纹理,因为是可以读写的,所以使用了RWTexture2D,register(u0)则表示这是一个无序访问视图(可以修改的)类型。这些变量在C++代码中的声明和设置将在后面说道。

        此外,如果有时候我们需要输入一些变量作为着色器文件的参数,可以使用 buffer。下面给出了一个简单的例子,这个变量是从CPU传到GPU供着色器使用的,注意传入的buffer的大小必须是16字节的整数倍,否则会报错,无法传入。

cbuffer CB: register(b0)
{
	uint c1;        // 参数
	uint c2;          //参数
	uint useless1;      // 未使用
	uint useless2;     //未使用
}

线程声明

         下面是线程声明,numthread给出了一个线程组内,X,Y,Z方向各多少个线程,单个线程组的线程数不能大于1024(D3D11限制)。线程组个数通过C++中使用Dispatch函数给出,也是分配了XYZ方向有多少线程组(线程组最多为65535),同时Dispatch也是启动计算着色器的函数。线程数限制具体可以查找MSDN文档得到,不同的版本最大线程的限制不一样。

// 一个线程组中的线程数目。线程可以1维展开,也可以
// 2维或3维排布
[numthreads(32, 32, 1)]

          这里借一幅参考资料3中的图,来说明线程组和线程的关系。左边就是我们使用dispatch函数分配的线程组个数,其每个组对应了一个位置,右边则是一个线程组内的线程分布,这是由numthread所分配的。

主函数

        主程序就是每个线程要要处理的程序。首先看这里我们可以获得的线程的坐标,即主函数输入的参数。这里的ID就是上图中可以看到的坐标。对于要处理图像的任务来说,这让我们可以读出每个输入2D纹理的像素值,可以写出到输出2D纹理的位置。需要注意的是(在我才开始接触的时候混淆的一点),这里的线程坐标和图像坐标并不是同一个。一个线程是处理一个子任务,我们可以让一个线程去处理图像一个4*4微元的值,只要我们知道相应的对应关系,即哪一个线程处理哪一块的图像,我们就可以在hlsl文件中编写相应的功能。

      这几个坐标中,使用的较多主要是SV_DispatchThreadID,这是一个三维的向量对应xyz坐标。如代码片段中,用DTid的xy坐标,就可以索引对应纹理的像素值。

      基础的着色器语言可以查看参考资料3这本书,有简单介绍,基本和C++相似,但有些不同,比如代码中的float4这种变量。

Texture2D g_TexA : register(t0);              //输入纹理

RWTexture2D<float4> g_Output : register(u0);  //输出纹理

// 一个线程组中的线程数目。线程可以1维展开,也可以
// 2维或3维排布

[numthreads(32, 32, 1)]

//每个线程的处理程序,也可以对应每个像素
void CS( uint3 Gid : SV_GroupID,            //线程组ID
    uint3 DTid : SV_DispatchThreadID,       //当前线程对应的全局ID
    uint3 GTid : SV_GroupThreadID,          //线程组内的线程ID
    uint GI : SV_GroupIndex                 //线程组内将线程一维展开后,对应的索引
 )
{   

		//求解暗通道(即求解RGB通道里面的最小值)
		float4 outV;
		float  min;
		min = g_TexA[DTid.xy][0];
		if (min > g_TexA[DTid.xy][1])
			min = g_TexA[DTid.xy][1];
		if (min > g_TexA[DTid.xy][2])
			min = g_TexA[DTid.xy][2];
		outV[0] = min;
		outV[1] = 0;
		outV[2] = 0;
		outV[3] = g_TexA[DTid.xy][3];
		//求暗通道里面的最大值
		g_Output[DTid.xy] = outV;


}

着色器文件的编译、着色器创建和使用

         编译着色器文件参考资料2的博客文章给出了详细的说明。我主要用到了以下函数。

//读取编译好的cso(complied shader object)文件
HRESULT WINAPI
D3DReadFileToBlob(_In_ LPCWSTR pFileName,
                  _Out_ ID3DBlob** ppContents);

//编译hlsl文件
HRESULT WINAPI
D3DCompileFromFile(_In_ LPCWSTR pFileName,
                   _In_reads_opt_(_Inexpressible_(pDefines->Name != NULL)) CONST D3D_SHADER_MACRO* pDefines,
                   _In_opt_ ID3DInclude* pInclude,
                   _In_ LPCSTR pEntrypoint,
                   _In_ LPCSTR pTarget,
                   _In_ UINT Flags1,
                   _In_ UINT Flags2,
                   _Out_ ID3DBlob** ppCode,
                   _Always_(_Outptr_opt_result_maybenull_) ID3DBlob** ppErrorMsgs);

        以上函数输出的变量是ID3DBlob **类型,我的理解是我们要使用的着色器对象,使用这个变量我们就可以创建对应文件的着色器。利用以下函数即可创建着色器,其中m_DarkChannel_CS是预先定义的着色器,类型为ID3D11ComputeShader。这里用到了D3D11设备这个对象m_pd3dDevice创建着色器。

/*函数原型
virtual HRESULT STDMETHODCALLTYPE CreateComputeShader( 
            /* [annotation] */ 
      _In_reads_(BytecodeLength)  const void *pShaderBytecode,
            /* [annotation] */ 
      _In_  SIZE_T BytecodeLength,
            /* [annotation] */ 
      _In_opt_  ID3D11ClassLinkage *pClassLinkage,
            /* [annotation] */ 
      _COM_Outptr_opt_  ID3D11ComputeShader **ppComputeShader)
*/

m_pd3dDevice->CreateComputeShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, 
                                  m_DarkChannel_CS.GetAddressOf())

计算着色器资源设置

         着色器文件的输入输出都是在C++代码中指定的。

         输入通过XXSetShaderResource()来设置(XX是指着色器类型,计算着色器即为CS)。需要注意的是设置着色器资源必须要使用纹理对应的ShaderResourceView类型的资源,所以在C++代码中,必须要要给纹理创建一个着色器资源才能作为着色器的输入。以下代码给出了该函数的定义和使用方法。第一个参数是输入的第几个资源,对应着色器文件中的t0。第二个参数一般是设为1就行;第三个参数是纹理的指针地址,m_pTextureInputA是着色器资源类型的指针。调用方法是通过上下文对象调用函数来绑定到对应着色器对象上。

////着色器输入资源设置
 /*
virtual void STDMETHODCALLTYPE CSSetShaderResources( 
            /* [annotation] */ 
  _In_range_( 0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT - 1 )  UINT StartSlot,
            /* [annotation] */ 
  _In_range_( 0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT - StartSlot )  UINT NumViews,
            /* [annotation] */ 
  _In_reads_opt_(NumViews)  ID3D11ShaderResourceView *const *ppShaderResourceViews)
*/

//设置计算着色器输入资源
m_pd3dImmediateContext->CSSetShaderResources(0, 1, m_pTextureInputA);

       输出是通过调用以下无序视图函数来设置的,着色器输出必须是无序访问视图类型,需要提前建立对应的变量。

/*设置计算着色器输出资源,为无序访问视图
 virtual void STDMETHODCALLTYPE CSSetUnorderedAccessViews( 
            /* [annotation] */ 
        _In_range_( 0, D3D11_1_UAV_SLOT_COUNT - 1 )  UINT StartSlot,
            /* [annotation] */ 
        _In_range_( 0, D3D11_1_UAV_SLOT_COUNT - StartSlot )  UINT NumUAVs,
            /* [annotation] */ 
        _In_reads_opt_(NumUAVs)  ID3D11UnorderedAccessView *const *ppUnorderedAccessViews,
            /* [annotation] */ 
         _In_reads_opt_(NumUAVs)  const UINT *pUAVInitialCounts)
*/

m_pd3dImmediateContext->CSSetUnorderedAccessViews(0, 1, 
                                  m_pTextureOutputA_UAV.GetAddressOf(), nullptr)

完成以上的计算着色器文件编写,D3D初始化,着色器创建,创建需要用的的着色器资源视图和无序访问视图,我们就可以调用着色器时,只需要使用几句C++代码即可完成。

//设置着色器
m_pd3dImmediateContext->CSSetShader(m_DarkChannel_CS.Get(), nullptr, 0);
//设置计算着色器输入资源(着色器资源视图)
m_pd3dImmediateContext->CSSetShaderResources(0, 1, m_pTextureInputA.GetAddressOf());
//设置计算着色器输出资源(无序访问视图)
m_pd3dImmediateContext->CSSetUnorderedAccessViews(0, 1, 
                                  m_pTextureOutputA_UAV.GetAddressOf(), nullptr);
//分配线程组
m_pd3dImmediateContext->Dispatch(64, 32, 1);

通过以上代码,就可以实现利用D3D11的计算着色器求出图像暗通道图像,如下图所示。 

 

参考资料:

1. HLSL编译着色器的三种方法

2. 计算着色器:入门

3. Practical Rendering and Computation with Direct3D 11(非常重要,这本书前面还讲了一些HLSL语言的基础,值得一看。)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值