原文:DirectX11 With Windows SDK--11 混合状态

前言

这一章会着重讲述混合状态,在下一章则会讲述深度/模板状态

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

混合等式

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

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



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

HLSL代码的变化

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

cbuffer CBChangesEveryDrawing : register(b0)
{
matrix g_World;
matrix g_WorldInvTranspose;
Material g_Material;
} cbuffer CBChangesEveryFrame : register(b1)
{
matrix g_View;
float3 g_EyePosW;
} cbuffer CBChangesOnResize : register(b2)
{
matrix g_Proj;
} cbuffer CBChangesRarely : register(b3)
{
DirectionalLight g_DirLight[10];
PointLight g_PointLight[10];
SpotLight g_SpotLight[10];
int g_NumDirLight;
int g_NumPointLight;
int g_NumSpotLight;
float g_Pad;
}

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

// Basic_PS_3D.hlsl
#include "Basic.hlsli" // 像素着色器(3D)
float4 PS_3D(VertexPosHWNormalTex pIn) : SV_Target
{
// 提前进行裁剪,对不符合要求的像素可以避免后续运算
float4 texColor = g_Tex.Sample(g_SamLinear, pIn.Tex);
clip(texColor.a - 0.1f); // ... // 计算
float4 litColor = texColor * (ambient + diffuse) + spec;
litColor.a = texColor.a * g_Material.Diffuse.a;
return litColor;
}
// Basic_PS_2D.hlsl
#include "Basic.hlsli" // 像素着色器(2D)
float4 PS_2D(VertexPosHTex pIn) : SV_Target
{
float4 color = g_Tex.Sample(g_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(ID3D11Device * device);
// 使用ComPtr无需手工释放 public:
static ComPtr<ID3D11RasterizerState> RSWireframe; // 光栅化器状态:线框模式
static ComPtr<ID3D11RasterizerState> RSNoCull; // 光栅化器状态:无背面裁剪模式 static ComPtr<ID3D11SamplerState> SSLinearWrap; // 采样器状态:线性过滤
static ComPtr<ID3D11SamplerState> SSAnistropicWrap; // 采样器状态:各项异性过滤 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::SSAnistropicWrap = nullptr;
ComPtr<ID3D11SamplerState> RenderStates::SSLinearWrap = nullptr; ComPtr<ID3D11BlendState> RenderStates::BSAlphaToCoverage = nullptr;
ComPtr<ID3D11BlendState> RenderStates::BSNoColorWrite = nullptr;
ComPtr<ID3D11BlendState> RenderStates::BSTransparent = nullptr; void RenderStates::InitAll(ID3D11Device * device)
{
// 先前初始化过的话就没必要重来了
if (IsInit())
return; // ***********初始化光栅化器状态***********
D3D11_RASTERIZER_DESC rasterizerDesc;
ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc)); // 线框模式
rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
rasterizerDesc.CullMode = D3D11_CULL_NONE;
rasterizerDesc.FrontCounterClockwise = false;
rasterizerDesc.DepthClipEnable = true;
HR(device->CreateRasterizerState(&rasterizerDesc, RSWireframe.GetAddressOf())); // 无背面剔除模式
rasterizerDesc.FillMode = D3D11_FILL_SOLID;
rasterizerDesc.CullMode = D3D11_CULL_NONE;
rasterizerDesc.FrontCounterClockwise = false;
rasterizerDesc.DepthClipEnable = true;
HR(device->CreateRasterizerState(&rasterizerDesc, RSNoCull.GetAddressOf())); // ***********初始化采样器状态***********
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, SSLinearWrap.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, SSAnistropicWrap.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()
{ // ******************
// 设置常量缓冲区描述
//
D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DYNAMIC;
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
// 新建用于VS和PS的常量缓冲区
cbd.ByteWidth = sizeof(CBChangesEveryDrawing);
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[0].GetAddressOf()));
cbd.ByteWidth = sizeof(CBChangesEveryFrame);
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[1].GetAddressOf()));
cbd.ByteWidth = sizeof(CBChangesOnResize);
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[2].GetAddressOf()));
cbd.ByteWidth = sizeof(CBChangesRarely);
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[3].GetAddressOf()));
// ******************
// 初始化游戏对象
//
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(m_pd3dDevice.Get(), L"Texture\\WireFence.dds", nullptr, texture.GetAddressOf()));
m_WireFence.SetBuffer(m_pd3dDevice.Get(), Geometry::CreateBox());
m_WireFence.SetTexture(texture.Get());
m_WireFence.SetMaterial(material); // 初始化地板
HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\floor.dds", nullptr, texture.ReleaseAndGetAddressOf()));
m_Floor.SetBuffer(m_pd3dDevice.Get(),
Geometry::CreatePlane(XMFLOAT3(0.0f, -1.0f, 0.0f), XMFLOAT2(20.0f, 20.0f), XMFLOAT2(5.0f, 5.0f)));
m_Floor.SetTexture(texture.Get());
m_Floor.SetMaterial(material); // 初始化墙体
m_Walls.resize(4);
HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\brick.dds", nullptr, texture.ReleaseAndGetAddressOf()));
// 这里控制墙体四个面的生成
for (int i = 0; i < 4; ++i)
{
m_Walls[i].SetBuffer(m_pd3dDevice.Get(),
Geometry::CreatePlane(XMFLOAT3(), 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);
m_Walls[i].SetMaterial(material);
m_Walls[i].SetWorldMatrix(world);
m_Walls[i].SetTexture(texture.Get());
} // 初始化水
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(m_pd3dDevice.Get(), L"Texture\\water.dds", nullptr, texture.ReleaseAndGetAddressOf()));
m_Water.SetBuffer(m_pd3dDevice.Get(),
Geometry::CreatePlane(XMFLOAT3(), XMFLOAT2(20.0f, 20.0f), XMFLOAT2(10.0f, 10.0f)));
m_Water.SetTexture(texture.Get());
m_Water.SetMaterial(material); // ******************
// 初始化常量缓冲区的值
// // 初始化每帧可能会变化的值
auto camera = std::shared_ptr<ThirdPersonCamera>(new ThirdPersonCamera);
m_pCamera = camera;
camera->SetViewPort(0.0f, 0.0f, (float)m_ClientWidth, (float)m_ClientHeight);
camera->SetTarget(XMFLOAT3(0.0f, 0.5f, 0.0f));
camera->SetDistance(5.0f);
camera->SetDistanceMinMax(2.0f, 14.0f); // 初始化仅在窗口大小变动时修改的值
m_pCamera->SetFrustum(XM_PI / 3, AspectRatio(), 0.5f, 1000.0f);
m_CBOnResize.proj = XMMatrixTranspose(m_pCamera->GetProjXM()); // ******************
// 初始化不会变化的值
// // 环境光
m_CBRarely.dirLight[0].ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_CBRarely.dirLight[0].diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
m_CBRarely.dirLight[0].specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_CBRarely.dirLight[0].direction = XMFLOAT3(0.0f, -1.0f, 0.0f);
// 灯光
m_CBRarely.pointLight[0].position = XMFLOAT3(0.0f, 15.0f, 0.0f);
m_CBRarely.pointLight[0].ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_CBRarely.pointLight[0].diffuse = XMFLOAT4(0.6f, 0.6f, 0.6f, 1.0f);
m_CBRarely.pointLight[0].specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
m_CBRarely.pointLight[0].att = XMFLOAT3(0.0f, 0.1f, 0.0f);
m_CBRarely.pointLight[0].range = 25.0f;
m_CBRarely.numDirLight = 1;
m_CBRarely.numPointLight = 1;
m_CBRarely.numSpotLight = 0; // 更新不容易被修改的常量缓冲区资源
D3D11_MAPPED_SUBRESOURCE mappedData;
HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[2].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(CBChangesOnResize), &m_CBOnResize, sizeof(CBChangesOnResize));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[2].Get(), 0); HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[3].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(CBChangesRarely), &m_CBRarely, sizeof(CBChangesRarely));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[3].Get(), 0); // 初始化所有渲染状态
RenderStates::InitAll(m_pd3dDevice.Get()); // ******************
// 给渲染管线各个阶段绑定好所需资源
// // 设置图元类型,设定输入布局
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout3D.Get());
// 预先绑定各自所需的缓冲区,其中每帧更新的缓冲区需要绑定到两个缓冲区上
m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffers[0].GetAddressOf());
m_pd3dImmediateContext->VSSetConstantBuffers(1, 1, m_pConstantBuffers[1].GetAddressOf());
m_pd3dImmediateContext->VSSetConstantBuffers(2, 1, m_pConstantBuffers[2].GetAddressOf());
// 默认绑定3D着色器
m_pd3dImmediateContext->VSSetShader(m_pVertexShader3D.Get(), nullptr, 0); m_pd3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get()); m_pd3dImmediateContext->PSSetConstantBuffers(0, 1, m_pConstantBuffers[0].GetAddressOf());
m_pd3dImmediateContext->PSSetConstantBuffers(1, 1, m_pConstantBuffers[1].GetAddressOf());
m_pd3dImmediateContext->PSSetConstantBuffers(3, 1, m_pConstantBuffers[3].GetAddressOf());
m_pd3dImmediateContext->PSSetShader(m_pPixelShader3D.Get(), nullptr, 0);
m_pd3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); m_pd3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF); return true;
}

