欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

DirectX11 With Windows SDK--11 混合状态与光栅化状态

程序员文章站 2023-01-12 13:12:55
前言 DirectX11 With Windows SDK完整目录: "http://www.cnblogs.com/X Jun/p/9028764.html" 虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下三种: 1. 光栅化状态(光 ......

前言

DirectX11 With Windows SDK完整目录:

虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下三种:

  1. 光栅化状态(光栅化阶段)
  2. 混合状态(输出合并阶段)
  3. 深度/模板状态(输出合并阶段)

Direct3D是基于状态机的,我们可以通过修改这些状态来修改渲染管线的当前行为。

实际上这一章会讲述光栅化状态和混合状态这两个部分,在后续的章节会主要讲述深度/模板状态

项目源码点此:

混合等式

对于两个相同位置的像素点,规定\(C_{src}\)为源像素的颜色(从像素着色器输出的像素),\(C_{dst}\)为目标像素的颜色(已经存在于后备缓冲区上的像素)。在Direct3D中使用下面的混合等式来将源像素色和目标像素色进行混合:

\[ \mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\]

其中\(\otimes\)运算符为分量乘法,即\(\mathbf{C}_{src} \otimes \mathbf{F}_{src}\) 实际上得到的是\((R_{src}*R_{dst}, G_{src}*G_{dst}, B_{src}*B_{dst})\)

\(\mathbf{F}_{src}\)\(\mathbf{F}_{dst}\)的值,以及运算符 \(\boxplus\) 的具体含义都需要在程序中进行指定。

对于Alpha通道的值,运算公式和上面的类似,并且两个等式的运算是分开进行的:

\[ A = A_{src} * F_{src} \boxplus A_{dst} * F_{dst}\]

同理该运算符 \(\boxplus\) 的含义也需要另外进行设置。

混合状态

混合运算符的设置

对于运算符 \(\boxplus\) 的含义,可以使用下面的枚举类型D3D11_BLEND_OP来描述:

枚举值 含义
D3D11_BLEND_OP_ADD = 1 \(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} + \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)\(A = A_{src} * F_{src} + A_{dst} * F_{dst}\)
D3D11_BLEND_OP_SUBTRACT = 2 \(\mathbf{C} = \mathbf{C}_{dst} \otimes \mathbf{F}_{dst} - \mathbf{C}_{src} \otimes \mathbf{F}_{src}\)\(A = A_{dst} * F_{dst} - A_{src} * F_{src}\)
D3D11_BLEND_OP_REV_SUBTRACT = 3 \(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} - \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)\(A = A_{src} * F_{src} - A_{dst} * F_{dst}\)
D3D11_BLEND_OP_MIN = 4 \(\mathbf{C} = min(\mathbf{C}_{src}, \mathbf{C}_{dst})\)\(A = min(A_{src}, A_{dst})\)
D3D11_BLEND_OP_MAX = 5 \(\mathbf{C} = max(\mathbf{C}_{src}, \mathbf{C}_{dst})\)\(A = max(A_{src}, A_{dst})\)

再次提醒,你可以分开指定运算颜色和Alpha通道的运算符。

混合因子的设置

对于混合公式,我们可以按需要设置混合因子。混合因子使用枚举类型D3D11_BLEND类型进行描述:

枚举值 含义
D3D11_BLEND_ZERO = 1 \(\mathbf{F}=(0,0,0)\)\(F=0\)
D3D11_BLEND_ONE = 2 \(\mathbf{F}=(1,1,1)\)\(F=1\)
D3D11_BLEND_SRC_COLOR = 3 \(\mathbf{F}=(r_{src},g_{src},b_{src})\)
D3D11_BLEND_INV_SRC_COLOR = 4 \(\mathbf{F}=(1-r_{src},1-g_{src},1-b_{src})\)
D3D11_BLEND_SRC_ALPHA = 5 \(\mathbf{F}=(a_{src},a_{src},a_{src})\)\(F=a_{src}\)
D3D11_BLEND_INV_SRC_ALPHA = 6 \(\mathbf{F}=(1-a_{src},1-a_{src},1-a_{src})\)\(F=1-a_{src}\)
D3D11_BLEND_DEST_ALPHA = 7 \(\mathbf{F}=(a_{dst},a_{dst},a_{dst})\)\(F=a_{dst}\)
D3D11_BLEND_INV_DEST_ALPHA = 8 \(\mathbf{F}=(1-a_{dst},1-a_{dst},1-a_{dst})\)\(F=1-a_{dst}\)
D3D11_BLEND_DEST_COLOR = 9 \(\mathbf{F}=(r_{dst},g_{dst},b_{dst})\)
D3D11_BLEND_INV_DEST_COLOR = 10 \(\mathbf{F}=(1-r_{dst},1-g_{dst},1-b_{dst})\)
D3D11_BLEND_SRC_ALPHA_SAT = 11 \(\mathbf{F}=(sat(a_{src}),sat(a_{src}),sat(a_{src}))\)\(F=sat(a_{src})\)
D3D11_BLEND_BLEND_FACTOR = 14 \(\mathbf{F}\) 的值来自于ID3D11DeviceContext::OMSetBlendState方法的BlendFactor参数
D3D11_BLEND_INV_BLEND_FACTOR = 15 \(\mathbf{F}\) 的值来自于ID3D11DeviceContext::OMSetBlendState方法的BlendFactor参数,并设为1 - BlendFactor

其中sat函数将值限定在[0.0, 1.0]之间。

ID3D11Device::CreateBlendState方法--创建混合状态

在创建混合状态前,需要填充D3D11_BLEND_DESC结构体:

typedef struct D3D11_BLEND_DESC
{
    BOOL AlphaToCoverageEnable;    // 默认关闭,这里
    BOOL IndependentBlendEnable;   // 是否每个渲染目标都有独立的混合混合描述,关闭的话都使用索引为0的描述信息
    D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[ 8 ];
}   D3D11_BLEND_DESC;

typedef struct D3D11_RENDER_TARGET_BLEND_DESC
{
    BOOL BlendEnable;             // 是否开启混合
    D3D11_BLEND SrcBlend;         // 源颜色混合因子
    D3D11_BLEND DestBlend;        // 目标颜色混合因子
    D3D11_BLEND_OP BlendOp;       // 颜色混合运算符
    D3D11_BLEND SrcBlendAlpha;    // 源Alpha混合因子
    D3D11_BLEND DestBlendAlpha;   // 目标Alpha混合因子
    D3D11_BLEND_OP BlendOpAlpha;  // Alpha混合运算符
    UINT8 RenderTargetWriteMask;  // D3D11_COLOR_WRITE_ENABLE枚举类型来指定可以写入的颜色
}   D3D11_RENDER_TARGET_BLEND_DESC;

枚举类型D3D11_COLOR_WRITE_ENABLE有如下枚举值:

枚举值 含义
D3D11_COLOR_WRITE_ENABLE_RED = 1 可以写入红色
D3D11_COLOR_WRITE_ENABLE_GREEN = 2 可以写入绿色
D3D11_COLOR_WRITE_ENABLE_BLUE = 4 可以写入蓝色
D3D11_COLOR_WRITE_ENABLE_ALPHA = 8 可以写入ALPHA通道
D3D11_COLOR_WRITE_ENABLE_ALL = 15 可以写入所有颜色

若你想指定红色和ALPHA通道可以写入,可以用位运算与结合起来,即D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_ALPHA

ID3D11Device::CreateBlendState含义如下:

HRESULT ID3D11Device::CreateBlendState( 
    const D3D11_BLEND_DESC *pBlendStateDesc,    // [In]混合状态描述
    ID3D11BlendState **ppBlendState);           // [Out]输出混合状态

ID3D11DeviceContext::OMSetBlendState方法--输出合并阶段设置混合状态

方法如下:

void ID3D11DeviceContext::OMSetBlendState(
  ID3D11BlendState *pBlendState,      // [In]混合状态,如果要使用默认混合状态则提供nullptr
  const FLOAT [4]  BlendFactor,       // [In]混合因子,如不需要可以为nullptr
  UINT             SampleMask);       // [In]采样掩码,默认为0xffffffff

默认混合状态如下:

AlphaToCoverageEnable = false;
IndependentBlendEnable = false;
RenderTarget[0].BlendEnable = false;
RenderTarget[0].SrcBlend = D3D11_BLEND_ONE
RenderTarget[0].DestBlend = D3D11_BLEND_ZERO
RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD
RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE
RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO
RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD
RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL

采样掩码的设置主要是针对多重采样的操作,若采样掩码的第i位为0,则对应第i次采样将不进行,但这得在实际上进行不小于i次的采样时才会起作用。通常情况下设为0xffffffff来允许所有采样操作

常用混合等式

无颜色写入混合

无颜色写入混合公式如下:
\(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)
\(\mathbf{C} = \mathbf{C}_{src} \otimes (0,0,0) + \mathbf{C}_{dst} \otimes (1,1,1)\)
\(\mathbf{C} = \mathbf{C}_{dst}\)
同样,Alpha值也应当保留
\(A = A_{dst}\)

颜色加法混合

颜色加法混合公式如下:
\(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)
\(\mathbf{C} = \mathbf{C}_{src} \otimes (1,1,1) + \mathbf{C}_{dst} \otimes (1,1,1)\)
\(\mathbf{C} = \mathbf{C}_{src} + \mathbf{C}_{dst}\)
最终的Alpha值是多少并不影响前面的运算,因此可以设为任意值,这里设为源像素Alpha值:
\(A = A_{src}\)

透明混合

透明混合公式如下:
\(\mathbf{C} = \mathbf{C}_{src} \otimes \mathbf{F}_{src} \boxplus \mathbf{C}_{dst} \otimes \mathbf{F}_{dst}\)
\(\mathbf{C} = \mathbf{C}_{src} \otimes (A_{src},A_{src},A_{src}) + \mathbf{C}_{dst} \otimes ((1-A_{src}),(1-A_{src}),(1-A_{src}))\)
\(\mathbf{C} = A_{src}\mathbf{C}_{src} + (1-A_{src})\mathbf{C}_{dst}\)
最终的Alpha值是多少并不影响前面的运算,因此可以设为任意值,这里设为源像素Alpha值:
\(A = A_{src}\)

但需要注意的是,透明混合的绘制顺序是十分重要的。首先必须按照摄像机到物体的距离,对物体进行排序,然后按照从后到前的顺序进行混合。因为如果一个对象是透明的,我们就可以通过它看到背后的场景。如果先绘制较前的透明物体,那么深度缓冲区的值会被刷新,然后较后的透明物体会因为深度测试不通过而不被绘制:
DirectX11 With Windows SDK--11 混合状态与光栅化状态
可以看到,上图是先绘制水面然后绘制篱笆盒,这样会导致篱笆盒的下半部分因为深度比水面大而导致不通过深度测试,从而没有被绘制出来。所以在绘制透明物体前,要么关闭深度测试,要么对物体到摄像机的先后顺序进行排序,并按从后到前的顺序进行绘制。

光栅化状态

ID3D11Device::CreateRasterizerState方法--创建光栅化状态

在创建光栅化状态前,我们需要先填充D3D11_RASTERIZER_DESC结构体来描述光栅化状态:

typedef struct D3D11_RASTERIZER_DESC
{
    D3D11_FILL_MODE FillMode;          // 填充模式
    D3D11_CULL_MODE CullMode;          // 裁剪模式
    BOOL FrontCounterClockwise;        // 是否三角形顶点按逆时针排布时为正面
    INT DepthBias;                     // 深度偏移值
    FLOAT DepthBiasClamp;              // 深度最大允许偏移值
    FLOAT SlopeScaledDepthBias;        // 忽略
    BOOL DepthClipEnable;              // 是否允许深度测试将范围外的像素进行裁剪,默认TRUE
    BOOL ScissorEnable;                // 是否允许指定矩形范围的裁剪,若TRUE,则需要在RSSetScissor设置像素保留的矩形区域
    BOOL MultisampleEnable;            // 是否允许多重采样
    BOOL AntialiasedLineEnable;        // 是否允许反走样线,仅当多重采样为FALSE时才有效
}   D3D11_RASTERIZER_DESC;

对于枚举类型D3D11_FILL_MODE有如下枚举值:

枚举值 含义
D3D11_FILL_WIREFRAME = 2 线框填充方式
D3D11_FILL_SOLID = 3 面填充方式

枚举类型D3D11_CULL_MODE有如下枚举值:

枚举值 含义
D3D11_CULL_NONE = 1 无背面裁剪,即三角形无论处在视野的正面还是背面都能看到
D3D11_CULL_FRONT = 2 对处在视野正面的三角形进行裁剪
D3D11_CULL_BACK = 3 对处在视野背面的三角形进行裁剪

光栅化创建的方法如下:

HRESULT ID3D11Device::CreateRasterizerState( 
    const D3D11_RASTERIZER_DESC *pRasterizerDesc,    // [In]光栅化状态描述
    ID3D11RasterizerState **ppRasterizerState) = 0;  // [Out]输出光栅化状态

ID3D11DeviceContext::RSSetState方法--设置光栅化状态

void ID3D11DeviceContext::RSSetState(
  ID3D11RasterizerState *pRasterizerState);  // [In]光栅化状态,若为nullptr,则使用默认光栅化状态

默认光栅化状态如下:

FillMode = D3D11_FILL_SOLID;
CullMode = D3D11_CULL_BACK;
FrontCounterClockwise = FALSE;
DepthBias = 0;
SlopeScaledDepthBias = 0.0f;
DepthBiasClamp = 0.0f;
DepthClipEnable = TRUE;
ScissorEnable = FALSE;
MultisampleEnable = FALSE;
AntialiasedLineEnable = FALSE;

HLSL代码的变化

首先在常量缓冲区上,需要将材质移到每物体绘制的常量缓冲区内,因为现在从现在的例子开始,不同的物体在材质上是不同的,需要频繁更新:

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 CBChangesOnResize : register(b2)
{
    row_major matrix gProj;
}

cbuffer CBNeverChange : register(b3)
{
    DirectionalLight gDirLight[10];
    PointLight gPointLight[10];
    SpotLight gSpotLight[10];
    int gNumDirLight;
    int gNumPointLight;
    int gNumSpotLight;
}

然后在像素着色器上,可以对alpha值过低的像素进行裁剪,通过调用clip函数,若参数的值小于0,则该像素会被裁剪掉,从而避免后续的光照运算。在下面的例子中,alpha值低于0.1的像素都会被裁剪掉。

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

   // ...
    
    // 计算   
    float4 litColor = texColor * (ambient + diffuse) + spec;
    litColor.a = texColor.a * gMaterial.Diffuse.a;
    return litColor;
}

// ...

// 像素着色器(2D)
float4 PS_2D(VertexOut pIn) : SV_Target
{
    float4 color = tex.Sample(samLinear, pIn.Tex);
    clip(color.a - 0.1f);
    return color;
}

C++代码的变化

RenderStates类

RenderStates类可以一次性创建出所有可能需要用到的状态对象,然后在需要的时候可以获取它的静态成员,并且因为使用了ComPtr智能指针,无需管理内存:

class RenderStates
{
public:
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    static void InitAll(const ComPtr<ID3D11Device>& device);
    // 使用ComPtr无需手工释放

public:
    static ComPtr<ID3D11RasterizerState> RSWireframe;   // 光栅化器状态:线框模式
    static ComPtr<ID3D11RasterizerState> RSNoCull;      // 光栅化器状态:无背面裁剪模式

    static ComPtr<ID3D11SamplerState> SSLinear;         // 采样器状态:线性过滤
    static ComPtr<ID3D11SamplerState> SSAnistropic;     // 采样器状态:各项异性过滤

    static ComPtr<ID3D11BlendState> BSNoColorWrite;     // 混合状态:不写入颜色
    static ComPtr<ID3D11BlendState> BSTransparent;      // 混合状态:透明混合
    static ComPtr<ID3D11BlendState> BSAlphaToCoverage;  // 混合状态:Alpha-To-Coverage
};

而具体实现如下:

using namespace Microsoft::WRL;

ComPtr<ID3D11RasterizerState> RenderStates::RSNoCull        = nullptr;
ComPtr<ID3D11RasterizerState> RenderStates::RSWireframe     = nullptr;

