菜单

DirectX11 With Windows SDK–17 利用几何着色器实现公告板效果

2019年8月1日 - Java

前言

上一章大家明白了什么样使用几何着色器将顶点通过流输出阶段输出到绑定的顶点缓冲区。接下来大家接二连三接纳它来实现部分新的成效,在这一章,你将驾驭:

  1. 贯彻公告板效果
  2. Alpha-To-Coverage
  3. 对GPU能源举办读/写操作
  4. 纹理数组
  5. 贯彻雾效

DirectX11 With Windows
SDK完整目录

Github项目源码

前言

从这一片段开头,以为就好像步向了无人深空一样,在头里初学DX11的时候,这一部分剧情都以差不离跳过的,以后企图重新认真地把它给拾重返。

DirectX11 With Windows
SDK完整目录

Github项目源码

贯彻雾效

固然那有的与几何着色器并未什么关联,可是雾的效果在该德姆o中会用到,而且后面也从不讲过这一部分剧情,故先在此间建议来。

有的时候候大家必要在玩耍中模仿一些特定的气象条件,举个例子说灰霾。它能够让实体平滑出现并非黑马蹦出来那样(物体的一片段留在视锥体内使得只好见到该部分,然后在稳步左近该物体的时候,该物体就像经过了八个无形的扫描门被日渐构造出来那么)。通过让雾在某一限制内具有一定的层系(让不可知区域比视锥体裁剪区域还近),大家得防止止上面所说的动静。但哪怕是晴天的天气,你也许仍可望包罗八个较广范围的雾效,即距离达到十分远的地点才稳步看不清物体。

笔者们能够动用这种格局来促成雾效:钦赐雾的颜料,以录制机为原点的雾起始的细微距离,雾效范围值(超越早先距离+雾效范围值的界定外的颜色皆被内定的雾色代替)。在供给绘制的三角形内,某一像素片元的水彩如下:
\(\begin{align} foggedColor &= litColor +
s(fogColor – litColor)\\ &= (1-s) \cdot litColor + s \cdot
fogColor\\ \end{align}\)

该函数对应HLSL中的lerp函数,s取0的时候最终颜色为litColor,然后稳步增大并逼近1的时候,最终颜色就逐步趋近于fogColor。然后参数s的值取决于上边包车型大巴函数:
\(s =
saturate(\frac{dist(\mathbf{p},\mathbf{E}) –
fogStart}{fogRange})\)
\(saturate(x) = \begin{cases} x, 0 \le x
\le 1\\ 0, x < 0\\ 1, x > 1\\ \end{cases}\)

其中dist(p,E)指的是两点之间的偏离值。合营上面包车型客车图去了然:
manbetx网页手机登录版 1

再有注意一点,在历次清空重新绘制的时候,要用雾的水彩进行清空。

几何着色器

首先用一张图来回看一下渲染管线的逐个阶段,近些日子停止我们接触的着色器有极端着色器和像素着色器,而接触到的渲染管线阶段有:输入装配阶段、顶点着色阶段、光栅化阶段、像素着色阶段、输出合併阶段。

manbetx网页手机登录版 2

能够阅览,几何着色器是我们在将顶点送入光栅化阶段此前,能够操作顶点的末梢贰个等级。它一律也允许大家编辑自身的着色器代码。几何着色器能够做如下事情:

  1. manbetx网页手机登录版,让程序自动决定如何在渲染管线中插入/移除几何体;
  2. 因而流输出阶段将顶点音信重新传递到终极缓冲区;
  3. 变动图元类型(如输入点图元,输出三角形图元);

但它也会有缺点,几何着色器输出的终端数据很或然是有很多种复的,从流输出拿回来顶点缓冲区的话会占用比较多的内部存款和储蓄器空间。它自己不可能输出索引数组。

几何着色阶段会收到一名目好多代表输入几何体类型的巅峰,然后大家能够自由选拔个中的这一个极端音讯,然后交到流输出目标重新批注成新的图元类型(只怕不变),传递给流输出阶段可能是光栅化阶段。而几何着色器仅能够承受来自输入装配阶段提供的顶点音信,对种种终端进行拍卖,不能够自行决定增减顶点。

在意:离开几何着色器的极端假使要传送给光栅化阶段,须求富含有转移到齐次裁剪坐标系的坐标音讯(语义为SV_POSITIONfloat4向量)

HLSL代码

与雾效相关的值存款和储蓄在上面包车型大巴常量缓冲区中,况且绘制3D物体的终端未有产生变化:

// Basic.fx
// ...

cbuffer CBDrawingStates : register(b2)
{
    float4 gFogColor;
    int gFogEnabled;
    float gFogStart;
    float gFogRange;
}

// ...
struct Vertex3DIn
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float2 Tex : TEXCOORD;
};

struct Vertex3DOut
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION; // 在世界中的位置
    float3 NormalW : NORMAL; // 法向量在世界中的方向
    float2 Tex : TEXCOORD;
};

Basic_VS_3D.hlsl也与事先一样,未有何样改观:

// Basic_VS_3D.hlsl
#include "Basic.fx"

// 顶点着色器(3D)
Vertex3DOut VS_3D(Vertex3DIn pIn)
{
    Vertex3DOut pOut;

    row_major matrix worldViewProj = mul(mul(gWorld, gView), gProj);
    pOut.PosH = mul(float4(pIn.PosL, 1.0f), worldViewProj);
    pOut.PosW = mul(float4(pIn.PosL, 1.0f), gWorld).xyz;
    pOut.NormalW = mul(pIn.NormalL, (float3x3) gWorldInvTranspose);
    pOut.Tex = mul(float4(pIn.Tex, 0.0f, 1.0f), gTexTransform).xy;
    return pOut;
}

Basic_PS_3D.hlsl现行反革命使用了4盏方向光以保险4种差别方向的光能够均匀照射,并增加了雾效部分的处理:

// Basic_PS_3D.hlsl
#include "Basic.fx"

// 像素着色器(3D)
float4 PS_3D(Vertex3DOut pIn) : SV_Target
{
    // 提前进行裁剪,对不符合要求的像素可以避免后续运算
    float4 texColor = tex.Sample(sam, pIn.Tex);
    clip(texColor.a - 0.05f);

    // 标准化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 求出顶点指向眼睛的向量,以及顶点与眼睛的距离
    float3 toEyeW = normalize(gEyePosW - pIn.PosW);
    float distToEye = distance(gEyePosW, pIn.PosW);

    // 初始化为0 
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);

    [unroll]
    for (int i = 0; i < 4; ++i)
    {
        ComputeDirectionalLight(gMaterial, gDirLight[i], pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }

    float4 litColor = texColor * (ambient + diffuse) + spec;

    // 雾效部分
    [flatten]
    if (gFogEnabled)
    {
        // 限定在0.0f到1.0f范围
        float fogLerp = saturate((distToEye - gFogStart) / gFogRange);
        // 根据雾色和光照颜色进行线性插值
        litColor = lerp(litColor, gFogColor, fogLerp);
    }

    litColor.a = texColor.a * gMaterial.Diffuse.a;
    return litColor;
}

对此白天来讲,大家能够利用RGBA=(0.75f, 0.75f, 0.75f, 1.0f)来作为雾的颜料。

而对于黑夜来讲,这么些雾效更疑似战斗迷雾的效果与利益,我们使用RGBA=(0.0f, 0.0f, 0.0f, 1.0f)来作为雾的颜料,那样远处的物体我们就让它看不见,而在可视范围内,距离越远的物体能见度越低。

切切实实的演示效果在终极能够观望。

可编程的几何着色器

树的公告板效果

当一棵树离摄像机太远的话,大家能够采纳通知板技能,用一张树的贴图来张开绘图,替代原来绘制3D树模型的不二等秘书技。首先大家给出树的纹路贴图组成:
manbetx网页手机登录版 3

关切Alpha通道部分,深浅桔黄区域指代Alpha值为1.0(完全不透明),而米红区域指代Alpha值0.0(完全透明)。所以在渲染树纹理的时候,大家只供给对Alpha值为0.0的像素区域举办裁剪就能够。

达成文告板的关键点在于:文告板要永世正向摄像机(即视界要与文告板表面垂直),使得用户的视野在x0z面上的阴影一向与贴图表面垂直。那样做省去了大批量极端的输入和拍卖,显得愈加快捷,并且这一个小技术还是可以够诈欺玩家令人误以为仍然原来的3D模型(眼尖的游戏用户依然有相当大希望认得出去),只要你别一发轫就告诉人家那棵树的绘图用了文告板原理就行了(→_→)。

今天不思考坐标系的Y轴部分(即从上面俯视),从上面的图能够看出,通知板投影的大旨部分的法向量是一直指向摄像机的。

manbetx网页手机登录版 4

因而大家得以收获通告板的u轴,
v轴和w轴单位向量以及基于文告板构建的部分坐标系:
\(\mathbf{w}=\frac{(E_x-C_x,0,E_z-C_z)}{E_x-C_x,0,E_z-C_z}\)
\(\mathbf{v}=(0,1,0)\)
\(\mathbf{u}=\mathbf{v}\times\mathbf{w}\)

下一场已知宗旨顶点地方、树宽度和冲天,就能够求得2D树矩形的多个顶点了:

// 计算出公告板矩形的四个顶点
//            up
//       v1___|___v3
//        |   |   |
// right__|___|   |
//        |__/____|
//       v0 /     v2
//        look  
v[0] = float4(center + halfWidth * right - halfHeight * up, 1.0f);
v[1] = float4(center + halfWidth * right + halfHeight * up, 1.0f);
v[2] = float4(center - halfWidth * right - halfHeight * up, 1.0f);
v[3] = float4(center - halfWidth * right + halfHeight * up, 1.0f);

瞩目上边的加减运算是对准float3张开的,然后用1.0f填充成4D向量。而且由于各类公告板所处的一些坐标系差异样,大家须要对它们各自总结出相应的坐标轴向量。