GameApp::UpdateScene方法的变化

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

void GameApp::UpdateScene(float dt)
{ // 更新鼠标事件,获取相对偏移量
Mouse::State mouseState = m_pMouse->GetState();
Mouse::State lastMouseState = m_MouseTracker.GetLastState();
m_MouseTracker.Update(mouseState); Keyboard::State keyState = m_pKeyboard->GetState();
m_KeyboardTracker.Update(keyState); // 获取子类
auto cam3rd = std::dynamic_pointer_cast<ThirdPersonCamera>(m_pCamera); // ******************
// 第三人称摄像机的操作
// // 绕原点旋转
cam3rd->RotateX(mouseState.y * dt * 1.25f);
cam3rd->RotateY(mouseState.x * dt * 1.25f);
cam3rd->Approach(-mouseState.scrollWheelValue / 120 * 1.0f); // 更新观察矩阵,并更新每帧缓冲区
m_pCamera->UpdateViewMatrix();
m_CBFrame.eyePos = m_pCamera->GetPositionXM();
m_CBFrame.view = XMMatrixTranspose(m_pCamera->GetViewXM()); // 重置滚轮值
m_pMouse->ResetScrollWheelValue(); // 退出程序,这里应向窗口发送销毁信息
if (m_KeyboardTracker.IsKeyPressed(Keyboard::Escape))
SendMessage(MainWnd(), WM_DESTROY, 0, 0); D3D11_MAPPED_SUBRESOURCE mappedData;
HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[1].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(CBChangesEveryFrame), &m_CBFrame, sizeof(CBChangesEveryFrame));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0);
}