ComPtr<ID3D11SamplerState> RenderStates::SSAnistropic       = nullptr;
ComPtr<ID3D11SamplerState> RenderStates::SSLinear           = nullptr;

ComPtr<ID3D11BlendState> RenderStates::BSAlphaToCoverage    = nullptr;
ComPtr<ID3D11BlendState> RenderStates::BSNoColorWrite       = nullptr;
ComPtr<ID3D11BlendState> RenderStates::BSTransparent        = nullptr;

void RenderStates::InitAll(const ComPtr<ID3D11Device>& device)
{
    // ***********初始化光栅化器状态***********
    D3D11_RASTERIZER_DESC rasterizerDesc;
    ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));

    // 线框模式
    rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
    rasterizerDesc.CullMode = D3D11_CULL_BACK;
    rasterizerDesc.FrontCounterClockwise = false;
    rasterizerDesc.DepthClipEnable = true;
    HR(device->CreateRasterizerState(&rasterizerDesc, &RSWireframe));

    // 无背面剔除模式
    rasterizerDesc.FillMode = D3D11_FILL_SOLID;
    rasterizerDesc.CullMode = D3D11_CULL_NONE;
    rasterizerDesc.FrontCounterClockwise = false;
    rasterizerDesc.DepthClipEnable = true;
    HR(device->CreateRasterizerState(&rasterizerDesc, &RSNoCull));

    
    // ***********初始化采样器状态***********
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory(&sampDesc, sizeof(sampDesc));

    // 线性过滤模式
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
    HR(device->CreateSamplerState(&sampDesc, SSLinear.GetAddressOf()));

    // 各向异性过滤模式
    sampDesc.Filter = D3D11_FILTER_ANISOTROPIC;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MaxAnisotropy = 4;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
    HR(device->CreateSamplerState(&sampDesc, SSAnistropic.GetAddressOf()));
    
    // ***********初始化混合状态***********
    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.GetAddressOf()));

    // 透明混合模式
    // Color = SrcAlpha * SrcColor + (1 - SrcAlpha) * DestColor 
    // Alpha = SrcAlpha
    blendDesc.AlphaToCoverageEnable = false;
    blendDesc.IndependentBlendEnable = false;
    rtDesc.BlendEnable = true;
    rtDesc.SrcBlend = D3D11_BLEND_SRC_ALPHA;
    rtDesc.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
    rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
    rtDesc.SrcBlendAlpha = D3D11_BLEND_ONE;
    rtDesc.DestBlendAlpha = D3D11_BLEND_ZERO;
    rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;

    HR(device->CreateBlendState(&blendDesc, BSTransparent.GetAddressOf()));
    
    // 无颜色写入混合模式
    // Color = DestColor
    // Alpha = DestAlpha
    rtDesc.SrcBlend = D3D11_BLEND_ZERO;
    rtDesc.DestBlend = D3D11_BLEND_ONE;
    rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
    rtDesc.SrcBlendAlpha = D3D11_BLEND_ZERO;
    rtDesc.DestBlendAlpha = D3D11_BLEND_ONE;
    rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;
    HR(device->CreateBlendState(&blendDesc, BSNoColorWrite.GetAddressOf()));
    
}

GameApp类的变化

首先内含的GameObject类需要添加Material类的存储,并提供GameObject::SetMaterial方法用于设置材质。这里不详细描述。

GameApp::InitResource方法的变化

该方法有如下变化:

  1. 初始化了篱笆盒、墙体、地板和静止水面物体
  2. 将摄像机设置为仅第三人称
  3. 设置了光栅化状态为无背面裁剪模式(因为透明情况下可以看到物体的背面)
  4. 设置了混合状态为透明混合模式