若未来大家须求绘制布告板,则在输入的时候仅提供相应的中心顶点,然后图元类型选拔D3D11_PRIMITIVE_TOPOLOGY_POINTLIST,在几何着色阶段大家间接将顶点直传到几何着色阶段,这一个极端传递给几何着色器后就能够分解成叁个个矩形(多个三角形),发生文告板。

从一个临近没什么用的几何着色器代码入手

若大家间接从VS项目新建三个几何着色器文件,则能够看看下边包车型地铁代码:

struct GSOutput
{
    float4 pos : SV_POSITION;
};

[maxvertexcount(3)]
void main(
    triangle float4 input[3] : SV_POSITION, 
    inout TriangleStream< GSOutput > output
)
{
    for (uint i = 0; i < 3; i++)
    {
        GSOutput element;
        element.pos = input[i];
        output.Append(element);
    }
}

ps. 可能有一点人会对void
main的写法表示不爽,举个例子说小编。但是那不是C语言的主函数……

若在输入装配阶段内定使用TriangleList图元的话,开头阅览该代码,实际上你能够开掘实际该着色器只是把输入的终极按原样输出给流输出对象,即跟什么都没做(咸鱼)有怎么着分别。。不过从这份代码里面就早已满含了几何着色器所特有的多边语法了。

首先,几何着色器是基于图元类型来进展调用的,若采取的是TriangleList,则每三个三角形的三个顶峰都会作为输入,触发几何着色器的调用。那样贰个TriangleList解释的二十七个顶点会触发12遍调用。

对于几何着色器,大家亟供给内定它每趟调用所允许输出的最大终端数目。大家能够接纳属性语法来强行修改着色器行为:

[maxvertexcount(N)]

这里N纵使每一遍调用允许出现的最大终端数目,然后最后输出的顶点数目不会超越N的值。maxvertexcount的值应该尽大概的小。

关于质量上的显现,小编依照龙书提供的征引找到了相应的辨证文书档案:

NVIDIA08

虽说是10年前的文档,这里说起:在GeForce 8800
GTX,二个几何着色器的调用在出口1到十多个标量的时候能够高达最流年行质量表现,可是当大家内定最大允许输出标量的多少在27到39个时,质量仅达到峰值的二分一。比方说,若是顶点的证明如下:

struct V0
{
    float3 pos : POSITION;
    float2 tex : TEXCOORD;
};

此间各样终端就已经富含了5个标量了,尽管以它看做出口类型,则maxvertexcount为4的时候就能够达到理论上的峰值品质(贰拾肆个标量)。

但如果顶点类型中还满含有float3项指标法向量,各个终端就卓殊蕴含了3个标量,那样在maxvertexcount为4的时候就输出了35个标量,唯有八分之四的峰值品质表现。

那份文书档案已经贴近10年了,对于这时候的显卡来讲使用几何着色器或者不是叁个很好的选用,然而当下的显卡也一度无法和当今的显卡天公地道了。

注意:

  1. maxvertexcount的值应该设置到尽或然小的值,因为它将直接决定几何着色器的周转作用。
  2. 几何着色器的历次调用最七只好管理10二十四个标量,对于只含有4D地方向量的终极来讲也只可以管理2五15个极点。
  3. 几何着色器输入的结构体类型分裂意当先129个标量,对于只包涵4D地点向量的巅峰来讲也不得不分包三十多个顶峰。

在HLSL编写翻译器里,如若设置的maxvertexcount过大,会直接吸收编写翻译错误:
manbetx网页手机登录版 5

下一场代码中的triangle是用来内定输入的图元类型,具体扶助的要害字如下:

图元类型 描述
point Point list
line Line list or line strip
triangle Triangle list or triangle strip
lineadj Line list with adjacency or line strip with adjacency
triangleadj Triangle list with adjacency or triangle strip with adjacency

实际的图元类型能够到第2章回看:点击这里

而参数类型能够是用户自定义的结构体类型,恐怕是向量(float4)类型。从巅峰着色器传过来的终端至少会包蕴三个意味着齐次裁剪坐标的向量。

参数名inupt实际上用户是足以率性钦赐的。

对于该输入参数的因素数目,取决于后边申明的图元类型:

图元类型 元素数目
point [1] 每次只能处理1个顶点
line [2] 一个线段必须包含2个顶点
triangle [3] 一个三角形需要3个顶点
lineadj [4] 一个邻接线段需要4个顶点
triangleadj [6] 一个邻接三角形需要6个顶点

而第二个参数必须是一个流输出指标,而且必要被钦命为inout可读写类型。可以看来,它是一个类模板,模板的形参钦定要出口的门类。流输出对象有如下三种:

流输出对象类型 描述
PointStream 一系列点的图元
LineStream 一系列线段的图元
TriangleStream 一系列三角形的图元

流输出对象都存有上面三种办法:

方法 描述
Append 向指定的流输出对象添加一个输出的数据
RestartStrip 在以线段或者三角形作为图元的时候,默认是以strip的形式输出的,如果我们不希望下一个输出的顶点与之前的顶点构成新图元,则需要调用此方法来重新开始新的strip。若希望输出的图元类型也保持和原来一样的TriangleList,则需要每调用3次Append方法后就调用一次RestartStrip。

注意:

  1. 所谓的删除顶点,实际上就是不将该终端传递给流输出对象
  2. 若传入的终点中剩下的一对不可能构成对应的图元,则甩掉掉那一个剩余的顶峰

在上马前,先放出Basic.fx文件的剧情:

#include "LightHelper.hlsli"

cbuffer CBChangesEveryFrame : register(b0)
{
    row_major matrix gWorld;
    row_major matrix gWorldInvTranspose;
}

cbuffer CBChangesOnResize : register(b1)
{
    row_major matrix gProj;
}

cbuffer CBNeverChange : register(b2)
{
    DirectionalLight gDirLight;
    Material gMaterial;
    row_major matrix gView;
    float3 gEyePosW;
    float gCylinderHeight;
}


struct VertexPosColor
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
};

struct VertexPosHColor
{
    float4 PosH : SV_POSITION;
    float4 Color : COLOR;
};


struct VertexPosNormalColor
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float4 Color : COLOR;
};

struct VertexPosHWNormalColor
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION;
    float3 NormalW : NORMAL;
    float4 Color : COLOR;
};

HLSL代码

下面是Basic.fx的总体代码:

// Basic.fx

#include "LightHelper.hlsli"

Texture2D tex : register(t0);
Texture2DArray texArray : register(t1);
SamplerState sam : register(s0);


cbuffer CBChangesEveryDrawing : register(b0)
{
    row_major matrix gWorld;
    row_major matrix gWorldInvTranspose;
    row_major matrix gTexTransform;
    Material gMaterial;
}

cbuffer CBChangesEveryFrame : register(b1)
{
    row_major matrix gView;
    float3 gEyePosW;
}

cbuffer CBDrawingStates : register(b2)
{
    float4 gFogColor;
    int gFogEnabled;
    float gFogStart;
    float gFogRange;
}

cbuffer CBChangesOnResize : register(b3)
{
    row_major matrix gProj;
}

cbuffer CBNeverChange : register(b4)
{
    DirectionalLight gDirLight[4];
}



struct Vertex3DIn
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float2 Tex : TEXCOORD;
};

struct Vertex3DOut
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION; // 在世界中的位置
    float3 NormalW : NORMAL; // 法向量在世界中的方向
    float2 Tex : TEXCOORD;
};

struct PointSprite
{
    float3 PosW : POSITION;
    float2 SizeW : SIZE;
};

struct BillboardVertex
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION;
    float3 NormalW : NORMAL;
    float2 Tex : TEXCOORD;
    uint PrimID : SV_PrimitiveID;
};

对此极端着色器,仅担任顶点的直传:

// Billboard_VS.hlsl

#include "Basic.fx"

PointSprite VS(PointSprite pIn)
{
    return pIn;
}

而几何着色器的代码如下:

// Billboard_GS.hlsl

#include "Basic.fx"

// 节省内存资源,先用float4向量声明。
static const float4 gVec[2] = { float4(0.0f, 1.0f, 0.0f, 0.0f), float4(1.0f, 1.0f, 1.0f, 0.0f) };
static const float2 gTex[4] = (float2[4])gVec;

[maxvertexcount(4)]
void GS(point PointSprite input[1], uint primID : SV_PrimitiveID, 
    inout TriangleStream<BillboardVertex> output)
{
    // 计算公告板所处的局部坐标系,其中公告板相当于
    // 被投影在了局部坐标系的xy平面,z轴

    float3 up = float3(0.0f, 1.0f, 0.0f);
    float3 look = gEyePosW - input[0].PosW;
    look.y = 0.0f;  // look向量只取投影到xz平面的向量
    look = normalize(look);
    float3 right = cross(up, look);

    // 计算出公告板矩形的四个顶点
    //            up
    //      v1 ___|___ v3
    //        |   |   |
    // right__|___|   |
    //        |  /    |
    //        |_/_____|
    //      v0 /       v2
    //       look  
    float4 v[4];
    float3 center = input[0].PosW;
    float halfWidth = 0.5f * input[0].SizeW.x;
    float halfHeight = 0.5f * input[0].SizeW.y;
    v[0] = float4(center + halfWidth * right - halfHeight * up, 1.0f);
    v[1] = float4(center + halfWidth * right + halfHeight * up, 1.0f);
    v[2] = float4(center - halfWidth * right - halfHeight * up, 1.0f);
    v[3] = float4(center - halfWidth * right + halfHeight * up, 1.0f);

    // 对顶点位置进行矩阵变换,并以TriangleStrip形式输出
    BillboardVertex gOut;
    row_major matrix viewProj = mul(gView, gProj);
    [unroll]
    for (int i = 0; i < 4; ++i)
    {
        gOut.PosW = v[i].xyz;
        gOut.PosH = mul(v[i], viewProj);
        gOut.NormalW = look;
        gOut.Tex = gTex[i];
        gOut.PrimID = primID;
        output.Append(gOut);
    }

}

