《Practical Rendering & Computation with Direct3D11》读书总结 Chapter-6-High Level Shading Language

Introduction

在DirectX中,编写可编程Shader的时候所使用的语言为High Level Shading Language(HLSL),它是由C/C++衍生出来的一种语言,具有一些特性,它的语言风格与C语言非常相似,但是又有很大的不同。最重要的不同之处在于在HLSL中没有指针、没有C++中的template、不支持动态的内存分配。HLSL语言写出来的程序,都要在C/C++中提前编译成ByteCode,再绑定到流水线上。

Language Basics

基本的数据类型
bool:32-bit 整数,包含逻辑true、false信息。
int:32-bit 有符号整数。
uint:32-bit 无符号整数。
half:16-bit 浮点数,注意在D3D11中,half的存在仅仅是为了兼容以往的half的使用,在D3D11的Shader中统一会将half转化为float。
float:32-bit 浮点数。
double:64-bit 浮点数。

Vectors:
Vector支持有1-4个成员,每个成员都是相同的类型。例如可以这样声明Vector:

    vector<float,4> floatVector;
    vector<int,2> intVector;

这样写是比较麻烦的,可以简化为:

    float4 floatVector;
    int2 intVector;

Vector的初始化方式:

    float2 vec0 = { 0.0f, 1.0f };
    float3 vec1 = float3( 0.0f , 0.1f , 0.2f );
    float4 vec2 = float4( vec1 , 1.0f );
    float4 vec3 = 1.0f; //将4个成员全部复制为1.0f

对Vector成员的访问:
Vector的四维分别用x、y、z、w或者r、g、b、a表示。

    float4 floatVector = 1.0f; //声明一个 [1.0f,1.0f,1.0f,1.0f]的Vector
    float firstComponent = floatVector.x;
    float secondComponent = floatVector.g;
    float thirdComponent = floatVector[2];

上面是三种访问成员的方式,当然也可以有复合的访问:

    float vec0 = float4(0.0f,1.0f,2.0f,3.0f);
    float3 vec1 = vec0.xyz;
    float2 vec2 = vec1.rg;
    float3 vec3 = vec0.zxy;
    float4 vec4 = vec2.xyxy;
    float4 vec5;
    vec5.zyxw = vec0.xyzw;

Matrices:

关于矩阵类型首先要明确几点:
1.D3D使用的是左手系,所有的变换矩阵都必须是左手系下的。
2.D3D中的向量都是行向量,表示矩阵向量乘法的时候,是向量×矩阵。
3.HLSL中的矩阵初始化以及对其访问,要按照正常的线性代数中的矩阵表示逻辑来初始化,即行在前,列在后的顺序。而对于输入装配过来的矩阵,比如CBuffer的中矩阵,编译器会对其使用以列在前、行在后的顺序进行处理,这是为了提升处理的效率,所以在cbuffer中如果要装配Matrix,需要在C++代码中进行转置处理,或者在HLSL再执行转置也可以。

    float4x4 worldMatrix = float4x4( float4( 1.0f, 0.0f, 0.0f, 0.0f ),
                                    float4( 1.0f, 0.0f, 0.0f, 0.0f ),
                                    float4( 1.0f, 0.0f, 0.0f, 0.0f ),
                                    float4( 1.0f, 0.0f, 0.0f, 0.0f ));
    float matVal = worldMatrix[0][1]; //访问第一行第二列的元素

Structures:

HLSL中声明结构体和在C++中差不多。

    struct MyStructure
    {
        float4 Vec;
        int Scalar;
        float4x4 Mat;
        float Array[8];
    }

Functions:

函数的定义方法与C++中的相同,但是要注意参数可以有修饰符。
修饰符有四种:
in :表示参数是这个函数的输入,对它所作的任何修改都只是暂时的,类似于C++中的按值传递
out :表示参数是这个函数的输出,它的值要被这个函数所设定。
input :表示参数既是输入也是输出,它的值可以被函数读取所利用,也可以被函数进一步修改。
uniform:表示参数是常量,不可修改。

Conditionals:

在HLSL中使用动态条件分支时,一定要注意不能使用PixelShader中的Sample语句,因为Sample通常都是以2x2的一组texels同时处理的,并且会用到相邻的信息,如果某个fragment由于动态的分支的语句被跳过Sample的话,相邻的Sample就得不到需要的信息,出现问题。

Constant Buffers

    cbuffer VSConstants
    {
        float4x4 WorldMatrix;
        float4x4 ViewProjMatrix;
        float3 Color;
        uint EnableFog;
    }

上面是HLSL中声明一个Constant Buffer的例子,像这样的声明其实HLSL自动做了一些处理,处理后的结果应该是:

    cbuffer VSConstants : register(cb0)
    {
        float4x4 WorldMatrix(c0);
        float4x4 ViewProjMatrix(c4);
        float3 Color(c8);
        uint EnableFog(c8.w);
    }