bool GameApp::InitResource()
{
    
    // ******************
    // 省略常量缓冲区的创建过程...
    
    // ******************
    // 初始化游戏对象
    ComPtr<ID3D11ShaderResourceView> 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);
    // 初始化篱笆盒
    HR(CreateDDSTextureFromFile(md3dDevice.Get(), L"Texture\\WireFence.dds", nullptr, texture.GetAddressOf()));
    mWireFence.SetBuffer(md3dDevice, Geometry::CreateBox());
    mWireFence.SetWorldMatrix(XMMatrixTranslation(0.0f, 0.01f, 0.0f));
    mWireFence.SetTexTransformMatrix(XMMatrixIdentity());
    mWireFence.SetTexture(texture);
    mWireFence.SetMaterial(material);
    
    // 初始化地板
    HR(CreateDDSTextureFromFile(md3dDevice.Get(), L"Texture\\floor.dds", nullptr, texture.ReleaseAndGetAddressOf()));
    mFloor.SetBuffer(md3dDevice, 
        Geometry::CreatePlane(XMFLOAT3(0.0f, -1.0f, 0.0f), XMFLOAT2(20.0f, 20.0f), XMFLOAT2(5.0f, 5.0f)));
    mFloor.SetWorldMatrix(XMMatrixIdentity());
    mFloor.SetTexTransformMatrix(XMMatrixIdentity());
    mFloor.SetTexture(texture);
    mFloor.SetMaterial(material);

    // 初始化墙体
    mWalls.resize(4);
    HR(CreateDDSTextureFromFile(md3dDevice.Get(), L"Texture\\brick.dds", nullptr, texture.ReleaseAndGetAddressOf()));
    // 这里控制墙体四个面的生成
    for (int i = 0; i < 4; ++i)
    {
        mWalls[i].SetBuffer(md3dDevice,
            Geometry::CreatePlane(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT2(20.0f, 8.0f), XMFLOAT2(5.0f, 1.5f)));
        XMMATRIX world = XMMatrixRotationX(-XM_PIDIV2) * XMMatrixRotationY(XM_PIDIV2 * i)
            * XMMatrixTranslation(i % 2 ? -10.0f * (i - 2) : 0.0f, 3.0f, i % 2 == 0 ? -10.0f * (i - 1) : 0.0f);
        mWalls[i].SetMaterial(material);
        mWalls[i].SetWorldMatrix(world);
        mWalls[i].SetTexTransformMatrix(XMMatrixIdentity());
        mWalls[i].SetTexture(texture);
    }
        
    // 初始化水
    material.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    material.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.5f);
    material.Specular = XMFLOAT4(0.8f, 0.8f, 0.8f, 32.0f);
    HR(CreateDDSTextureFromFile(md3dDevice.Get(), L"Texture\\water.dds", nullptr, texture.ReleaseAndGetAddressOf()));
    mWater.SetBuffer(md3dDevice,
        Geometry::CreatePlane(XMFLOAT3(), XMFLOAT2(20.0f, 20.0f), XMFLOAT2(10.0f, 10.0f)));
    mWater.SetWorldMatrix(XMMatrixIdentity());
    mWater.SetTexTransformMatrix(XMMatrixIdentity());
    mWater.SetTexture(texture);
    mWater.SetMaterial(material);

    // 省略初始化采样器状态...

    
    // ******************
    // 初始化常量缓冲区的值
    // 初始化每帧可能会变化的值
    mCameraMode = CameraMode::ThirdPerson;
    auto camera = std::shared_ptr<ThirdPersonCamera>(new ThirdPersonCamera);
    mCamera = camera;
    
    camera->SetTarget(XMFLOAT3(0.0f, 0.5f, 0.0f));
    camera->SetDistance(5.0f);
    camera->SetDistanceMinMax(2.0f, 14.0f);
    mCBFrame.view = mCamera->GetView();
    XMStoreFloat4(&mCBFrame.eyePos, mCamera->GetPositionXM());

    // 初始化仅在窗口大小变动时修改的值
    mCamera->SetFrustum(XM_PI / 3, AspectRatio(), 0.5f, 1000.0f);
    mCBOnReSize.proj = mCamera->GetProj();

    // 省略灯光的初始化...

    // 更新不容易被修改的常量缓冲区资源
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[2].Get(), 0, nullptr, &mCBOnReSize, 0, 0);
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[3].Get(), 0, nullptr, &mCBNeverChange, 0, 0);

    // 初始化所有渲染状态
    RenderStates::InitAll(md3dDevice);
    
    
    // ******************************
    // 设置好渲染管线各阶段所需资源

    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    // 预先绑定各自所需的缓冲区,其中每帧更新的缓冲区需要绑定到两个缓冲区上
    md3dImmediateContext->VSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
    md3dImmediateContext->VSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
    md3dImmediateContext->VSSetConstantBuffers(2, 1, mConstantBuffers[2].GetAddressOf());

    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());

    md3dImmediateContext->PSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
    md3dImmediateContext->PSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
    md3dImmediateContext->PSSetConstantBuffers(3, 1, mConstantBuffers[3].GetAddressOf());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, mSamplerState.GetAddressOf());

    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);

    return true;
}