率先一初步不用float2数组是因为每种float2成分会单独包装,浪费了大要上的上空,由此这里运用一种新鲜的语法方式使得内存能够取得充足利用。

接下来要小心maxvertexcount的值要设为4,就算Append的次数为4,但实际上输出的三角形顶点数为6。

实战1: 将一个三角形形分割成多个三角

未来大家的指标是把二个三角形差别成多个三角:
manbetx网页手机登录版 6

那也为未来完毕分形做为基础。建议读者能够先活动尝试编写着色器代码再来比较。在编辑好着色器代码后,
要给渲染管线绑定好一切所需的财富能力够看出功能。

图元ID

明日描述系统值SV_PrimitiveID,大家得以将它看成函数的额外形参进行提供。它报告大家在输入装配阶段下活动分配的图元ID值。当大家调用了三个draw方法,供给绘制n个图元,那么首先个图元对应的ID值为0,第二个为1,直到最终一个为n-1.当前的持有图元ID仅在当下的单次调用绘制是独一无二的。在这之中该系统值的写入操作允许在几何着色器和像素着色器举办,而读取操作则允许在几何/像素/外壳/域着色器中开始展览。

在上头的例证中,大家将三个巅峰发生的矩形多个极点都标识为同一个图元ID,是因为到三回九转的像素着色器中,我们用该图元ID映射到纹理数组的索引值,来对应到要绘制的树的纹路。

注意:
如若几何着色器未有提供图元ID,在像素着色器中也能够将它加进参数列表中以应用:

float4 PS(Vertex3DOut pin, uint primID : SV_PrimitiveID) : SV_Target
{
// Pixel shader body…
}

但假若像素着色器提供了图元ID,渲染管线又绑定了几何着色器,则几何着色器必须提供该参数。在几何着色器中您能够采用或更动图元ID值。

HLSL代码

Triangle_VS.hlsl, Triangle_GS.hlslTriangle_PS.hlsl的兑现如下:

// Triangle_VS.hlsl
#include "Basic.fx"

VertexPosHColor VS(VertexPosColor pIn)
{
    row_major matrix worldViewProj = mul(mul(gWorld, gView), gProj);
    VertexPosHColor pOut;
    pOut.Color = pIn.Color;
    pOut.PosH = mul(float4(pIn.PosL, 1.0f), worldViewProj);
    return pOut;
}

// Triangle_GS.hlsl
#include "Basic.fx"

[maxvertexcount(9)]
void GS(triangle VertexPosHColor input[3], inout TriangleStream<VertexPosHColor> output)
{
    //
    // 将一个三角形分裂成三个三角形,即没有v3v4v5的三角形
    //       v1
    //       /\
    //      /  \
    //   v3/____\v4
    //    /\xxxx/\
    //   /  \xx/  \
    //  /____\/____\
    // v0    v5    v2


    VertexPosHColor vertexes[6];
    int i;
    [unroll]
    for (i = 0; i < 3; ++i)
    {
        vertexes[i] = input[i];
        vertexes[i + 3].Color = (input[i].Color + input[(i + 1) % 3].Color) / 2.0f;
        vertexes[i + 3].PosH = (input[i].PosH + input[(i + 1) % 3].PosH) / 2.0f;
    }

    [unroll]
    for (i = 0; i < 3; ++i)
    {
        output.Append(vertexes[i]);
        output.Append(vertexes[3 + i]);
        output.Append(vertexes[(i + 2) % 3 + 3]);
        output.RestartStrip();

    }
}

// Triangle_PS.hlsl
#include "Basic.fx"

float4 PS(VertexPosHColor pIn) : SV_Target
{
    return pIn.Color;
}

那边输入和输出的图元类型都是一样的,但随便怎么着动静都必然要注意设置好maxvertexcount的值,这里一定二个三角的多少个顶点输出9个顶峰(构成多个三角),并且每3次Append就须要调用1次RestartStrip

顶点ID

接着是系统值SV_VertexID,在输入装配阶段的时候渲染管线就能够为那一个输入的终端分配顶点ID值。若选取的是Draw方法,则那一个极端将会按梯次从0到n-1被标志(n为顶点数目);若选拔的是DrawIndexed措施,则极端ID对应到的是该终端所处的索引值。该参数仅能在终点着色器的参数列表中提供:

VertexOut VS(VertexIn vin, uint vertID : SV_VertexID)
{
// vertex shader body…
}

最后给出像素着色器的代码:

// Billboard_PS.hlsl

#include "Basic.fx"

float4 PS(BillboardVertex pIn) : SV_Target
{
    // 每4棵树一个循环,尽量保证出现不同的树
    float4 texColor = texArray.Sample(sam, float3(pIn.Tex, pIn.PrimID % 4));
    // 提前进行裁剪,对不符合要求的像素可以避免后续运算
    clip(texColor.a - 0.05f);

    // 标准化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 求出顶点指向眼睛的向量,以及顶点与眼睛的距离
    float3 toEyeW = normalize(gEyePosW - pIn.PosW);
    float distToEye = distance(gEyePosW, pIn.PosW);

    // 初始化为0 
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);

    [unroll]
    for (int i = 0; i < 4; ++i)
    {
        ComputeDirectionalLight(gMaterial, gDirLight[i], pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }

    float4 litColor = texColor * (ambient + diffuse) + spec;

    // 雾效部分
    [flatten]
    if (gFogEnabled)
    {
        // 限定在0.0f到1.0f范围
        float fogLerp = saturate((distToEye - gFogStart) / gFogRange);
        // 根据雾色和光照颜色进行线性插值
        litColor = lerp(litColor, gFogColor, fogLerp);
    }

    litColor.a = texColor.a * gMaterial.Diffuse.a;
    return litColor;
}

此地充分了刚刚的雾效,并选取了纹路数组。

实战2: 通过圆线构造圆柱体左侧

已知图元类型为LineStrip,未来有一雨后春笋三翻五次的顶点构成圆线(近似圆弧的连接折线),构造出圆柱体的左边。即输入图元类型为线段,输出两个矩形(八个三角)。

manbetx网页手机登录版 7

思路:
光有极端地方还不足以构造出圆柱体左边,因为不可能显明圆柱往哪些方向延伸。所以大家还亟需对各类终端引进所在圆柱左侧包车型客车法向量,通过叉乘就足以规定上偏侧/下方向并开始展览延伸了。

纹理数组

此前在C++代码层中,大家的每一张纹理使用ID3D11Texture2D的接口对象去独立存款和储蓄。但实际上在我们创制ID3D11Texture2D对象的时候,我们得以设置它的ArraySize来钦赐该指标足以存放的纹路数目。

只是大家创制纹理并非利用D3DX多元的函数,因为大家根本就不采取DirectX
SDK。在前头大家创立纹理使用的是DDSTextureLoader.hWICTextureLoader.h中的函数。这里再提起一下,那五个头文件对应的库能够在上边四个路子找到:

DirectXTex

DirectXTK

回到HLSL代码,我们所以不应用上面的这种样式创制纹理数组:

Texture2D TexArray[4];

float4 PS(GeoOut pin) : SV_Target
{
    float4 c = TexArray[pin.PrimID%4].Sample(samLinear, pin.Tex);

是因为如此做的话HLSL编写翻译器会报错:sampler array index must be a literal
experssion,即pin.PrimID的值也亟须是个字面值,并不是变量。但大家照旧想要能够基于变量取对应纹理的手艺。

科学的做法应该是宣称叁个Texture2DArray的数组:

Texture2DArray texArray : register(t1);

此处运用的是索引为1的纹路寄放器是因为前边还应该有一个纹理已经绑定了t0.

HLSL代码

Cylinder_VS.hlsl, Cylinder_GS.hlslCylinder_PS.hlsl的实现如下:

// Cylinder_VS.hlsl
#include "Basic.fx"

VertexPosHWNormalColor VS(VertexPosNormalColor pIn)
{
    VertexPosHWNormalColor pOut;
    row_major matrix viewProj = mul(gView, gProj);
    pOut.PosW = mul(float4(pIn.PosL, 1.0f), gWorld).xyz;
    pOut.PosH = mul(float4(pOut.PosW, 1.0f), viewProj);
    pOut.NormalW = mul(pIn.NormalL, (float3x3)gWorldInvTranspose);
    pOut.Color = pIn.Color;
    return pOut;
}

// Cylinder_GS.hlsl
#include "Basic.fx"

// 一个v0v1线段输出6个三角形顶点
[maxvertexcount(6)]
void GS(line VertexPosHWNormalColor input[2], inout TriangleStream<VertexPosHWNormalColor> output)
{
    // *****************************
    // 要求圆线框是顺时针的,然后自底向上构造圆柱侧面           
    //   -->      v2____v3
    //  ______     |\   |
    // /      \    | \  |
    // \______/    |  \ |
    //   <--       |___\|
    //           v1(i1) v0(i0)

    float3 upDir = normalize(cross(input[0].NormalW, (input[1].PosW - input[0].PosW)));
    VertexPosHWNormalColor v2, v3;

    matrix viewProj = mul(gView, gProj);


    v2.PosW = input[1].PosW + upDir * gCylinderHeight;
    v2.PosH = mul(float4(v2.PosW, 1.0f), viewProj);
    v2.NormalW = input[1].NormalW;
    v2.Color = input[1].Color;

    v3.PosW = input[0].PosW + upDir * gCylinderHeight;
    v3.PosH = mul(float4(v3.PosW, 1.0f), viewProj);
    v3.NormalW = input[0].NormalW;
    v3.Color = input[0].Color;

    output.Append(input[0]);
    output.Append(input[1]);
    output.Append(v2);
    output.RestartStrip();

    output.Append(v2);
    output.Append(v3);
    output.Append(input[0]);
}

// Cylinder_PS.hlsl
#include "Basic.fx"

float4 PS(VertexPosHWNormalColor pIn) : SV_Target
{
    // 标准化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 顶点指向眼睛的向量
    float3 toEyeW = normalize(gEyePosW - pIn.PosW);

    // 初始化为0 
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);

    // 只计算方向光
    ComputeDirectionalLight(gMaterial, gDirLight, pIn.NormalW, toEyeW, ambient, diffuse, spec);

    return pIn.Color * (ambient + diffuse) + spec;
}

