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

前言

DirectX11 With Windows SDK完整目录:http://www.cnblogs.com/X-Jun/p/9028764.html

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

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

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

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

项目源码点此:https://github.com/MKXJun/DX11-Without-DirectX-SDK

混合等式

对于两个相同位置的像素点,规定\(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}\)

但需要注意的是,透明混合的绘制顺序是十分重要的。首先必须按照摄像机到物体的距离,对物体进行排序,然后按照从后到前的顺序进行混合。因为如果一个对象是透明的,我们就可以通过它看到背后的场景。如果先绘制较前的透明物体,那么深度缓冲区的值会被刷新,然后较后的透明物体会因为深度测试不通过而不被绘制:

可以看到,上图是先绘制水面然后绘制篱笆盒,这样会导致篱笆盒的下半部分因为深度比水面大而导致不通过深度测试,从而没有被绘制出来。所以在绘制透明物体前,要么关闭深度测试,要么对物体到摄像机的先后顺序进行排序,并按从后到前的顺序进行绘制。

光栅化状态

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));
}

最终效果如下:

原文地址:https://www.cnblogs.com/X-Jun/p/9346640.html

时间: 2024-10-17 02:07:02

DirectX11 With Windows SDK--11 混合状态与光栅化状态的相关文章

DirectX11 With Windows SDK 24--Render-To-Texture(RTT)技术的应用、使用ScreenGrab保存纹理到文件

前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用.此外,这里还会讲到如何使用DirectXTex的ScreenGrab来保存纹理,可以说是干货满满了. 如果想要看Render-To-Texture在动态天空盒的应用,可以点此回顾: 章节 23 立方体映射:动态天空盒的实现 DirectX11 With Windows SDK完整目录 Github项目

粒子系统与雨的效果 (DirectX11 with Windows SDK)

前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用Windows SDK来实现. 顺手安利一波:我最近在学DX11 with WindowSDK 教程 博客地址: https://www.cnblogs.com/X-Jun/p/9028764.html 所以下面的都是基于这个教程和上面提到的那本书来写的,推荐看到教程17章和书里第15章之后再来看这篇博客,有助

DirectX11 With Windows SDK--12 深度/模板状态

前言 DirectX11 With Windows SDK完整目录:http://www.cnblogs.com/X-Jun/p/9028764.html 深度/模板测试使用的是与后备缓冲区同等分辨率大小的缓冲区,每个元素的一部分连续位用于深度测试,其余的则用作模板测试.两个测试的目的都是为了能够根据深度/模板状态需求的设置来选择需要绘制的像素. 项目源码点此:https://github.com/MKXJun/DX11-Without-DirectX-SDK 深度/模板测试 深度测试.模板测试

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

前言 上一章我们知道了如何使用几何着色器将顶点通过流输出阶段输出到绑定的顶点缓冲区.接下来我们继续利用它来实现一些新的效果,在这一章,你将了解: 实现公告板效果 Alpha-To-Coverage 对GPU资源进行读/写操作 纹理数组 实现雾效 DirectX11 With Windows SDK完整目录 Github项目源码 实现雾效 虽然这部分与几何着色器并没有什么关系,但是雾的效果在该Demo中会用到,并且前面也没有讲过这部分内容,故先在这里提出来. 有时候我们需要在游戏中模拟一些特定的天

DirectX11 With Windows SDK--29 计算着色器:内存模型、线程同步;实现顺序无关透明度(OIT)

前言 由于透明混合在不同的绘制顺序下结果会不同,这就要求绘制前要对物体进行排序,然后再从后往前渲染.但即便是仅渲染一个物体(如上一章的水波),也会出现透明绘制顺序不对的情况,普通的绘制是无法避免的.如果要追求正确的效果,就需要对每个像素位置对所有的像素按深度值进行排序.本章将介绍一种仅DirectX11及更高版本才能实现的顺序无关的透明度(Order-Independent Transparency,OIT),虽然它是用像素着色器来实现的,但是用到了计算着色器里面的一些相关知识. 这一章综合性很

DirectX11 With Windows SDK--15 几何着色器初探

# 前言 从这一部分开始,感觉就像是踏入了无人深空一样,在之前初学DX11的时候,这部分内容都是基本上跳过的,现在打算重新认真地把它给拾回来. [DirectX11 With Windows SDK完整目录](http://www.cnblogs.com/X-Jun/p/9028764.html) [Github项目源码](https://github.com/MKXJun/DirectX11-With-Windows-SDK) # 几何着色器 首先用一张图来回顾一下渲染管线的各个阶段,目前为止

DirectX11 With Windows SDK--16 利用几何着色器可选的流输出阶段帮助绘制多种分形

前言 在上一章,我们知道了如何使用几何着色器来重新组装图元,比如从一个三角形分裂成三个三角形.但是为了实现更高阶的分形,我们必须要从几何着色器拿到输出的顶点.这里我们可以使用可选的流输出阶段来拿到顶点集合. 注意: 本章末尾有大量的GIF动图! DirectX11 With Windows SDK完整目录 Github项目源码 流输出阶段 现在我们知道GPU可以写入纹理(textures),例如深度/模板缓冲区以及后备缓冲区.当然,我们也可以通过渲染管线的流输出阶段让GPU将几何着色器输出的顶点

DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪

前言 这一章将了解如何在DirectX 11利用硬件实例化技术高效地绘制重复的物体,以及使用视锥体裁剪技术提前将位于视锥体外的物体进行排除. 在此之前需要额外了解的章节如下: 章节回顾 18 使用DirectXCollision库进行碰撞检测 19 模型加载:obj格式的读取及使用二进制文件提升读取效率 DirectX11 With Windows SDK完整目录 Github项目源码 硬件实例化(Hardware Instancing) 硬件实例化指的是在场景中绘制同一个物体多次,但是是以不同

DirectX11 With Windows SDK--30 计算着色器:图像模糊、索贝尔算子

前言 到这里计算着色器的主线学习基本结束,剩下的就是再补充两个有关图像处理方面的应用.这里面包含了龙书11的图像模糊,以及龙书12额外提到的Sobel算子进行边缘检测.主要内容源自于龙书12,项目源码也基于此进行调整. 学习目标: 熟悉图像处理常用的卷积 熟悉高斯模糊.Sobel算子 DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报. 图像卷积 在图像处理中,经常需要用到