GameApp::UpdateScene方法的变化

现在摄像机只有第三人称:

void GameApp::UpdateScene(float dt)
{

    // 更新鼠标事件,获取相对偏移量
    Mouse::State mouseState = mMouse->GetState();
    Mouse::State lastMouseState = mMouseTracker.GetLastState();
    POINT center = { mClientWidth / 2, mClientHeight / 2 };
    int dx = mouseState.x - center.x, dy = mouseState.y - center.y;
    mMouseTracker.Update(mouseState);
    Keyboard::State keyState = mKeyboard->GetState();
    mKeyboardTracker.Update(keyState);
    // 固定鼠标位置到窗口中间
    ClientToScreen(MainWnd(), &center);
    SetCursorPos(center.x, center.y);
    // 获取子类
    auto cam3rd = std::dynamic_pointer_cast<ThirdPersonCamera>(mCamera);

    
    // 第三人称摄像机的操作
    // 绕原点旋转
    cam3rd->SetTarget(XMFLOAT3());
    cam3rd->RotateX(dy * dt * 1.25f);
    cam3rd->RotateY(dx * dt * 1.25f);
    cam3rd->Approach(-mouseState.scrollWheelValue / 120 * 1.0f);

    // 更新观察矩阵,并更新每帧缓冲区
    mCamera->UpdateViewMatrix();
    XMStoreFloat4(&mCBFrame.eyePos, mCamera->GetPositionXM());
    mCBFrame.view = mCamera->GetView();
    

    // 重置滚轮值
    mMouse->ResetScrollWheelValue();
    
    
    // 退出程序,这里应向窗口发送销毁信息
    if (keyState.IsKeyDown(Keyboard::Escape))
        SendMessage(MainWnd(), WM_DESTROY, 0, 0);
    
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[1].Get(), 0, nullptr, &mCBFrame, 0, 0);
}

GameApp::DrawScene方法的变化

对于3D物体的,要先绘制不透明的物体,然后再绘制透明的物体。而对于透明的物体,这里一定要先绘制靠后的物体,然后才是靠前的物体。这里无论视角怎么变化,物体的先后顺序都是不会改变的,所以不会出现有物体的一部分无法绘制的情况:

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->RSSetState(nullptr);
    for (auto& wall : mWalls)
        wall.Draw(md3dImmediateContext);
    mFloor.Draw(md3dImmediateContext);

    // 绘制透明对象
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    // 篱笆盒稍微抬起一点高度
    mWireFence.SetWorldMatrix(XMMatrixTranslation(2.0f, 0.01f, 0.0f));
    mWireFence.Draw(md3dImmediateContext);
    mWireFence.SetWorldMatrix(XMMatrixTranslation(-2.0f, 0.01f, 0.0f));
    mWireFence.Draw(md3dImmediateContext);
    // 绘制了篱笆盒后再绘制水面
    mWater.Draw(md3dImmediateContext);
    
    // 绘制Direct2D部分
    md2dRenderTarget->BeginDraw();
    std::wstring text = L"当前摄像机模式:第三人称视角  Esc退出\n"
        "鼠标移动控制视野 滚轮控制第三人称观察距离";
    md2dRenderTarget->DrawTextW(text.c_str(), (UINT32)text.length(), mTextFormat.Get(),
        D2D1_RECT_F{ 0.0f, 0.0f, 500.0f, 60.0f }, mColorBrush.Get());
    HR(md2dRenderTarget->EndDraw());

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

最终效果如下:
DirectX11 With Windows SDK--11 混合状态与光栅化状态