前言

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

  1. 光栅化状态(光栅化阶段)
  2. 采样器状态(像素着色阶段)
  3. 混合状态(输出合并阶段)
  4. 深度/模板状态(输出合并阶段)

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

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

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;
// 初始化木箱
HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, texture.GetAddressOf()));
m_WoodCrate.SetBuffer(m_pd3dDevice.Get(), Geometry::CreateBox());
m_WoodCrate.SetTexture(texture.Get()); // 初始化地板
HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\floor.dds", nullptr, texture.ReleaseAndGetAddressOf()));
m_Floor.SetBuffer(m_pd3dDevice.Get(),
Geometry::CreatePlane(XMFLOAT2(20.0f, 20.0f), XMFLOAT2(5.0f, 5.0f)));
m_Floor.SetTexture(texture.Get());
m_Floor.GetTransform().SetPosition(0.0f, -1.0f, 0.0f); // 初始化墙体
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(XMFLOAT2(20.0f, 8.0f), XMFLOAT2(5.0f, 1.5f)));
Transform& transform = m_Walls[i].GetTransform();
transform.SetRotation(-XM_PIDIV2, XM_PIDIV2 * i, 0.0f);
transform.SetPosition(i % 2 ? -10.0f * (i - 2) : 0.0f, 3.0f, i % 2 == 0 ? -10.0f * (i - 1) : 0.0f);
m_Walls[i].SetTexture(texture.Get());
} // 初始化采样器状态
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(m_pd3dDevice->CreateSamplerState(&sampDesc, m_pSamplerState.GetAddressOf())); // ******************
// 初始化常量缓冲区的值
// 初始化每帧可能会变化的值
m_CameraMode = CameraMode::FirstPerson;
auto camera = std::shared_ptr<FirstPersonCamera>(new FirstPersonCamera);
m_pCamera = camera;
camera->SetViewPort(0.0f, 0.0f, (float)m_ClientWidth, (float)m_ClientHeight);
camera->LookAt(XMFLOAT3(), XMFLOAT3(0.0f, 0.0f, 1.0f), XMFLOAT3(0.0f, 1.0f, 0.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, 10.0f, 0.0f);
m_CBRarely.pointLight[0].ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_CBRarely.pointLight[0].diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
m_CBRarely.pointLight[0].specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 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;
// 初始化材质
m_CBRarely.material.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_CBRarely.material.diffuse = XMFLOAT4(0.6f, 0.6f, 0.6f, 1.0f);
m_CBRarely.material.specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 50.0f); // 更新不容易被修改的常量缓冲区资源
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); // ******************
// 给渲染管线各个阶段绑定好所需资源
// 设置图元类型,设定输入布局
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout3D.Get());
// 默认绑定3D着色器
m_pd3dImmediateContext->VSSetShader(m_pVertexShader3D.Get(), nullptr, 0);
// 预先绑定各自所需的缓冲区,其中每帧更新的缓冲区需要绑定到两个缓冲区上
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()); 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, m_pSamplerState.GetAddressOf()); 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_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); // 篱笆盒稍微抬起一点高度
Transform& wireFrameTransform = m_WireFence.GetTransform();
wireFrameTransform.SetPosition(2.0f, 0.01f, 0.0f);
m_WireFence.Draw(m_pd3dImmediateContext.Get());
wireFrameTransform.SetPosition(-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--07 添加光照与常用几何模型、光栅化状态

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

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

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

  3. windows sdk编程禁止窗体最大化最小化

    #include <windows.h> /*消息处理函数声明*/ HRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM ...

  4. DirectX11 With Windows SDK--00 目录

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

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

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

  6. DirectX11 With Windows SDK--13 动手实现一个简易Effects框架、阴影效果绘制

    前言 到现在为止,所有的教程项目都没有使用Effects11框架类来管理资源.因为在D3DCompile API (#47)版本中,如果你尝试编译fx_5_0的效果文件,会收到这样的警告: X4717 ...

  7. DirectX11 With Windows SDK--14 深度测试

    前言 当使用加法/减法/乘法颜色混合,或者使用透明混合的时候,在经过深度测试时可能会引发一些问题.例如现在我们需要使用加法混合来绘制一系列对象,而这些对象彼此之间不会相互阻挡.若我们仍使用原来的深度测 ...

  8. DirectX11 With Windows SDK--11 混合状态

    原文:DirectX11 With Windows SDK--11 混合状态 前言 这一章会着重讲述混合状态,在下一章则会讲述深度/模板状态 DirectX11 With Windows SDK完整目 ...

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

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

随机推荐

  1. 进程命令(tasklist)

    TaskList命令: // 描述: 显示本地或远程计算机上正在运行的进程列表信息. // 语法: tasklist [/s <computer> [ /u [<domain> ...

  2. 【Linux基础】查看某一端口是否开放(1025为例)

    1.使用lsof 命令来查看端口是否开放 lsof -i:1025 //如果有显示说明已经开放了,如果没有显示说明没有开放 lsof(list open files)是一个列出当前系统打开文件的工具. ...

  3. Python开发【第二篇】运算符

    "+" 加号 __author__ = 'Tang' a = 8 b = 9 c = a + b a = 8.0 b = 9 c = a + b print(c) # 17.0 a ...

  4. CentOS 7 中使用NTP进行时间同步

    1. NTP时钟同步方式说明NTP在linux下有两种时钟同步方式,分别为直接同步和平滑同步: 直接同步 使用ntpdate命令进行同步,直接进行时间变更.如果服务器上存在一个12点运行的任务,当前服 ...

  5. 通过supper()有参构造器,完成子类对象调用父类属性的方法,并完成赋值

    package com.Summer_0426.cn; /** * @author Summer * 通过supper()有参构造器,完成子类对象调用父类属性的方法,并完成赋值 * */ public ...

  6. redis 初步认识四(redis锁,防并发)

    using System; namespace ConsoleAppRedis { class Program { static void Main(string[] args) { //第一种,无登 ...

  7. Cesium如何通过addImageryProvider方法加载SkylineGlobe Server发布的WMS服务

    某某某单位用SkylineGlobeServer7版本发布了好些服务,然后让我们在Cesium里都加载进来展示. 其实只要符合OGC标准的,加进来还是很容易的. 示例代码如下: function te ...

  8. Vue-项目打包上线

    一.打包生成dist目录 运行npm run build 进行打包,控制台显示“Build complete”表示打包完成了. npm run build 二.dist目录放到后端跟目录 打包后生成一 ...

  9. 【翻译】WhatsApp 加密概述(技术白皮书)

    目录      简介      术语      客户端注册      会话初始化设置      接收会话设置      交换信息      传输媒体和附件      群组消息      通话设置   ...

  10. 循环语句之for循环

    什么是循环语句? 循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句,当反复执行这个循环体时,需要在合适的时候把循环判断条件修改为false,从而结束循环,否 ...