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

DirectX11 With Windows SDK--13 抛弃FX11并初步实现BasicManager类

程序员文章站 2022-05-11 14:29:08
前言 DirectX11 With Windows SDK完整目录:http://www.cnblogs.com/X Jun/p/9028764.html 到现在为止,所有的教程项目都没有使用Effects11框架类来绘制场景。因为在D3DCompile API ( 47)版本中,如果你尝试编译fx ......

前言

DirectX11 With Windows SDK完整目录:

到现在为止,所有的教程项目都没有使用Effects11框架类来绘制场景。因为在D3DCompile API (#47)版本中,如果你尝试编译fx_5_0的效果文件,会收到这样的警告:
X4717: Effects deprecated for D3DCompiler_47

在未来的版本中,D3DCompiler可能会停止对FX11的支持,所以我们需要自行去管理各种特效,并改用HLSL编译器去编译每一个着色器。可以参考本系列前面的教程:

教程

这篇教程还会提到用深度/模板状态去实现简单的阴影效果,但不会深入数学公式原理。

项目源码点此:

回顾RenderStates类

目前的RenderStates类存放有比较常用的各种状态,原来在Effects11框架下是可以在fx文件初始化各种渲染状态,并设置到Technique11中。但现在我们只能在C++代码层中一次性创建好各种所需的渲染状态:

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<ID3D11RasterizerState> RSCullClockWise;   // 光栅化器状态:顺时针裁剪模式

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

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

    static ComPtr<ID3D11DepthStencilState> DSSWriteStencil;     // 深度/模板状态:写入模板值
    static ComPtr<ID3D11DepthStencilState> DSSDrawWithStencil;  // 深度/模板状态:对指定模板值的区域进行绘制
    static ComPtr<ID3D11DepthStencilState> DSSNoDoubleBlend;    // 深度/模板状态:无二次混合区域
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthTest;      // 深度/模板状态:关闭深度测试
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthWrite;     // 深度/模板状态:仅深度测试,不写入深度值
};

具体的设置可以参照源码或者上一章内容。

BasicManager类

现在,为了减轻GameApp类的负担,并且为了能够实现部分类似Effects11的功能,需要根据HLSL的代码去实现一个对应的简易BasicManager类。

BasicManager.h中还包含了对应HLSL常量缓冲区的结构体:

#ifndef BASICMANAGER_H
#define BASICMANAGER_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 CBDrawingState
{
    int isReflection;
    int isShadow;
    DirectX::XMINT2 pad;
};

struct CBChangesOnResize
{
    DirectX::XMMATRIX proj;
};


struct CBNeverChange
{
    DirectX::XMMATRIX reflection;
    DirectX::XMMATRIX shadow;
    DirectX::XMMATRIX refShadow;
    DirectionalLight dirLight[10];
    PointLight pointLight[10];
    SpotLight spotLight[10];
    int numDirLight;
    int numPointLight;
    int numSpotLight;
    float pad;      // 打包保证16字节对齐
};

// 暂时省略BasicManager类
// ...

#endif

现在HLSL中的Basic.fx如下:

#include "LightHelper.hlsli"

Texture2D tex : register(t0);
SamplerState sam : register(s0);


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

cbuffer CBDrawingState : register(b1)
{
    int gIsReflection;
    int gIsShadow;
}

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

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

cbuffer CBNeverChange : register(b4)
{
    row_major matrix gReflection;
    row_major matrix gShadow;
    row_major matrix gRefShadow;
    DirectionalLight gDirLight[10];
    PointLight gPointLight[10];
    SpotLight gSpotLight[10];
    int gNumDirLight;
    int gNumPointLight;
    int gNumSpotLight;
    float gPad;
}



struct Vertex3DIn
{
    float3 Pos : POSITION;
    float3 Normal : NORMAL;
    float2 Tex : TEXCOORD;
};

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

struct Vertex2DIn
{
    float3 Pos : POSITION;
    float2 Tex : TEXCOORD;
};

struct Vertex2DOut
{
    float4 PosH : SV_POSITION;
    float2 Tex : TEXCOORD;
};

各着色器的HLSL代码都分别放入独立的文件内,后面会详细讲述。

一个简易的BasicManager具有如下功能:

  1. 修改常量缓冲区的变量(整块更新)
  2. 类似Effects11那样,可以自己设置单通道下的各种状态、着色器