cb0表示这个cbuffer被绑定到了0号Constant Buffer寄存器上。而后面的c0、c4、c8表示的是偏移量,即该成员到结构体内存开头的偏移量,这个偏移量以float4为单位。注意,这个偏移量一定要保证一个变量不能同时跨越两个float4的内存块,这也就是为什么cbuffer的大小一定要是float4的大小的整数倍的原因。这些设定的值都是可以显式修改的。

在Constant Buffer中其实还有一类很特殊的Buffer——Texture Buffer。Constant Buffer存储少量的常量数据,比较轻巧。但是考虑到某些情形,比如在Vertex Shader中,当需要用到一个相对较大的数组的时候,每个顶点都需要用到这个数组中不同的地方的值,这个时候如果还是用Constant Buffer的话,就会使得每个线程对这个Buffer的访问序列化,即让各个线程按照一定的顺序来访问,造成线程的阻塞,降低运行的速度。那么Texture Buffer就可以相对较好的解决这个问题,用Texture Buffer会使访问buffer的时候是通过texture fetching pipeline,不会产生这种阻塞的问题。

    tbuffer Bones : register(t0)
    {
    float4x4 BoneMatrices[256];
    }

使用tbuffer的时候要将buffer绑定到texture register上,tbuffer资源的创建也需要用CreateTexture函数而不是CreateBuffer函数,而且要创建一个Shader Resource View来绑定资源,通过SetShaderResources来绑定。
当然好像这么一看TextureBuffer和ConstantBuffer似乎没有什么相似的地方= =所以我也不知道为什么要说它是Constant Buffer的一种特殊情形。

Resource Objects

在Shader中用到的资源都是通过 SetShaderResources、SetUnorderedAccessViews这样的函数来绑定的,这些资源在CPU中调用的时候通常会指定SLOT,这对应着HLSL中register中的编号。ShaderResourceView对应的是t寄存器,而UnorderedAccessView对应的是u寄存器(只有8个)

Buffers
Buffer Resource都来自于一个ID3D11Buffer类型的数据,如果现在有一个格式为DXGI__FORMAT_R32G32B32A32_FLOAT的Buffer,在HLSL中可以这样来接受:

    Buffer<float4> Float4Buffer : register(t0);

Buffer的种类是有很多的,它们的使用方式也有一些区别。

ByteAddressBuffer:这种Buffer允许使用byte offset而不是使用index来访问,在HLSL中可以使用Load、Load2、Load3、Load4方法,相应地会返回1、2、3、4个uint类型的值,如果Buffer的格式不是uint类型,那么还需要做转换。

StructuredBuffer:接受Buffer的时候尖括号内要是相应的结构体类型,访问直接按照访问结构体的成员方式即可。

Read/Write Buffer:这种Buffer需要加上RW前缀。

Append/Consume Buffer:可以对Append Buffer使用Append()方法,可以对Consume Buffer使用Consume()方法。

Stream Output Buffer:比较特殊,它不会被绑定到寄存器上,而是直接作为一个inout型的参数作为Geometry Shader的参数。

Textures

Texture的全部类型包括:Texture1D、Texture1DArray、Texture2D、Texture2DArray、Texture2DMS、Texture2DMSArray、Texture3D、Texture3DArray、TextureCube、TextureCubeArray。
对于所有的Texture,可以用的操作方法有:

SampleMethod:Sample方法需要有SamplerState,这个也是需要绑定到寄存器上的资源,绑定在s寄存器上。1D的Texture需要提供1个[0-1]内的浮点数,而2D的需要2个,3D的需要3个,Sample的返回值的类型取决于Texture的Format,如果Texture本身的Format为DXGI_FORMAT_R32G32B32A32_Float,那么返回值就是float4类型。

SampleMethod:在SamplerState中都会指定一个比较函数,会根据Sample的结果和传入参数进行比较函数之后的结果来确定返回值。如果比较函数通过了,就会返回1.0,否则会返回0.0。

GatherMehod:Sample的时候通常会以2x2的方式同时处理一组texels,那么Gather方法就是同时获取这2x2个fragment中的信息,例如GatherRed就会返回4个texels的Red信息。这个方法在做图像处理的时候很常用。

LoadMethod:Load也是获取Texture中的某个位置的信息,与Sample不同的是,它传入的参数是无符号整数型,是以原始的Texture的格式来做信息的获取。对于MSTexture,它还可以获取一个Pixel的subsample的信息。

GetMethod:询问资源的基本信息,如GetDimensions获取资源的大小,mipmap的等级,sample的数量等等。

之后还介绍了HLSL的一些常用的内置函数,这些可以去查阅相关文档了解,不过最好还是遇到不懂的函数了再去查阅。

猜你喜欢

转载自blog.csdn.net/yjr3426619/article/details/81317586
今日推荐