GameApp::DrawScene方法的变化

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

void GameApp::DrawScene()
{
assert(m_pd3dImmediateContext);
assert(m_pSwapChain); m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); // ********************
// 1. 绘制不透明对象
//
m_pd3dImmediateContext->RSSetState(nullptr);
m_pd3dImmediateContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); for (auto& wall : m_Walls)
wall.Draw(m_pd3dImmediateContext.Get());
m_Floor.Draw(m_pd3dImmediateContext.Get()); // ********************
// 2. 绘制透明对象
//
m_pd3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
m_pd3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF); // 篱笆盒稍微抬起一点高度
m_WireFence.SetWorldMatrix(XMMatrixTranslation(2.0f, 0.01f, 0.0f));
m_WireFence.Draw(m_pd3dImmediateContext.Get());
m_WireFence.SetWorldMatrix(XMMatrixTranslation(-2.0f, 0.01f, 0.0f));
m_WireFence.Draw(m_pd3dImmediateContext.Get());
// 绘制了篱笆盒后再绘制水面
m_Water.Draw(m_pd3dImmediateContext.Get()); // ********************
// 绘制Direct2D部分
// // ... HR(m_pSwapChain->Present(0, 0));
}

最终效果如下:

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11 With Windows SDK--11 混合状态的更多相关文章

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

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  2. DirectX11 With Windows SDK--12 深度/模板状态、平面镜反射绘制

    前言 深度/模板测试使用的是与后备缓冲区同等分辨率大小的缓冲区,每个元素的一部分连续位用于深度测试,其余的则用作模板测试.两个测试的目的都是为了能够根据深度/模板状态需求的设置来选择需要绘制的像素. ...

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

    前言 虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下四种: 光栅化状态(光栅化阶段) 采样器状态(像素着色阶段) 混合状态(输出合并阶段 ...

  4. DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

    原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的 ...

  5. DirectX11 With Windows SDK--09 纹理映射与采样器状态

    前言 在之前的DirectX SDK中,纹理的读取使用的是D3DX11CreateShaderResourceViewFromFile函数,现在在Windows SDK中已经没有这些函数,我们需要找到 ...

  6. DirectX11 With Windows SDK--00 目录

    前言 (更新于 2019/4/10) 从第一次接触DirectX 11到现在已经有将近两年的时间了.还记得前年暑假被要求学习DirectX 11,在用龙书的源码配置项目运行环境的时候都花了好几天的时间 ...

  7. DirectX11 With Windows SDK--26 计算着色器:入门

    前言 现在开始迎来所谓的高级篇了,目前计划是计算着色器部分的内容视项目情况,大概会分3-5章来讲述. DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群 ...

  8. DirectX11 With Windows SDK--01 DirectX11初始化

    前言 由于个人觉得龙书里面第4章提供的Direct3D 初始化项目封装得比较好,而且DirectX SDK Samples里面的初始化程序过于精简,不适合后续使用,故选择了以Init Direct3D ...

  9. DirectX11 With Windows SDK--07 添加光照与常用几何模型

    前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的光照模型.除此之外,该项目还用到了多个常量缓冲区,因此还会提及HLSL的常量缓冲区打包规则以及如何设置多 ...

随机推荐

  1. 2.快速创建springboot项目 连pom文件里面的配置都不用配了

    无论是创建项目 还是module 模块 选择这个 .然后在后面的选择中选择自己要的功能 就可以把相关的依赖都加进去 省去了依赖 其后的写法跟第一篇一样 在这个项目下面有一个配置文件 ====>a ...

  2. leetcode 850. Rectangle Area II

    给定一些矩形2 求覆盖面积 矩形不超过200个 1 算法1 朴素思想 虽然朴素但是代码却有意思 利用容斥原理 复杂度高达 N*2^N class Solution: def intersect(rec ...

  3. 封装MySQL C API 基本操作

    根据我的以前的文章 http://blog.csdn.net/skyhuangdan/article/details/21099929 链接数据库成功后进行封装. 我封装类使用的是VS2005下的wi ...

  4. Leetcode264. Ugly Number II丑数2

    编写一个程序,找出第 n 个丑数. 丑数就是只包含质因数 2, 3, 5 的正整数. 示例: 输入: n = 10 输出: 12 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 ...

  5. 配置android studio环境

    配置java jdk 1.1运行exe 程序 1.2配置jdk 环境变量 添加环境变量 ;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin .;%JAVA_HOME%\lib;% ...

  6. iview 小问题笔记

    总结一下用 iview 组件期间遇到的小问题.一边做项目一边做笔记. 官方文档:iview API 按需引入: 1,iview 分页组件 <Page :total="aboutPage ...

  7. web服务器--nginx简介

    nginx 介绍Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务.Nginx是一款轻量级的Web 服务器/反向代理服务器及电 ...

  8. C++/CLI 创建WPF程序

    本文简单演示下用C++/CLI创建WPF程序,IDE为VS2015 首先创建CLR项目,选择CLR空项目: 然后,右键源文件,选择新建class,选择CLR->Component Class 接 ...

  9. Orleans 整体介绍

    背景 Orleans 是微软开源的Actor模型开发框架. Actor模型 此模型解决了并发编程时对资源竞争使用的问题,将对同一个业务数据的访问从并行变为串行执行,降低了多线程编程的难度,使普通编程人 ...

  10. Zookeeper 扫盲

    Zookeeper 扫盲 :disappointed_relieved: 配置文件详解: tickTime:基本事件单元,以毫秒为单位,这个时间作为 Zookeeper 服务器之间或客户端之间维持心跳 ...