但是该管理类并不用于绘制,具体的绘制函数交给了简易GameObject类来操作。所以每次绘制物体时,根据情况可能需要调用BasicManager的设置方法来指定当前要以怎样的形式来渲染,然后才是调用GameObject::Draw方法绘制。

因为该类没有反射功能,所以用户需要决定什么时候才去更新常量缓冲区资源。

class BasicManager
{
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();
    // Alpha混合绘制
    void SetRenderAlphaBlend();     
    // 无二次混合
    void SetRenderNoDoubleBlend(UINT stencilRef);
    // 仅写入模板值
    void SetWriteStencilOnly(UINT stencilRef);      
    // 对指定模板值的区域进行绘制,采用默认状态
    void SetRenderDefaultWithStencil(UINT stencilRef);      
    // 对指定模板值的区域进行绘制,采用Alpha混合
    void SetRenderAlphaBlendWithStencil(UINT stencilRef);       
    // 2D默认状态绘制
    void Set2DRenderDefault();
    // 2D混合绘制
    void Set2DRenderAlphaBlend();


private:
    // 从.fx/.hlsl文件中编译着色器
    HRESULT CompileShaderFromFile(const WCHAR* szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut);

private:
    ComPtr<ID3D11VertexShader> mVertexShader3D;             // 用于3D的顶点着色器
    ComPtr<ID3D11PixelShader> mPixelShader3D;               // 用于3D的像素着色器
    ComPtr<ID3D11VertexShader> mVertexShader2D;             // 用于2D的顶点着色器
    ComPtr<ID3D11PixelShader> mPixelShader2D;               // 用于2D的像素着色器

    ComPtr<ID3D11InputLayout> mVertexLayout2D;              // 用于2D的顶点输入布局
    ComPtr<ID3D11InputLayout> mVertexLayout3D;              // 用于3D的顶点输入布局

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

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

默认状态绘制

该绘制模式和后面的所有绘制模式都使用的是线性Wrap采样器。

BasicManager::SetRenderDefault方法使用了默认的3D像素着色器和顶点着色器,并且其余各状态都保留使用默认状态:

void BasicManager::SetRenderDefault()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
}

Alpha透明混合绘制

该绘制模式关闭了光栅化裁剪,并采用透明混合方式。

void BasicManager::SetRenderAlphaBlend()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

无重复混合(单次混合)

该绘制模式用于绘制阴影,防止过度混合。需要指定绘制区域的模板值。

void BasicManager::SetRenderNoDoubleBlend(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSNoDoubleBlend.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

仅写入模板值

该模式用于向模板缓冲区写入用户指定的模板值,并且不写入到深度缓冲区和后备缓冲区。

void BasicManager::SetWriteStencilOnly(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSWriteStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSNoColorWrite.Get(), nullptr, 0xFFFFFFFF);
}

对指定模板值区域进行常规绘制

该模式下,仅对模板缓冲区的模板值和用户指定的相等的区域进行常规绘制。

void BasicManager::SetRenderDefaultWithStencil(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSCullClockWise.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSDrawWithStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
}

对指定模板值区域进行Alpha透明混合绘制

该模式下,仅对模板缓冲区的模板值和用户指定的相等的区域进行Alpha透明混合绘制。

void BasicManager::SetRenderAlphaBlendWithStencil(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSDrawWithStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

2D默认绘制

该模式使用的是2D顶点着色器和像素着色器,并修改为2D输入布局。

void BasicManager::Set2DRenderDefault()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout2D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader2D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(nullptr);
    md3dImmediateContext->PSSetShader(mPixelShader2D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF);
}

2D透明混合绘制

相比上面,多了透明混合状态。

void BasicManager::Set2DRenderAlphaBlend()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout2D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader2D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader2D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(nullptr, 0);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
}

更新常量缓冲区

这里使用的是成员模板函数,根据传入的结构体类型来更新常量缓冲区:

template<class T>
void BasicManager::UpdateConstantBuffer(const T& cbuffer)
{
}

template<>
void BasicManager::UpdateConstantBuffer<CBChangesEveryDrawing>(const CBChangesEveryDrawing& cbuffer)
{
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[0].Get(), 0, nullptr, &cbuffer, 0, 0);
}