纹理数组的采集样品

Texture2DArray同样也具备Sample方法:

// 每4棵树一个循环,尽量保证出现不同的树
float4 texColor = texArray.Sample(sam, float3(pIn.Tex, pIn.PrimID % 4));

率先个参数照旧是采集样品器

而第3个参数则是二个3D向量,当中x与y的值对应的依然纹理坐标,而z分量即就是个float,重倘若用来作为索引值选用纹理数组中的某二个实际纹理。同理索引值0对应纹理数组的率先张纹理,1对应的是第二张纹理等等…

在大家的那一个demo中,纹理数组贮存了4张不一样体制的树的纹路贴图,然后用SV_Primitive模4的值来调节哪张树纹理贴图将被绘制。

manbetx网页手机登录版 8

行使纹理数组的优势是,大家得以一回性预先创建好全体须要选取的纹理,并绑定到HLSL的纹路数组中,而不须要每回都再一次绑定三个纹理。然后大家再使用索引值来寻访纹理数组中的某一纹理。

实战3: 画出极端的法向量

画出终极的法向量能够援助你进行调解,排查法向量是还是不是现身了难点。那时候图元的类别为PointList,需求通过几何着色器输出一个线条(两极分化)。由于终端中蕴藏法向量,剩下的正是要自行决定法向量的长短。

下图的法向量长度为0.5

manbetx网页手机登录版 9

纹理数组的加载

今昔我们手头上仅部分正是DDSTextureLoader.hWICTextureLoader.h中的函数,但那么些中的函数每回都只好加载一张纹理。大家还索要修改龙书样例中读取纹理的函数,具体的操作顺序如下:

  1. 叁个个读取存有纹理的文件,创设出一体系ID3D11Texture2D指标,这里的各类对象单独包涵一张纹理;
  2. 创办三个ID3D11Texture2D对象,它同期也是一个纹理数组;
  3. 将事先读取的享有纹理有系统地复制到刚创造的纹路数组对象中;
  4. 为该纹理数组对象创立创立二个纹理能源视图(Shader Resource View)。

首先大家要求了然巩固版的纹路成立函数。

HLSL代码

Normal_VS.hlsl, Normal_GS.hlslNormal_PS.hlsl的落到实处如下:

// Normal_VS.hlsl
#include "Basic.fx"

VertexPosHWNormalColor VS(VertexPosNormalColor pIn)
{
    VertexPosHWNormalColor pOut;
    row_major matrix viewProj = mul(gView, gProj);
    pOut.PosW = mul(float4(pIn.PosL, 1.0f), gWorld).xyz;
    pOut.PosH = mul(float4(pOut.PosW, 1.0f), viewProj);
    pOut.NormalW = mul(pIn.NormalL, (float3x3) gWorldInvTranspose);
    pOut.Color = pIn.Color;
    return pOut;
}

// Normal_GS.hlsl
#include "Basic.fx"

[maxvertexcount(2)]
void GS(point VertexPosHWNormalColor input[1], inout LineStream<VertexPosHWNormalColor> output)
{
    matrix viewProj = mul(gView, gProj);


    VertexPosHWNormalColor v;

    // 防止深度值资源争夺
    v.PosW = input[0].PosW + input[0].NormalW * 0.01f;
    v.NormalW = input[0].NormalW;
    v.PosH = mul(float4(v.PosW, 1.0f), viewProj);
    v.Color = input[0].Color;
    output.Append(v);

    v.PosW = v.PosW + input[0].NormalW * 0.5f;
    v.PosH = mul(float4(v.PosW, 1.0f), viewProj);

    output.Append(v);
}

// Normal_PS.hlsl
#include "Basic.fx"

float4 PS(VertexPosHWNormalColor pIn) : SV_TARGET
{
    return pIn.Color;
}

CreateDDSTextureFromFileEx函数–使用越多的参数,从文件中读取DDS纹理

HRESULT CreateDDSTextureFromFileEx(
    ID3D11Device* d3dDevice,                // [In]D3D设备
    ID3D11DeviceContext* d3dContext,        // [In]D3D设备上下文(可选)
    const wchar_t* szFileName,              // [In].dds文件名
    size_t maxsize,                         // [In]最大允许mipmap等级,默认0
    D3D11_USAGE usage,                      // [In]D3D11_USAGE枚举值类型,指定CPU/GPU读写权限
    unsigned int bindFlags,                 // [In]绑定标签,指定它可以被绑定到什么对象上
    unsigned int cpuAccessFlags,            // [In]CPU访问权限标签
    unsigned int miscFlags,                 // [In]杂项标签,忽略
    bool forceSRGB,                         // [In]强制使用SRGB,默认false
    ID3D11Resource** texture,               // [Out]获取创建好的纹理(可选)
    ID3D11ShaderResourceView** textureView, // [Out]获取创建好的纹理资源视图(可选)
    DDS_ALPHA_MODE* alphaMode = nullptr);   // [Out]忽略(可选)

也正是说,图片的多寡格式、宽度、高度等音讯都以随文件读取的时候获得的,大家鞭长莫及在那边钦点。所以我们供给提供的兼具DDS纹理宽度、中度、数据格式都应当平等。对于数据格式不一致等的,大家得以动用DirectX Texture Tool来修改,可是该程序满含在DirectX
SDK中,这里自身在Github上尝试提供单身的DxTex.exe先后看能否一直使用。以后本身事先确定保证德姆o中的4张树纹理都设置为同一的数额格式。

首先步,读取一雨后冬笋纹理的代码如下:

//
// 1. 读取所有纹理
//
size_t size = filenames.size();
std::vector<ComPtr<ID3D11Texture2D>> srcTex(size);
UINT mipLevel = maxMipMapSize;
UINT width, height;
DXGI_FORMAT format;
for (size_t i = 0; i < size; ++i)
{
    // 由于这些纹理并不会被GPU使用,我们使用D3D11_USAGE_STAGING枚举值
    // 使得CPU可以读取资源
    HR(CreateDDSTextureFromFileEx(device.Get(),
        deviceContext.Get(),
        filenames[i].c_str(),
        maxMipMapSize,
        D3D11_USAGE_STAGING,                            // Usage
        0,                                              // BindFlags
        D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ, // CpuAccessFlags
        0,                                              // MiscFlags
        false,
        (ID3D11Resource**)srcTex[i].GetAddressOf(),
        nullptr));

    // 读取创建好的纹理Mipmap等级, 宽度和高度
    D3D11_TEXTURE2D_DESC texDesc;
    srcTex[i]->GetDesc(&texDesc);
    if (i == 0)
    {
        mipLevel = texDesc.MipLevels;
        width = texDesc.Width;
        height = texDesc.Height;
        format = texDesc.Format;
    }
    // 这里断言所有纹理的MipMap等级,宽度和高度应当一致
    assert(mipLevel == texDesc.MipLevels);
    assert(texDesc.Width == width && texDesc.Height == height);
    // 这里要求所有提供的图片数据格式应当是一致的,若存在不一致的情况,请
    // 使用dxtex.exe(DirectX Texture Tool)将所有的图片转成一致的数据格式
    assert(texDesc.Format == format);

}

接下去的第二步便是创建纹理数组,大家选拔第一个纹理的陈说去填充纹理数组的一有的叙述:

//
// 2.创建纹理数组
//
D3D11_TEXTURE2D_DESC texDesc, texArrayDesc;
srcTex[0]->GetDesc(&texDesc);
texArrayDesc.Width = texDesc.Width;
texArrayDesc.Height = texDesc.Height;
texArrayDesc.MipLevels = texDesc.MipLevels;
texArrayDesc.ArraySize = size;
texArrayDesc.Format = texDesc.Format;
texArrayDesc.SampleDesc.Count = 1;
texArrayDesc.SampleDesc.Quality = 0;
texArrayDesc.Usage = D3D11_USAGE_DEFAULT;
texArrayDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
texArrayDesc.CPUAccessFlags = 0;
texArrayDesc.MiscFlags = 0;

ComPtr<ID3D11Texture2D> texArray;
HR(device->CreateTexture2D(&texArrayDesc, nullptr, texArray.GetAddressOf()));

在第三步举行理并答复制以前,大家还索要领悟纹理的子资源

C++代码的有个别变化

纹理子能源(Texture Subresources)

WICTextureLoader或者DDSTextureLoader读抽出来的纹路数据实际上实际不是由单独的贰个二维数组结合,而是两个例外尺寸的二维数组,不一致的mipmap品级对应区别的二维数组,这一个二维数组都以该纹理的子财富。比方512×512的纹路加载进来包罗的mipmap等第数(Mipmap
Levels)为10,富含了从512×512, 256×256,
128×128…到1×1的十个二维数组颜色数据,而Direct3D API使用Mip切成片(Mip
slice)来钦点某一mipmap级其他纹理,也许有一点像索引。比方mip
slice值为0时,对应的是512×512的纹理,而mip
slice值1对应的是256×256,就那样推算。

对此纹理数组,每一种元素就地点说的单个纹理对应的mipmap链,Direct3D
API使用数组切丝(array
slice)来做客不一样纹理,也是一定于索引.那样我们就足以把具备的纹路财富用下边包车型大巴图来代表,假定下图有4个纹理,各类纹理包括3个子能源,则当前点名的是Array
Slice为2,Mip Slice为1的子能源。

manbetx网页手机登录版 10

BasicFX.h的变化