template<>
void BasicManager::UpdateConstantBuffer<CBDrawingState>(const CBDrawingState& cbuffer)
{
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[1].Get(), 0, nullptr, &cbuffer, 0, 0);
}

template<>
void BasicManager::UpdateConstantBuffer<CBChangesEveryFrame>(const CBChangesEveryFrame& cbuffer)
{
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[2].Get(), 0, nullptr, &cbuffer, 0, 0);
}

template<>
void BasicManager::UpdateConstantBuffer<CBChangesOnResize>(const CBChangesOnResize& cbuffer)
{
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[3].Get(), 0, nullptr, &cbuffer, 0, 0);
}

template<>
void BasicManager::UpdateConstantBuffer<CBNeverChange>(const CBNeverChange& cbuffer)
{
    md3dImmediateContext->UpdateSubresource(mConstantBuffers[4].Get(), 0, nullptr, &cbuffer, 0, 0);
}

防止GameApp::OnResize在BasicManager未初始化时修改投影矩阵

BasicManager::IsInit方法判断摄像机是否经过了初始化。由于BasicManager的初始化在GameApp::Init之前,但却会调用GameApp::OnResize先更新其中的一个常量缓冲区,所以需要加上一重防护在未初始化的时候不应该操作,并在GameApp::InitResource方法初始化摄像机的投影矩阵。

当然,目前BasicManager能做的事情还是比较有限的,并且还需要随着HLSL代码的变动而随之调整。更多的功能会在后续教程中实现。

绘制平面阴影

使用XMMatrixShadow可以生成阴影矩阵,根据光照类型和位置对几何体投影到平面上的。

XMMATRIX XMMatrixShadow(
    FXMVECTOR ShadowPlane,      // 平面向量(nx, ny, nz, d)
    FXMVECTOR LightPosition);   // w = 0时表示平行光方向, w = 1时表示光源位置

通常指定的平面会稍微比实际平面高那么一点点,以避免深度缓冲区资源争夺导致阴影显示有问题。

使用模板缓冲区防止过度混合

一个物体投影到平面上时,投影区域的某些位置可能位于多个三角形之内,这会导致这些位置会有多个像素通过测试并进行混合操作,渲染的次数越多,显示的颜色会越黑。

DirectX11 With Windows SDK--13 抛弃FX11并初步实现BasicManager类

我们可以使用模板缓冲区来解决这个问题。

  1. 在之前的例子中,我们用模板值为0的区域表示非镜面反射区,模板值为1的区域表示为镜面反射区;
  2. 使用RenderStates::DSSNoDoubleBlend的深度模板状态,当给定的模板值和深度/模板缓冲区的模板值一致时,通过模板测试并对模板值加1,绘制该像素的混合,然后下一次由于给定的模板值比深度/模板缓冲区的模板值小1,不会再通过模板测试,也就阻挡了后续像素的绘制;
  3. 应当先绘制镜面的阴影区域,再绘制正常的阴影区域。

着色器代码的变化

Basic_PS_2D.hlsl文件变化如下:

#include "Basic.fx"

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

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.1f);

    // 标准化法向量
    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);
    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);
    int i;


    // 强制展开循环以减少指令数
    [unroll]
    for (i = 0; i < gNumDirLight; ++i)
    {
        ComputeDirectionalLight(gMaterial, gDirLight[i], pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }
    
    [unroll]
    for (i = 0; i < gNumPointLight; ++i)
    {
        PointLight pointLight = gPointLight[i];
        // 若当前在绘制反射物体,需要对光照进行反射矩阵变换
        [flatten]
        if (gIsReflection)
        {
            pointLight.Position = (float3) mul(float4(pointLight.Position, 1.0f), gReflection);
        }

        ComputePointLight(gMaterial, pointLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }
    
    [unroll]
    for (i = 0; i < gNumSpotLight; ++i)
    {
        SpotLight spotLight = gSpotLight[i];
        // 若当前在绘制反射物体,需要对光照进行反射矩阵变换
        [flatten]
        if (gIsReflection)
        {
            spotLight.Position = (float3) mul(float4(spotLight.Position, 1.0f), gReflection);
        }

        ComputeSpotLight(gMaterial, spotLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }
    

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

Basic_VS_2D.hlsl变化如下:

#include "Basic.fx"

// 顶点着色器(2D)
Vertex2DOut VS_2D(Vertex2DIn pIn)
{
    Vertex2DOut pOut;
    pOut.PosH = float4(pIn.Pos, 1.0f);
    pOut.Tex = mul(float4(pIn.Tex, 0.0f, 1.0f), gTexTransform).xy;
    return pOut;
}

Basic_VS_3D.hlsl变化如下:

#include "Basic.fx"

// 顶点着色器(3D)
Vertex3DOut VS_3D(Vertex3DIn pIn)
{
    Vertex3DOut pOut;
    
    float4 posH = mul(float4(pIn.Pos, 1.0f), gWorld);
    // 若当前在绘制反射物体,先进行反射操作
    [flatten]
    if (gIsReflection)
    {
        posH = mul(posH, gReflection);
    }
    // 若当前在绘制阴影,先进行投影操作
    [flatten]
    if (gIsShadow)
    {
        posH = (gIsReflection ? mul(posH, gRefShadow) : mul(posH, gShadow));
    }

    pOut.PosH = mul(mul(posH, gView), gProj);
    pOut.PosW = mul(float4(pIn.Pos, 1.0f), gWorld).xyz;
    pOut.NormalW = mul(pIn.Normal, (float3x3) gWorldInvTranspose);
    pOut.Tex = mul(float4(pIn.Tex, 0.0f, 1.0f), gTexTransform).xy;
    return pOut;
}

现在每个着色器的代码都放到单独的文件来参与编译了。

场景绘制

现在场景只有墙体、地板、木箱和镜面。

第1步: 镜面区域写入模板缓冲区

// *********************
// 1. 给镜面反射区域写入值1到模板缓冲区
// 

mBasicManager.SetWriteStencilOnly(1);
mMirror.Draw(md3dImmediateContext);

第2步: 绘制不透明的反射物体和阴影

// ***********************
// 2. 绘制不透明的反射物体
//

// 开启反射绘制
mDrawingState.isReflection = 1;     // 反射开启
mBasicManager.UpdateConstantBuffer(mDrawingState);
mBasicManager.SetRenderDefaultWithStencil(1);

mWalls[2].Draw(md3dImmediateContext);
mWalls[3].Draw(md3dImmediateContext);
mWalls[4].Draw(md3dImmediateContext);
mFloor.Draw(md3dImmediateContext);
mWoodCrate.Draw(md3dImmediateContext);

// 绘制阴影
mWoodCrate.SetMaterial(mShadowMat);
mDrawingState.isShadow = 1;         // 反射开启,阴影开启
mBasicManager.UpdateConstantBuffer(mDrawingState);
mBasicManager.SetRenderNoDoubleBlend(1);

mWoodCrate.Draw(md3dImmediateContext);

// 恢复到原来的状态
mDrawingState.isShadow = 0;
mBasicManager.UpdateConstantBuffer(mDrawingState);
mWoodCrate.SetMaterial(mWoodCrateMat);

第3步: 绘制透明镜面

// ***********************
// 3. 绘制透明镜面
//

mBasicManager.SetRenderAlphaBlendWithStencil(1);

mMirror.Draw(md3dImmediateContext);
    
// 关闭反射绘制
mDrawingState.isReflection = 0;
mBasicManager.UpdateConstantBuffer(mDrawingState);

第4步: 绘制不透明的正常物体和阴影

// ************************
// 4. 绘制不透明的正常物体
//
mBasicManager.SetRenderDefault();

for (auto& wall : mWalls)
    wall.Draw(md3dImmediateContext);
mFloor.Draw(md3dImmediateContext);
mWoodCrate.Draw(md3dImmediateContext);

// 绘制阴影
mWoodCrate.SetMaterial(mShadowMat);
mDrawingState.isShadow = 1;         // 反射关闭,阴影开启
mBasicManager.UpdateConstantBuffer(mDrawingState);
mBasicManager.SetRenderNoDoubleBlend(0);

mWoodCrate.Draw(md3dImmediateContext);

mDrawingState.isShadow = 0;         // 反射关闭
mBasicManager.UpdateConstantBuffer(mDrawingState);
mWoodCrate.SetMaterial(mWoodCrateMat);

绘制效果如下:
DirectX11 With Windows SDK--13 抛弃FX11并初步实现BasicManager类

注意该样例只生成点光灯到地板的阴影。你可以用各种摄像机模式来进行测试。