常量缓冲区和BasicFX类的转移如下:

#ifndef BASICFX_H
#define BASICFX_H

#include <wrl/client.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <directxmath.h>
#include <vector>
#include "LightHelper.h"
#include "RenderStates.h"
#include "Vertex.h"

// 由于常量缓冲区的创建需要是16字节的倍数,该函数可以返回合适的字节大小
inline UINT Align16Bytes(UINT size)
{
    return (size + 15) & (UINT)(-16);
}

struct CBChangesEveryFrame
{
    DirectX::XMMATRIX world;
    DirectX::XMMATRIX worldInvTranspose;
};

struct CBChangesOnResize
{
    DirectX::XMMATRIX proj;
};

struct CBNeverChange
{
    DirectionalLight dirLight;
    Material material;
    DirectX::XMMATRIX view;
    DirectX::XMFLOAT3 eyePos;
    float cylinderHeight;
};

class BasicFX
{
public:
    // 使用模板别名(C++11)简化类型名
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    // 初始化Basix.fx所需资源并初始化光栅化状态
    bool InitAll(ComPtr<ID3D11Device> device);
    // 是否已经初始化
    bool IsInit() const;

    template <class T>
    void UpdateConstantBuffer(const T& cbuffer);

    // 绘制三角形分裂
    void SetRenderSplitedTriangle();
    // 绘制无上下盖的圆柱体
    void SetRenderCylinderNoCap();
    // 绘制所有顶点的法向量
    void SetRenderNormal();


private:
    // objFileNameInOut为编译好的着色器二进制文件(.*so),若有指定则优先寻找该文件并读取
    // hlslFileName为着色器代码,若未找到着色器二进制文件则编译着色器代码
    // 编译成功后,若指定了objFileNameInOut,则保存编译好的着色器二进制信息到该文件
    // ppBlobOut输出着色器二进制信息
    HRESULT CreateShaderFromFile(const WCHAR* objFileNameInOut, const WCHAR* hlslFileName, LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob** ppBlobOut);

private:
    ComPtr<ID3D11VertexShader> mTriangleVS;                 
    ComPtr<ID3D11PixelShader> mTrianglePS;                  
    ComPtr<ID3D11GeometryShader> mTriangleGS;               

    ComPtr<ID3D11VertexShader> mCylinderVS;                 
    ComPtr<ID3D11PixelShader> mCylinderPS;
    ComPtr<ID3D11GeometryShader> mCylinderGS;

    ComPtr<ID3D11VertexShader> mNormalVS;
    ComPtr<ID3D11PixelShader> mNormalPS;
    ComPtr<ID3D11GeometryShader> mNormalGS;

    ComPtr<ID3D11InputLayout> mVertexPosColorLayout;        // VertexPosColor输入布局
    ComPtr<ID3D11InputLayout> mVertexPosNormalColorLayout;  // VertexPosNormalColor输入布局

    ComPtr<ID3D11DeviceContext> md3dImmediateContext;       // 设备上下文

    std::vector<ComPtr<ID3D11Buffer>> mConstantBuffers;     // 常量缓冲区
};

#endif

D3D11CalcSubresource函数–计算子财富的索引值

对此纹理数组的每种子财富都足以用八个一维的索引值访问,索引值的增减是以Mip切成条值为主递增的。
manbetx网页手机登录版 11

然后给定当前纹理数组的mipmap品级数(Mipmap Levels),数组切丝(Array
Slice)和Mip切成丝(Mip
Slice),大家就能够用上面包车型大巴函数来求得钦定子财富的索引值:

inline UINT D3D11CalcSubresource(UINT MipSlice, UINT ArraySlice, UINT MipLevels )
{ return MipSlice + ArraySlice * MipLevels; }

然后是酷炫相关的多少个函数

BasicFX::InitAll方法

如今必要初步化一堆着色器、输入布局和常量缓冲区,并绑定常量缓冲区到暗中认可渲染管线:

bool BasicFX::InitAll(ComPtr<ID3D11Device> device)
{
    if (!device)
        return false;

    ComPtr<ID3DBlob> blob;

    // 创建顶点着色器和顶点布局
    HR(CreateShaderFromFile(L"HLSL\\Triangle_VS.vso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mTriangleVS.GetAddressOf()));
    HR(device->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
        blob->GetBufferPointer(), blob->GetBufferSize(), mVertexPosColorLayout.GetAddressOf()));

    HR(CreateShaderFromFile(L"HLSL\\Cylinder_VS.vso", L"HLSL\\Cylinder_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mCylinderVS.GetAddressOf()));
    HR(device->CreateInputLayout(VertexPosNormalColor::inputLayout, ARRAYSIZE(VertexPosNormalColor::inputLayout),
        blob->GetBufferPointer(), blob->GetBufferSize(), mVertexPosNormalColorLayout.GetAddressOf()));

    HR(CreateShaderFromFile(L"HLSL\\Normal_VS.vso", L"HLSL\\Normal_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mNormalVS.GetAddressOf()));

    // 创建像素着色器
    HR(CreateShaderFromFile(L"HLSL\\Triangle_PS.pso", L"HLSL\\Triangle_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mTrianglePS.GetAddressOf()));

    HR(CreateShaderFromFile(L"HLSL\\Cylinder_PS.pso", L"HLSL\\Cylinder_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mCylinderPS.GetAddressOf()));

    HR(CreateShaderFromFile(L"HLSL\\Normal_PS.pso", L"HLSL\\Normal_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mNormalPS.GetAddressOf()));

    // 创建几何着色器
    HR(CreateShaderFromFile(L"HLSL\\Triangle_GS.gso", L"HLSL\\Triangle_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mTriangleGS.GetAddressOf()));

    HR(CreateShaderFromFile(L"HLSL\\Cylinder_GS.gso", L"HLSL\\Cylinder_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mCylinderGS.GetAddressOf()));

    HR(CreateShaderFromFile(L"HLSL\\Normal_GS.gso", L"HLSL\\Normal_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mNormalGS.GetAddressOf()));


    RenderStates::InitAll(device);
    device->GetImmediateContext(md3dImmediateContext.GetAddressOf());

    // ******************
    // 设置常量缓冲区描述
    mConstantBuffers.assign(3, nullptr);
    D3D11_BUFFER_DESC cbd;
    ZeroMemory(&cbd, sizeof(cbd));
    cbd.Usage = D3D11_USAGE_DEFAULT;
    cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbd.CPUAccessFlags = 0;

    cbd.ByteWidth = Align16Bytes(sizeof(CBChangesEveryFrame));
    HR(device->CreateBuffer(&cbd, nullptr, mConstantBuffers[0].GetAddressOf()));
    cbd.ByteWidth = Align16Bytes(sizeof(CBChangesOnResize));
    HR(device->CreateBuffer(&cbd, nullptr, mConstantBuffers[1].GetAddressOf()));
    cbd.ByteWidth = Align16Bytes(sizeof(CBNeverChange));
    HR(device->CreateBuffer(&cbd, nullptr, mConstantBuffers[2].GetAddressOf()));

    // 预先绑定各自所需的缓冲区
    md3dImmediateContext->VSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
    md3dImmediateContext->VSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
    md3dImmediateContext->VSSetConstantBuffers(2, 1, mConstantBuffers[2].GetAddressOf());

    md3dImmediateContext->GSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
    md3dImmediateContext->GSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
    md3dImmediateContext->GSSetConstantBuffers(2, 1, mConstantBuffers[2].GetAddressOf());

    md3dImmediateContext->PSSetConstantBuffers(2, 1, mConstantBuffers[2].GetAddressOf());
    return true;
}

ID3D11DeviceContext::Map函数–获取指向子财富中数量的指针并驳回GPU对该子财富的拜会

HRESULT ID3D11DeviceContext::Map(
    ID3D11Resource           *pResource,          // [In]包含ID3D11Resource接口的资源对象
    UINT                     Subresource,         // [In]子资源索引
    D3D11_MAP                MapType,             // [In]D3D11_MAP枚举值,指定读写相关操作
    UINT                     MapFlags,            // [In]填0,忽略
    D3D11_MAPPED_SUBRESOURCE *pMappedResource     // [Out]获取到的已经映射到内存的子资源
);

D3D11_MAP枚举值类型的分子如下:

D3D11_MAP成员 含义
D3D11_MAP_READ 映射到内存的资源用于读取。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_READ标签
D3D11_MAP_WRITE 映射到内存的资源用于写入。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_WRITE标签
D3D11_MAP_READ_WRITE 映射到内存的资源用于读写。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_READ和D3D11_CPU_ACCESS_WRITE标签
D3D11_MAP_WRITE_DISCARD 映射到内存的资源用于写入,之前的资源数据将会被抛弃。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_WRITE和D3D11_USAGE_DYNAMIC标签
D3D11_MAP_WRITE_NO_OVERWRITE 映射到内存的资源用于写入,但不能复写已经存在的资源。该枚举值只能用于顶点/索引缓冲区。该资源在创建的时候需要有D3D11_CPU_ACCESS_WRITE标签,在Direct3D 11不能用于设置了D3D11_BIND_CONSTANT_BUFFER标签的资源,但在11.1后可以。具体可以查阅MSDN文档

获取到的结构体D3D11_MAPPED_SUBRESOURCE分子如下:

typedef struct D3D11_MAPPED_SUBRESOURCE {
    void *pData;
    UINT RowPitch;  
    UINT DepthPitch;
};

首先pData本着的是炫彩到内存上的子财富首成分地址,即对应Row和Depth
Slice值都为0的职位

其次RowPitch一般来讲是单排成分占用的字节数,对于512×512的纹路来讲,若它利用DXGI_R8G8B8A8_UNORM数据类型,则一行占用了512像素*4字节/像素=2048字节。它的种种子财富的RowPitch都是千篇一律的,那样方便其进展下一行跳转,固然在内部存储器上会有多少的荒废,在mipmap品级越高时也富有展现,但三个完完全全的mipmap链消耗的内部存款和储蓄器也就逼近原来单张纹理所占字节数的2倍而已。

DepthPitch在2D纹理的意思则是当前子财富占用的字节数。像刚刚说的那样,你在调节和测量试验器上观看比赛各种mipmap品级对应的DepthPitch值,能够窥见mip
slice扩充1,子财富占用的字节数是本来的三分之一,并不是原本的51%。那能够证实每行占用的字节数是不会转移的。

BasicFX::SetRenderSplitedTriangle方法–渲染差别的三角形

该方法管理的是图元TriangleList。因为延续的章程处理的图元分裂,在调用起初就得设置回准确的图元。也请保管道输送入装配阶段提供好内需分化的三角形形顶点。

void BasicFX::SetRenderSplitedTriangle()
{
    md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    md3dImmediateContext->IASetInputLayout(mVertexPosColorLayout.Get());
    md3dImmediateContext->VSSetShader(mTriangleVS.Get(), nullptr, 0);
    md3dImmediateContext->GSSetShader(mTriangleGS.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mTrianglePS.Get(), nullptr, 0);
}

ID3D11DeviceContext::UpdateSubresource函数[2]–将内部存款和储蓄器数据拷贝到不可举行映射的子财富中

本条函数在事先大家首借使用来将内存数据拷贝到常量缓冲区中,未来我们也能够用它将内部存款和储蓄器数据拷贝到纹理的子财富个中:

void ID3D11DeviceContext::UpdateSubresource(
  ID3D11Resource  *pDstResource,    // [In]目标资源对象
  UINT            DstSubresource,   // [In]对于2D纹理来说,该参数为指定Mipmap等级的子资源
  const D3D11_BOX *pDstBox,         // [In]这里填nullptr   
  const void      *pSrcData,        // [In]用于拷贝的内存数据
  UINT            SrcRowPitch,      // [In]该2D纹理的 宽度*数据格式的位数
  UINT            SrcDepthPitch     // [In]对于2D纹理来说并不需要用到该参数,因此可以任意设置
);

BasicFX::SetRenderCylinderNoCap方法–渲染圆柱侧边

该方法管理的是图元LineStrip,确认保证输入的一文山会海顶点和法向量能够在同一平面上。若提供的顶点集结按顺时针排布,则会自底向上营造出圆柱体,反之则是自顶向下塑造。

此处必要关闭背面裁剪,因为大家也能够看到圆柱侧边包车型大巴其中(未有盖子)。

void BasicFX::SetRenderCylinderNoCap()
{
    md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP);
    md3dImmediateContext->IASetInputLayout(mVertexPosNormalColorLayout.Get());
    md3dImmediateContext->VSSetShader(mCylinderVS.Get(), nullptr, 0);
    md3dImmediateContext->GSSetShader(mCylinderGS.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mCylinderPS.Get(), nullptr, 0);
}

ID3D11DeviceContext::UnMap函数–让指向资源的指针无效人己一视复启用GPU对该能源的拜见权限

void ID3D11DeviceContext::Unmap(
    ID3D11Resource *pResource,      // [In]包含ID3D11Resource接口的资源对象
    UINT           Subresource      // [In]需要取消的子资源索引
);

其三步的求实代码如下:

//
// 3.将所有的纹理子资源赋值到纹理数组中
//

// 每个纹理元素
for (size_t i = 0; i < size; ++i)
{
    // 纹理中的每个mipmap等级
    for (UINT j = 0; j < mipLevel; ++j)
    {
        D3D11_MAPPED_SUBRESOURCE mappedTex2D;
        // 允许映射索引i纹理中,索引j的mipmap等级的2D纹理
        HR(deviceContext->Map(srcTex[i].Get(),
            j, D3D11_MAP_READ, 0, &mappedTex2D));
        deviceContext->UpdateSubresource(
            texArray.Get(),
            D3D11CalcSubresource(j, i, mipLevel),   // i * mipLevel + j
            nullptr,
            mappedTex2D.pData,
            mappedTex2D.RowPitch,
            mappedTex2D.DepthPitch);
        // 停止映射
        deviceContext->Unmap(srcTex[i].Get(), j);
    }
}

末尾一步就是要创建着色器财富视图。

BasicFX::SetRenderNormal方法–渲染法向量

该措施管理的图元是PointList,确定保证输入的顶点要包罗法向量。

void BasicFX::SetRenderNormal()
{
    md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
    md3dImmediateContext->IASetInputLayout(mVertexPosNormalColorLayout.Get());
    md3dImmediateContext->VSSetShader(mNormalVS.Get(), nullptr, 0);
    md3dImmediateContext->GSSetShader(mNormalGS.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mNormalPS.Get(), nullptr, 0);
}

ID3D11Device::CreateShaderResourceView–创建着色器能源视图

HRESULT ID3D11Device::CreateShaderResourceView(
    ID3D11Resource                        *pResource,   // [In]待绑定资源
    const D3D11_SHADER_RESOURCE_VIEW_DESC *pDesc,       // [In]着色器资源视图描述
    ID3D11ShaderResourceView              **ppSRView    // [Out]获取创建的着色器资源视图
);

故而还必要填充D3D11_SHADER_RESOURCE_VIEW_DESC结构体:

typedef struct D3D11_SHADER_RESOURCE_VIEW_DESC
{
    DXGI_FORMAT Format;     // 数据格式
    D3D11_SRV_DIMENSION ViewDimension;  // 视图维度,决定下面需要填充哪个共用体成员
    union 
    {
        D3D11_BUFFER_SRV Buffer;
        D3D11_TEX1D_SRV Texture1D;
        D3D11_TEX1D_ARRAY_SRV Texture1DArray;
        D3D11_TEX2D_SRV Texture2D;
        D3D11_TEX2D_ARRAY_SRV Texture2DArray;
        D3D11_TEX2DMS_SRV Texture2DMS;
        D3D11_TEX2DMS_ARRAY_SRV Texture2DMSArray;
        D3D11_TEX3D_SRV Texture3D;
        D3D11_TEXCUBE_SRV TextureCube;
        D3D11_TEXCUBE_ARRAY_SRV TextureCubeArray;
        D3D11_BUFFEREX_SRV BufferEx;
    };
}   D3D11_SHADER_RESOURCE_VIEW_DESC;

终极一步的代码如下:

//
// 4.创建纹理数组的SRV
//
D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc;
viewDesc.Format = texArrayDesc.Format;
viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
viewDesc.Texture2DArray.MostDetailedMip = 0;
viewDesc.Texture2DArray.MipLevels = texArrayDesc.MipLevels;
viewDesc.Texture2DArray.FirstArraySlice = 0;
viewDesc.Texture2DArray.ArraySize = size;

ComPtr<ID3D11ShaderResourceView> texArraySRV;
HR(device->CreateShaderResourceView(texArray.Get(), &viewDesc, texArraySRV.GetAddressOf()));

// 已经确保所有资源由ComPtr管理,无需手动释放

return texArraySRV;

GameApp类的成形

该品种涵盖上边三种实战内容,要求用户去钦定当前播放的形式。

首先表明部分变化如下:

class GameApp : public D3DApp
{
public:
    enum class Mode { SplitedTriangle, CylinderNoCap, CylinderNoCapWithNormal };

public:
    GameApp(HINSTANCE hInstance);
    ~GameApp();

    bool Init();
    void OnResize();
    void UpdateScene(float dt);
    void DrawScene();

private:
    bool InitResource();

    void ResetTriangle();
    void ResetRoundWire();



private:

    ComPtr<ID2D1SolidColorBrush> mColorBrush;               // 单色笔刷
    ComPtr<IDWriteFont> mFont;                              // 字体
    ComPtr<IDWriteTextFormat> mTextFormat;                  // 文本格式

    ComPtr<ID3D11Buffer> mVertexBuffer;                     // 顶点集合
    int mVertexCount;                                       // 顶点数目
    Mode mShowMode;                                         // 当前显示模式

    BasicFX mBasicFX;                                       // Basic特效管理类

    CBChangesEveryFrame mCBChangeEveryFrame;                // 该缓冲区存放每帧更新的变量
    CBChangesOnResize mCBOnReSize;                          // 该缓冲区存放仅在窗口大小变化时更新的变量
    CBNeverChange mCBNeverChange;                           // 该缓冲区存放不会再进行修改的变量
};

CreateDDSTexture2DArrayShaderResourceView函数–创制用于DDS纹理的数组着色器能源视图

该函数放到了GameApp类中,你也能够独自抽离出来。

ComPtr<ID3D11ShaderResourceView> CreateDDSTexture2DArrayShaderResourceView(
    ComPtr<ID3D11Device> device,                    // [In]D3D设备
    ComPtr<ID3D11DeviceContext> deviceContext,      // [In]D3D设备上下文
    const std::vector<std::wstring>& filenames,     // [In]文件名数组
    int maxMipMapSize);    // [In]最大允许mipmap等级,若为0,则使用默认纹理mipmap等级

切切实实的函数完成就是上边四步的保有代码。

GameApp::ResetTriangle方法–重设为三角形顶点

void GameApp::ResetTriangle()
{
    // ******************
    // 初始化三角形
    // 设置三角形顶点
    VertexPosColor vertices[] =
    {
        { XMFLOAT3(-1.0f * 3, -0.866f * 3, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
        { XMFLOAT3(0.0f * 3, 0.866f * 3, 0.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT3(1.0f * 3, -0.866f * 3, 0.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) }
    };
    // 设置顶点缓冲区描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_DEFAULT;
    vbd.ByteWidth = sizeof vertices;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    // 新建顶点缓冲区
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices;
    HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf()));
    // 三角形顶点数
    mVertexCount = 3;
}

Alpha-To-Coverage

在Demo运维的时候,留神观看能够窥见树公告板的少数边缘部分有点相比较非凡的黑边。

那是因为脚下默许使用的是Alpha
Test,即HLSL中选拔clip函数将Alpha值为0的像素点给删除掉,这一个像素亦非树的一局部。该函数决定某一像素是留给依旧甩掉,那会导致不平整的过渡现象,在摄像机渐渐左近该纹理时,图片本人也在持续拓宽,硬边部分也会被放大,就如上边那张图:

manbetx网页手机登录版 12

自然,你也足以利用透明混合的秘技,不过透明混合对绘制的一一是有供给的,须求透明物体按从后到前的逐一实行绘图,即需要在绘制透明物体前先对实体按到壁画机的离开排个序。当然假若急需绘制大批量的草丛的话,这种艺术所须要的开销会变得万分大,操作起来也非常劳神。

自然,我们得以虚拟下行使MSAA(多种采集样品抗锯齿),并合营Alpha
Test实行。MSAA能够用于将多边形的锯齿边缘平滑管理,然后让Direct3D开启阿尔法-to-coverage技能,标志边缘部分。

率先在成立后备缓冲区、深度/模板缓冲区的时候要求开拓4倍多重采样的支撑,大家只供给在GameApp的构造函数中如此写就能够:

GameApp::GameApp(HINSTANCE hInstance)
    : D3DApp(hInstance)
{
    // 开启4倍多重采样
    mEnable4xMsaa = true;
}

下一场在前头的例子里,大家曾经在RenderStates类中先行创立好了交集意况:

D3D11_BLEND_DESC blendDesc;
ZeroMemory(&blendDesc, sizeof(blendDesc));
auto& rtDesc = blendDesc.RenderTarget[0];
// Alpha-To-Coverage模式
blendDesc.AlphaToCoverageEnable = true;
blendDesc.IndependentBlendEnable = false;
rtDesc.BlendEnable = false;
rtDesc.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
HR(device->CreateBlendState(&blendDesc, BSAlphaToCoverage.ReleaseAndGetAddressOf()));

下一场只需求在须求的时候绑定该情形就能够。

GameApp::ResetRoundWire方法–重设为圆线顶点

void GameApp::ResetRoundWire()
{
    // ******************
    // 初始化圆线
    // 设置圆边上各顶点
    // 必须要按顺时针设置
    // 由于要形成闭环,起始点需要使用2次
    //  ______
    // /      \
    // \______/
    //
    VertexPosNormalColor vertices[41];
    for (int i = 0; i < 40; ++i)
    {
        vertices[i].pos = XMFLOAT3(cosf(XM_PI / 20 * i), -1.0f, -sinf(XM_PI / 20 * i));
        vertices[i].normal = XMFLOAT3(cosf(XM_PI / 20 * i), 0.0f, -sinf(XM_PI / 20 * i));
        vertices[i].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    }
    vertices[40] = vertices[0];

    // 设置顶点缓冲区描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_DEFAULT;
    vbd.ByteWidth = sizeof vertices;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    // 新建顶点缓冲区
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices;
    HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf()));
    // 线框顶点数
    mVertexCount = 41;
}

BasicFX.h的变化

常量缓冲区对应的结构体和BasicFX类的变型如下:

#ifndef BASICFX_H
#define BASICFX_H

#include <wrl/client.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <directxmath.h>
#include <vector>
#include "LightHelper.h"
#include "RenderStates.h"
#include "Vertex.h"

// 由于常量缓冲区的创建需要是16字节的倍数,该函数可以返回合适的字节大小
inline UINT Align16Bytes(UINT size)
{
    return (size + 15) & (UINT)(-16);
}

struct CBChangesEveryDrawing
{
    DirectX::XMMATRIX world;
    DirectX::XMMATRIX worldInvTranspose;
    DirectX::XMMATRIX texTransform;
    Material material;
};

struct CBChangesEveryFrame
{
    DirectX::XMMATRIX view;
    DirectX::XMFLOAT4 eyePos;
};

struct CBDrawingStates
{
    DirectX::XMFLOAT4 fogColor;
    int fogEnabled;
    float fogStart;
    float fogRange;
    float pad;
};

struct CBChangesOnResize
{
    DirectX::XMMATRIX proj;
};

struct CBNeverChange
{
    DirectionalLight dirLight[4];
};

class BasicFX
{
public:
    // 使用模板别名(C++11)简化类型名
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    // 初始化Basix.fx所需资源并初始化光栅化状态
    bool InitAll(ComPtr<ID3D11Device> device);
    // 是否已经初始化
    bool IsInit() const;

    template <class T>
    void UpdateConstantBuffer(const T& cbuffer);

    // 默认状态绘制
    void SetRenderDefault();

    // 公告板绘制
    void SetRenderBillboard(bool enableAlphaToCoverage);

private:
    // objFileNameInOut为编译好的着色器二进制文件(.*so),若有指定则优先寻找该文件并读取
    // hlslFileName为着色器代码,若未找到着色器二进制文件则编译着色器代码
    // 编译成功后,若指定了objFileNameInOut,则保存编译好的着色器二进制信息到该文件
    // ppBlobOut输出着色器二进制信息
    HRESULT CreateShaderFromFile(const WCHAR* objFileNameInOut, const WCHAR* hlslFileName, LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob** ppBlobOut);

private:
    ComPtr<ID3D11VertexShader> mBasicVS;
    ComPtr<ID3D11PixelShader> mBasicPS;

    ComPtr<ID3D11VertexShader> mBillboardVS;
    ComPtr<ID3D11GeometryShader> mBillboardGS;
    ComPtr<ID3D11PixelShader> mBillboardPS;


    ComPtr<ID3D11InputLayout> mVertexPosSizeLayout;         // 点精灵输入布局
    ComPtr<ID3D11InputLayout> mVertexPosNormalTexLayout;    // 3D顶点输入布局

    ComPtr<ID3D11DeviceContext> md3dImmediateContext;       // 设备上下文

    std::vector<ComPtr<ID3D11Buffer>> mConstantBuffers;     // 常量缓冲区
};

#endif

开端化函数和SetRenderDeafult措施这里就不赘述了。

GameApp::UpdateScene方法变化

老是切换要求记得复位极端缓冲区,重新将顶点集绑定到输入,同仁一视设渲染类型。

void GameApp::UpdateScene(float dt)
{

    // 更新鼠标事件,获取相对偏移量
    Mouse::State mouseState = mMouse->GetState();
    Mouse::State lastMouseState = mMouseTracker.GetLastState();
    mMouseTracker.Update(mouseState);

    Keyboard::State keyState = mKeyboard->GetState();
    mKeyboardTracker.Update(keyState);

    // 更新每帧变化的值
    if (mShowMode == Mode::SplitedTriangle)
    {
        mCBChangeEveryFrame.worldInvTranspose = mCBChangeEveryFrame.world = XMMatrixIdentity();
    }
    else
    {
        static float phi = 0.0f, theta = 0.0f;
        phi += 0.2f * dt, theta += 0.3f * dt;
        mCBChangeEveryFrame.world = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);
        mCBChangeEveryFrame.worldInvTranspose = XMMatrixTranspose(XMMatrixInverse(nullptr, mCBChangeEveryFrame.world));
    }
    mBasicFX.UpdateConstantBuffer(mCBChangeEveryFrame);


    // 切换显示模式
    if (mKeyboardTracker.IsKeyPressed(Keyboard::D1))
    {
        mShowMode = Mode::SplitedTriangle;
        ResetTriangle();
        // 输入装配阶段的顶点缓冲区设置
        UINT stride = sizeof(VertexPosColor);       // 跨越字节数
        UINT offset = 0;                            // 起始偏移量
        md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset);
        mBasicFX.SetRenderSplitedTriangle();
    }
    else if (mKeyboardTracker.IsKeyPressed(Keyboard::D2))
    {
        mShowMode = Mode::CylinderNoCap;
        ResetRoundWire();
        // 输入装配阶段的顶点缓冲区设置
        UINT stride = sizeof(VertexPosNormalColor);     // 跨越字节数
        UINT offset = 0;                                // 起始偏移量
        md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset);
        mBasicFX.SetRenderCylinderNoCap();
    }

    // 显示法向量
    if (mKeyboardTracker.IsKeyPressed(Keyboard::Q))
    {
        if (mShowMode == Mode::CylinderNoCap)
            mShowMode = Mode::CylinderNoCapWithNormal;
        else if (mShowMode == Mode::CylinderNoCapWithNormal)
            mShowMode = Mode::CylinderNoCap;
    }

}

BasicFX::SetRenderBillboard方法–布告板绘制

福寿康宁如下:

void BasicFX::SetRenderBillboard(bool enableAlphaToCoverage)
{
    md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
    md3dImmediateContext->IASetInputLayout(mVertexPosSizeLayout.Get());
    md3dImmediateContext->VSSetShader(mBillboardVS.Get(), nullptr, 0);
    md3dImmediateContext->GSSetShader(mBillboardGS.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mBillboardPS.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(
        (enableAlphaToCoverage ? RenderStates::BSAlphaToCoverage.Get() : nullptr),
        nullptr, 0xFFFFFFFF);
}

参数enableAlphaToCoverage调控是还是不是要绑定渲染状态对象RenderStates::BSAlphaToCoverage

GameApp::DrawScene的变化

void GameApp::DrawScene()
{
    assert(md3dImmediateContext);
    assert(mSwapChain);

    md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
    md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

    md3dImmediateContext->Draw(mVertexCount, 0);
    // 绘制法向量,绘制完后记得归位
    if (mShowMode == Mode::CylinderNoCapWithNormal)
    {
        mBasicFX.SetRenderNormal();
        md3dImmediateContext->Draw(mVertexCount, 0);
        mBasicFX.SetRenderCylinderNoCap();
    }


    //
    // 绘制Direct2D部分
    //
    md2dRenderTarget->BeginDraw();
    std::wstring text = L"切换类型:1-分裂的三角形 2-圆线构造柱面\n"
        "当前模式: ";
    if (mShowMode == Mode::SplitedTriangle)
        text += L"分裂的三角形";
    else if (mShowMode == Mode::CylinderNoCap)
        text += L"圆线构造柱面(Q-显示圆线的法向量)";
    else
        text += L"圆线构造柱面(Q-隐藏圆线的法向量)";
    md2dRenderTarget->DrawTextW(text.c_str(), (UINT32)text.length(), mTextFormat.Get(),
        D2D1_RECT_F{ 0.0f, 0.0f, 600.0f, 200.0f }, mColorBrush.Get());
    HR(md2dRenderTarget->EndDraw());

    HR(mSwapChain->Present(0, 0));
}

末段效果如下:
manbetx网页手机登录版 13

DirectX11 With Windows
SDK完整目录

Github项目源码

GameApp类的变型

类成员相关注明如下:

class GameApp : public D3DApp
{
public:
    // 摄像机模式
    enum class CameraMode { FirstPerson, ThirdPerson, Free };

public:
    GameApp(HINSTANCE hInstance);
    ~GameApp();

    bool Init();
    void OnResize();
    void UpdateScene(float dt);
    void DrawScene();

private:
    bool InitResource();
    void InitPointSpritesBuffer();

    // 根据给定的DDS纹理文件集合,创建2D纹理数组
    // 要求所有纹理的宽度和高度都一致
    // 若maxMipMapSize为0,使用默认mipmap等级
    // 否则,mipmap等级将不会超过maxMipMapSize
    ComPtr<ID3D11ShaderResourceView> CreateDDSTexture2DArrayShaderResourceView(
        ComPtr<ID3D11Device> device,
        ComPtr<ID3D11DeviceContext> deviceContext,
        const std::vector<std::wstring>& filenames,
        int maxMipMapSize = 0);


private:

    ComPtr<ID2D1SolidColorBrush> mColorBrush;               // 单色笔刷
    ComPtr<IDWriteFont> mFont;                              // 字体
    ComPtr<IDWriteTextFormat> mTextFormat;                  // 文本格式

    ComPtr<ID3D11Buffer> mPointSpritesBuffer;               // 点精灵顶点缓冲区
    ComPtr<ID3D11ShaderResourceView> mTreeTexArray;         // 树的纹理数组
    Material mTreeMat;                                      // 树的材质

    GameObject mGround;                                     // 地面

    BasicFX mBasicFX;                                       // Basic特效管理类

    CameraMode mCameraMode;                                 // 摄像机模式
    std::shared_ptr<Camera> mCamera;                        // 摄像机

    bool mIsNight;                                          // 是否黑夜
    bool mEnableAlphaToCoverage;                            // 是否开启Alpha-To-Coverage

    CBChangesEveryDrawing mCBChangesEveryDrawing;           // 该缓冲区存放每次绘制更新的变量
    CBChangesEveryFrame mCBChangesEveryFrame;               // 该缓冲区存放每帧更新的变量
    CBDrawingStates mCBDrawingStates;                       // 该缓冲区存放绘制状态
    CBChangesOnResize mCBChangesOnReSize;                   // 该缓冲区存放仅在窗口大小变化时更新的变量
    CBNeverChange mCBNeverChange;                           // 该缓冲区存放不会再进行修改的变量
};

GameApp::InitPointCoca ColasBuffer方法–开首化存放点Smart的缓冲区

该方法会生成拾七个终端,均匀并略带随机性地围绕在原点周边。那些极端一经创培育不可以被改换了,它们将会被用来布告板的创导:

void GameApp::InitPointSpritesBuffer()
{
    srand((unsigned)time(nullptr));
    VertexPosSize vertexes[16];
    float theta = 0.0f;
    for (int i = 0; i < 16; ++i)
    {
        // 取20-50的半径放置随机的树
        float radius = (float)(rand() % 31 + 20);
        float randomRad = rand() % 256 / 256.0f * XM_2PI / 16;
        vertexes[i].pos = XMFLOAT3(radius * cosf(theta + randomRad), 8.0f, radius * sinf(theta + randomRad));
        vertexes[i].size = XMFLOAT2(30.0f, 30.0f);
        theta += XM_2PI / 16;
    }

    // 设置顶点缓冲区描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_IMMUTABLE;  // 数据不可修改
    vbd.ByteWidth = sizeof (vertexes);
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    // 新建顶点缓冲区
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertexes;
    HR(md3dDevice->CreateBuffer(&vbd, &InitData, mPointSpritesBuffer.GetAddressOf()));
}

GameApp::InitResource方法–早先化能源

该措施集成了颇具能源的开头化,注意树的纹路数组要提供到输入槽1,对应纹理置放器t1的Texture2DArray

bool GameApp::InitResource()
{
    // 默认白天,开启AlphaToCoverage
    mIsNight = false;
    mEnableAlphaToCoverage = true;
    // ******************
    // 初始化各种物体

    // 初始化树纹理资源
    mTreeTexArray = CreateDDSTexture2DArrayShaderResourceView(
        md3dDevice,
        md3dImmediateContext,
        std::vector<std::wstring>{
        L"Texture\\tree0.dds",
            L"Texture\\tree1.dds",
            L"Texture\\tree2.dds",
            L"Texture\\tree3.dds"});

    // 初始化点精灵缓冲区
    InitPointSpritesBuffer();

    // 初始化树的材质
    mTreeMat.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    mTreeMat.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    mTreeMat.Specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 16.0f);

    ComPtr<ID3D11ShaderResourceView> texture;
    // 初始化地板
    mGround.SetBuffer(md3dDevice, Geometry::CreatePlane(XMFLOAT3(0.0f, -5.0f, 0.0f), XMFLOAT2(100.0f, 100.0f), XMFLOAT2(10.0f, 10.0f)));
    HR(CreateDDSTextureFromFile(md3dDevice.Get(), L"Texture\\Grass.dds", nullptr, texture.GetAddressOf()));
    mGround.SetTexture(texture);
    Material material;
    material.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    material.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    material.Specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 16.0f);
    mGround.SetMaterial(material);
    mGround.SetWorldMatrix(XMMatrixIdentity());
    mGround.SetTexTransformMatrix(XMMatrixIdentity());

    // ******************
    // 初始化常量缓冲区的值

    mCBChangesEveryDrawing.material = mTreeMat;
    mCBChangesEveryDrawing.world = mCBChangesEveryDrawing.worldInvTranspose = XMMatrixIdentity();
    mCBChangesEveryDrawing.texTransform = XMMatrixIdentity();


    // 方向光
    mCBNeverChange.dirLight[0].Ambient = XMFLOAT4(0.1f, 0.1f, 0.1f, 1.0f);
    mCBNeverChange.dirLight[0].Diffuse = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);
    mCBNeverChange.dirLight[0].Specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 1.0f);
    mCBNeverChange.dirLight[0].Direction = XMFLOAT3(-0.577f, -0.577f, 0.577f);
    mCBNeverChange.dirLight[1] = mCBNeverChange.dirLight[0];
    mCBNeverChange.dirLight[1].Direction = XMFLOAT3(0.577f, -0.577f, 0.577f);
    mCBNeverChange.dirLight[2] = mCBNeverChange.dirLight[0];
    mCBNeverChange.dirLight[2].Direction = XMFLOAT3(0.577f, -0.577f, -0.577f);
    mCBNeverChange.dirLight[3] = mCBNeverChange.dirLight[0];
    mCBNeverChange.dirLight[3].Direction = XMFLOAT3(-0.577f, -0.577f, -0.577f);

    // 摄像机相关
    mCameraMode = CameraMode::Free;
    auto camera = std::shared_ptr<FirstPersonCamera>(new FirstPersonCamera);
    mCamera = camera;
    camera->SetPosition(XMFLOAT3());
    camera->SetFrustum(XM_PI / 3, AspectRatio(), 1.0f, 1000.0f);
    camera->LookTo(
        XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f),
        XMVectorSet(0.0f, 0.0f, 1.0f, 1.0f),
        XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
    camera->UpdateViewMatrix();


    mCBChangesEveryFrame.view = camera->GetView();
    XMStoreFloat4(&mCBChangesEveryFrame.eyePos, camera->GetPositionXM());

    mCBChangesOnReSize.proj = camera->GetProj();

    // 雾状态默认开启
    mCBDrawingStates.fogEnabled = 1;
    mCBDrawingStates.fogColor = XMFLOAT4(0.75f, 0.75f, 0.75f, 1.0f);    // 银色
    mCBDrawingStates.fogRange = 75.0f;
    mCBDrawingStates.fogStart = 15.0f;
    // 更新常量缓冲区资源
    mBasicFX.UpdateConstantBuffer(mCBChangesEveryDrawing);
    mBasicFX.UpdateConstantBuffer(mCBChangesEveryFrame);
    mBasicFX.UpdateConstantBuffer(mCBChangesOnReSize);
    mBasicFX.UpdateConstantBuffer(mCBDrawingStates);
    mBasicFX.UpdateConstantBuffer(mCBNeverChange);

    // 直接绑定树的纹理
    md3dImmediateContext->PSSetShaderResources(1, 1, mTreeTexArray.GetAddressOf());

    return true;
}

其他方法限于篇幅就不放在这里了,读者能够查看源码观望剩余部分的代码达成。今后来看落到实处效果与利益呢。

贯彻效果与利益

能够考查到,在与文告版中距离接触时能够很醒目地看看通告板在随之摄像机旋转。假诺距离相当的远的话转动的宽窄就能非常的小,用户才会比较难以辨认出远处物体是或不是为布告板或3D模型了。
manbetx网页手机登录版 14

上面演示了白天和黑夜的雾效
manbetx网页手机登录版 15

末尾则是Alpha-To-Coverage的拉开/关闭效果相比较
manbetx网页手机登录版 16

DirectX11 With Windows
SDK完整目录

Github项目源码

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图