到如今为止,全部的教程项目都没有使用Effects11框架类来管理资源。由于在D3DCompile API (#47)版本中,若是你尝试编译fx_5_0的效果文件,会收到这样的警告:
X4717: Effects deprecated for D3DCompiler_47
html
在将来的版本中,D3DCompiler可能会中止对FX11的支持,因此咱们须要自行去管理各类特效,并改用HLSL编译器去编译每个着色器。同时,在阅读本章以前,你须要先学习本系列前面的一些重点章节再继续:git
章节目录 |
---|
01 DirectX11初始化 |
02 顶点/像素着色器的建立、顶点缓冲区 |
03 索引缓冲区、常量缓冲区 |
09 纹理映射与采样器状态 |
11 混合状态与光栅化状态 |
12 深度/模板状态、反射绘制 |
在DirectXTK中的Effects.h
能够看到它实现了一系列Effects管理类,相比Effects11
框架库,它缺乏了反射机制,而且使用的是它内部已经写好、编译好的着色器。DirectXTK的Effects也只不过是为了简化游戏开发流程而设计出来的。固然,里面的一部分源码实现也值得咱们去学习。github
注意:这章经历了一次十分大的改动,原先所使用的BasicEffect类由于在后续的章节中发现很难扩展,因此进行了一次大幅度重构。并会逐渐替换掉后面教程的项目源码所使用的BasicEffect。数组
在这一章的学习事后,你将会理解Effects11
的一部分运做机制是怎样的。而关于它的反射机制、着色器编译部分不会进行探讨。app
这篇教程还会提到用深度/模板状态去实现简单的阴影效果,但不会深刻数学公式原理。框架
DirectX11 With Windows SDK完整目录ide
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。布局
目前的RenderStates
类存放有比较经常使用的各类状态,原来在Effects11
框架下是能够在fx文件初始化各类渲染状态,并设置到Technique11
中。但如今咱们只能在C++代码层中一次性建立好各类所需的渲染状态:学习
class RenderStates { public: template <class T> using ComPtr = Microsoft::WRL::ComPtr<T>; static bool IsInit(); static void InitAll(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; // 深度/模板状态:仅深度测试,不写入深度值 };
具体的设置能够参照源码或者上一章内容。
该Effects框架支持的功能以下:
不过它也有这样的缺陷:
此外,该框架内部会对矩阵进行转置,所以在传递矩阵给Effects时只须要传递默认的行主矩阵便可。
首先是文件结构:
其中可以暴露给程序使用的只有头文件Effects.h
,里面能够存放多套不一样的特效框架类的声明,而关于每一个框架类的实现部分都应当用一个独立的源文件存放。而EffectHelper.h
则是用来帮助管理常量缓冲区的,服务于各类框架类的实现部分以及所属的源文件,所以不该该直接使用。
理论上它也是能够作成静态库使用的,而后着色器代码稳定后也不该当变更。在使用的时候只须要包含头文件Effects.h
便可。
该头文件包含了一些有用的东西,但它须要在包含特效类实现的源文件中使用,且必须晚于Effects.h
和d3dUtil.h
包含。
有些类型须要在堆上按16字节对齐,好比XMVECTOR
和XMMATRIX
,虽说拿这些对象做为类的成员不太合适,毕竟分配在堆上的话基本上没法保证内存按16字节对齐了,但仍是但愿可以作到。在VS的corecrt_malloc.h
(只要有包含stdlib.h
, malloc.h
之一的头文件均可以)中有这样的一个函数:_aligned_malloc
,它能够指定须要分配的内存字节大小以及按多少字节对齐。其中对齐值必须为2的整数次幂的字节数。
void * _aligned_malloc( size_t size, // [In]分配内存字节数 size_t alignment // [In]按多少字节内存来对齐 );
若一个类中包含有已经指定内存对齐的成员,则须要优先把这些成员放到最前。
而后与之对应的就是_aligned_free
函数了,它能够释放以前由_aligned_malloc
分配获得的内存。
下面是类模板AlignedType
的实现,让须要内存对齐的类去继承该类便可。它重载了operator new
和operator delete
的实现:
// 若类须要内存对齐,从该类派生 template<class DerivedType> struct AlignedType { static void* operator new(size_t size) { const size_t alignedSize = __alignof(DerivedType); static_assert(alignedSize > 8, "AlignedNew is only useful for types with > 8 byte alignment! Did you forget a __declspec(align) on DerivedType?"); void* ptr = _aligned_malloc(size, alignedSize); if (!ptr) throw std::bad_alloc(); return ptr; } static void operator delete(void * ptr) { _aligned_free(ptr); } };
须要注意的是,继承AlignedType
的类或者其成员必须自己有__declspec(align)
的标识。如果内部成员,在全部包含该标识的值中最大的align
值 必须是2的整数次幂且必须大于8。
下面演示了正确的和错误的行为:
// 错误!VertexPosColor按4字节对齐! struct VertexPosColor : AlignedType<VertexPos> { XMFLOAT3 pos; XMFLOAT4 color; }; // 正确!Data按16字节对齐,由于pos自己是按16字节对齐的。 struct Data : AlignedType<VertexPos> { XMVECTOR pos; int val; }; // 正确!Vector类按16字节对齐 __declspec(align(16)) struct Vector : AlignedType<Vector> { float x; float y; float z; float w; };
这里AlignedType<T>
主要是用于BasicEffect::Impl
类,由于其内部包含了XMVECTOR
和XMMATRIX
类型的成员,且该类须要分配在堆上。
一个常量缓冲区可能会被建立、更新或者绑定到管线。若常量缓冲区的值没有发生变化,咱们不但愿它进行无心义的更新。这里可使用一个dirty
标记,确认它是否被修改过。在Effects
调用Apply
后,若是常量缓冲区的任一内部成员发生修改的话,咱们就将数据更新到常量缓冲区并恢复该标记。
首先是抽象基类CBufferBase
:
struct CBufferBase { template<class T> using ComPtr = Microsoft::WRL::ComPtr<T>; CBufferBase() : isDirty() {} ~CBufferBase() = default; BOOL isDirty; ComPtr<ID3D11Buffer> cBuffer; virtual HRESULT CreateBuffer(ID3D11Device * device) = 0; virtual void UpdateBuffer(ID3D11DeviceContext * deviceContext) = 0; virtual void BindVS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindHS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindDS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindGS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindCS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindPS(ID3D11DeviceContext * deviceContext) = 0; };
这么作是为了方便咱们放入数组进行遍历。
而后是派生类CBufferObject
,startSlot
指定了HLSL对应cbuffer的索引,T
则是C++对应的结构体,存储临时数据:
template<UINT startSlot, class T> struct CBufferObject : CBufferBase { T data; CBufferObject() : CBufferBase(), data() {} HRESULT CreateBuffer(ID3D11Device * device) override { if (cBuffer != nullptr) return S_OK; 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; cbd.ByteWidth = sizeof(T); return device->CreateBuffer(&cbd, nullptr, cBuffer.GetAddressOf()); } void UpdateBuffer(ID3D11DeviceContext * deviceContext) override { if (isDirty) { isDirty = false; D3D11_MAPPED_SUBRESOURCE mappedData; deviceContext->Map(cBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData); memcpy_s(mappedData.pData, sizeof(T), &data, sizeof(T)); deviceContext->Unmap(cBuffer.Get(), 0); } } void BindVS(ID3D11DeviceContext * deviceContext) override { deviceContext->VSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf()); } void BindHS(ID3D11DeviceContext * deviceContext) override { deviceContext->HSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf()); } void BindDS(ID3D11DeviceContext * deviceContext) override { deviceContext->DSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf()); } void BindGS(ID3D11DeviceContext * deviceContext) override { deviceContext->GSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf()); } void BindCS(ID3D11DeviceContext * deviceContext) override { deviceContext->CSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf()); } void BindPS(ID3D11DeviceContext * deviceContext) override { deviceContext->PSSetConstantBuffers(startSlot, 1, cBuffer.GetAddressOf()); } };
关于常量缓冲区临时变量的修改则在后续的内容。
首先是抽象基类IEffects
,它仅容许被移动,而且仅包含Apply
方法。
class IEffect { public: // 使用模板别名(C++11)简化类型名 template <class T> using ComPtr = Microsoft::WRL::ComPtr<T>; IEffect() = default; // 不支持复制构造 IEffect(const IEffect&) = delete; IEffect& operator=(const IEffect&) = delete; // 容许转移 IEffect(IEffect&& moveFrom) = default; IEffect& operator=(IEffect&& moveFrom) = default; virtual ~IEffect() = default; // 更新并绑定常量缓冲区 virtual void Apply(ID3D11DeviceContext * deviceContext) = 0; };
原来的ID3DX11EffectPass
包含的方法Apply
用于在各个着色器阶段绑定所须要的常量缓冲区、纹理等资源,并更新以前有所修改的常量缓冲区。如今咱们实现Effects框架中的Apply
方法也是这么作的。
而后是派生类BasicEffect
,从它的方法来看,包含了单例获取、渲染状态的切换、修改常量缓冲区某一成员的值、应用变动四个大块:
class BasicEffect : public IEffect { public: BasicEffect(); virtual ~BasicEffect() override; BasicEffect(BasicEffect&& moveFrom) noexcept; BasicEffect& operator=(BasicEffect&& moveFrom) noexcept; // 获取单例 static BasicEffect& Get(); // 初始化Basic.hlsli所需资源并初始化渲染状态 bool InitAll(ID3D11Device * device); // // 渲染模式的变动 // // 默认状态来绘制 void SetRenderDefault(ID3D11DeviceContext * deviceContext); // Alpha混合绘制 void SetRenderAlphaBlend(ID3D11DeviceContext * deviceContext); // 无二次混合 void SetRenderNoDoubleBlend(ID3D11DeviceContext * deviceContext, UINT stencilRef); // 仅写入模板值 void SetWriteStencilOnly(ID3D11DeviceContext * deviceContext, UINT stencilRef); // 对指定模板值的区域进行绘制,采用默认状态 void SetRenderDefaultWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef); // 对指定模板值的区域进行绘制,采用Alpha混合 void SetRenderAlphaBlendWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef); // 2D默认状态绘制 void Set2DRenderDefault(ID3D11DeviceContext * deviceContext); // 2D混合绘制 void Set2DRenderAlphaBlend(ID3D11DeviceContext * deviceContext); // // 矩阵设置 // void XM_CALLCONV SetWorldMatrix(DirectX::FXMMATRIX W); void XM_CALLCONV SetViewMatrix(DirectX::FXMMATRIX V); void XM_CALLCONV SetProjMatrix(DirectX::FXMMATRIX P); void XM_CALLCONV SetReflectionMatrix(DirectX::FXMMATRIX R); void XM_CALLCONV SetShadowMatrix(DirectX::FXMMATRIX S); void XM_CALLCONV SetRefShadowMatrix(DirectX::FXMMATRIX RefS); // // 光照、材质和纹理相关设置 // // 各类类型灯光容许的最大数目 static const int maxLights = 5; void SetDirLight(size_t pos, const DirectionalLight& dirLight); void SetPointLight(size_t pos, const PointLight& pointLight); void SetSpotLight(size_t pos, const SpotLight& spotLight); void SetMaterial(const Material& material); void SetTexture(ID3D11ShaderResourceView * texture); void XM_CALLCONV SetEyePos(DirectX::FXMVECTOR eyePos); // // 状态开关设置 // void SetReflectionState(bool isOn); void SetShadowState(bool isOn); // 应用常量缓冲区和纹理资源的变动 void Apply(ID3D11DeviceContext * deviceContext); private: class Impl; std::unique_ptr<Impl> pImpl; };
XM_CALLCONV
即在第六章以前提到的__vectorcall
或__fastcall
约定。
而后来到BasicEffect.cpp
,首先包含了对应HLSL五个cbuffer
的C++结构体:
#include "Effects.h" #include "EffectHelper.h" #include "Vertex.h" #include <d3dcompiler.h> #include <experimental/filesystem> using namespace DirectX; using namespace std::experimental;
EffectHelper.h
须要放在Effects.h
以后。
这5个结构体都放在源文件是由于这些结构体仅限于在该文件种使用。
以前在BasicEffect
中声明了Impl
类,主要目的是为了将类的成员和方法定义都转移到源文件中,而且还包含了HLSL五个cbuffer的C++结构体。不只能够减小BasicEffect
类的压力,还能够避免暴露上面的五个结构体:
class BasicEffect::Impl : public AlignedType<BasicEffect::Impl> { public: // // 这些结构体对应HLSL的结构体。须要按16字节对齐 // struct CBChangesEveryDrawing { DirectX::XMMATRIX world; DirectX::XMMATRIX worldInvTranspose; Material material; }; struct CBDrawingStates { int isReflection; int isShadow; DirectX::XMINT2 pad; }; struct CBChangesEveryFrame { DirectX::XMMATRIX view; DirectX::XMVECTOR eyePos; }; struct CBChangesOnResize { DirectX::XMMATRIX proj; }; struct CBChangesRarely { DirectX::XMMATRIX reflection; DirectX::XMMATRIX shadow; DirectX::XMMATRIX refShadow; DirectionalLight dirLight[BasicEffect::maxLights]; PointLight pointLight[BasicEffect::maxLights]; SpotLight spotLight[BasicEffect::maxLights]; }; public: // 必须显式指定 Impl() : m_IsDirty() {} ~Impl() = default; public: // 须要16字节对齐的优先放在前面 CBufferObject<0, CBChangesEveryDrawing> m_CBDrawing; // 每次对象绘制的常量缓冲区 CBufferObject<1, CBDrawingStates> m_CBStates; // 每次绘制状态变动的常量缓冲区 CBufferObject<2, CBChangesEveryFrame> m_CBFrame; // 每帧绘制的常量缓冲区 CBufferObject<3, CBChangesOnResize> m_CBOnResize; // 每次窗口大小变动的常量缓冲区 CBufferObject<4, CBChangesRarely> m_CBRarely; // 几乎不会变动的常量缓冲区 BOOL m_IsDirty; // 是否有值变动 std::vector<CBufferBase*> m_pCBuffers; // 统一管理上面全部的常量缓冲区 ComPtr<ID3D11VertexShader> m_pVertexShader3D; // 用于3D的顶点着色器 ComPtr<ID3D11PixelShader> m_pPixelShader3D; // 用于3D的像素着色器 ComPtr<ID3D11VertexShader> m_pVertexShader2D; // 用于2D的顶点着色器 ComPtr<ID3D11PixelShader> m_pPixelShader2D; // 用于2D的像素着色器 ComPtr<ID3D11InputLayout> m_pVertexLayout2D; // 用于2D的顶点输入布局 ComPtr<ID3D11InputLayout> m_pVertexLayout3D; // 用于3D的顶点输入布局 ComPtr<ID3D11ShaderResourceView> m_pTexture; // 用于绘制的纹理 };
这里用一个匿名空间保管单例对象的指针。当有一个实例被构造出来的时候就会给其赋值。后续就不容许再被实例化了,可使用Get
方法获取该单例。
namespace { // BasicEffect单例 static BasicEffect * g_pInstance = nullptr; } BasicEffect::BasicEffect() { if (g_pInstance) throw std::exception("BasicEffect is a singleton!"); g_pInstance = this; pImpl = std::make_unique<BasicEffect::Impl>(); } BasicEffect::~BasicEffect() { } BasicEffect::BasicEffect(BasicEffect && moveFrom) noexcept { pImpl.swap(moveFrom.pImpl); } BasicEffect & BasicEffect::operator=(BasicEffect && moveFrom) noexcept { pImpl.swap(moveFrom.pImpl); return *this; } BasicEffect & BasicEffect::Get() { if (!g_pInstance) throw std::exception("BasicEffect needs an instance!"); return *g_pInstance; }
BasicEffect::InitAll
方法负责建立出全部的着色器和常量缓冲区,以及全部的渲染状态:
bool BasicEffect::InitAll(ID3D11Device * device) { if (!device) return false; if (!pImpl->m_pCBuffers.empty()) return true; if (!RenderStates::IsInit()) throw std::exception("RenderStates need to be initialized first!"); ComPtr<ID3DBlob> blob; // 建立顶点着色器(2D) HR(CreateShaderFromFile(L"HLSL\\Basic_VS_2D.cso", L"HLSL\\Basic_VS_2D.hlsl", "VS_2D", "vs_5_0", blob.GetAddressOf())); HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pVertexShader2D.GetAddressOf())); // 建立顶点布局(2D) HR(device->CreateInputLayout(VertexPosTex::inputLayout, ARRAYSIZE(VertexPosTex::inputLayout), blob->GetBufferPointer(), blob->GetBufferSize(), pImpl->m_pVertexLayout2D.GetAddressOf())); // 建立像素着色器(2D) HR(CreateShaderFromFile(L"HLSL\\Basic_PS_2D.cso", L"HLSL\\Basic_PS_2D.hlsl", "PS_2D", "ps_5_0", blob.ReleaseAndGetAddressOf())); HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pPixelShader2D.GetAddressOf())); // 建立顶点着色器(3D) HR(CreateShaderFromFile(L"HLSL\\Basic_VS_3D.cso", L"HLSL\\Basic_VS_3D.hlsl", "VS_3D", "vs_5_0", blob.ReleaseAndGetAddressOf())); HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pVertexShader3D.GetAddressOf())); // 建立顶点布局(3D) HR(device->CreateInputLayout(VertexPosNormalTex::inputLayout, ARRAYSIZE(VertexPosNormalTex::inputLayout), blob->GetBufferPointer(), blob->GetBufferSize(), pImpl->m_pVertexLayout3D.GetAddressOf())); // 建立像素着色器(3D) HR(CreateShaderFromFile(L"HLSL\\Basic_PS_3D.cso", L"HLSL\\Basic_PS_3D.hlsl", "PS_3D", "ps_5_0", blob.ReleaseAndGetAddressOf())); HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pPixelShader3D.GetAddressOf())); pImpl->m_pCBuffers.assign({ &pImpl->m_CBDrawing, &pImpl->m_CBFrame, &pImpl->m_CBStates, &pImpl->m_CBOnResize, &pImpl->m_CBRarely}); // 建立常量缓冲区 for (auto& pBuffer : pImpl->m_pCBuffers) { HR(pBuffer->CreateBuffer(device)); } return true; }
下面全部的渲染模式使用的是线性Wrap采样器。
BasicEffect::SetRenderDefault
方法使用了默认的3D像素着色器和顶点着色器,而且其他各状态都保留使用默认状态:
void BasicEffect::SetRenderDefault(ID3D11DeviceContext * deviceContext) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0); deviceContext->RSSetState(nullptr); deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(nullptr, 0); deviceContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); }
该绘制模式关闭了光栅化裁剪,并采用透明混合方式。
void BasicEffect::SetRenderAlphaBlend(ID3D11DeviceContext * deviceContext) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0); deviceContext->RSSetState(RenderStates::RSNoCull.Get()); deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(nullptr, 0); deviceContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF); }
该绘制模式用于绘制阴影,防止过分混合。须要指定绘制区域的模板值。
void BasicEffect::SetRenderNoDoubleBlend(ID3D11DeviceContext * deviceContext, UINT stencilRef) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0); deviceContext->RSSetState(RenderStates::RSNoCull.Get()); deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDoubleBlend.Get(), stencilRef); deviceContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF); }
该模式用于向模板缓冲区写入用户指定的模板值,而且不写入到深度缓冲区和后备缓冲区。
void BasicEffect::SetWriteStencilOnly(ID3D11DeviceContext * deviceContext, UINT stencilRef) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0); deviceContext->RSSetState(nullptr); deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(RenderStates::DSSWriteStencil.Get(), stencilRef); deviceContext->OMSetBlendState(RenderStates::BSNoColorWrite.Get(), nullptr, 0xFFFFFFFF); }
该模式下,仅对模板缓冲区的模板值和用户指定的相等的区域进行常规绘制。
void BasicEffect::SetRenderDefaultWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0); deviceContext->RSSetState(RenderStates::RSCullClockWise.Get()); deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(RenderStates::DSSDrawWithStencil.Get(), stencilRef); deviceContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); }
该模式下,仅对模板缓冲区的模板值和用户指定的相等的区域进行Alpha透明混合绘制。
void BasicEffect::SetRenderAlphaBlendWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0); deviceContext->RSSetState(RenderStates::RSNoCull.Get()); deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(RenderStates::DSSDrawWithStencil.Get(), stencilRef); deviceContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF); }
该模式使用的是2D顶点着色器和像素着色器,并修改成2D输入布局。
void BasicEffect::Set2DRenderDefault(ID3D11DeviceContext * deviceContext) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout2D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader2D.Get(), nullptr, 0); deviceContext->RSSetState(nullptr); deviceContext->PSSetShader(pImpl->m_pPixelShader2D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(nullptr, 0); deviceContext->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); }
相比上面,多了透明混合状态。
void BasicEffect::Set2DRenderAlphaBlend(ID3D11DeviceContext * deviceContext) { deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexLayout2D.Get()); deviceContext->VSSetShader(pImpl->m_pVertexShader2D.Get(), nullptr, 0); deviceContext->RSSetState(RenderStates::RSNoCull.Get()); deviceContext->PSSetShader(pImpl->m_pPixelShader2D.Get(), nullptr, 0); deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf()); deviceContext->OMSetDepthStencilState(nullptr, 0); deviceContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF); }
下面这些全部的方法会更新CBufferObject
中的临时数据,数据脏标记被设为true
:
void XM_CALLCONV BasicEffect::SetWorldMatrix(DirectX::FXMMATRIX W) { auto& cBuffer = pImpl->m_CBDrawing; cBuffer.data.world = XMMatrixTranspose(W); cBuffer.data.worldInvTranspose = XMMatrixInverse(nullptr, W); // 两次转置抵消 pImpl->m_IsDirty = cBuffer.isDirty = true; } void XM_CALLCONV BasicEffect::SetViewMatrix(FXMMATRIX V) { auto& cBuffer = pImpl->m_CBFrame; cBuffer.data.view = XMMatrixTranspose(V); pImpl->m_IsDirty = cBuffer.isDirty = true; } void XM_CALLCONV BasicEffect::SetProjMatrix(FXMMATRIX P) { auto& cBuffer = pImpl->m_CBOnResize; cBuffer.data.proj = XMMatrixTranspose(P); pImpl->m_IsDirty = cBuffer.isDirty = true; } void XM_CALLCONV BasicEffect::SetReflectionMatrix(FXMMATRIX R) { auto& cBuffer = pImpl->m_CBRarely; cBuffer.data.reflection = XMMatrixTranspose(R); pImpl->m_IsDirty = cBuffer.isDirty = true; } void XM_CALLCONV BasicEffect::SetShadowMatrix(FXMMATRIX S) { auto& cBuffer = pImpl->m_CBRarely; cBuffer.data.shadow = XMMatrixTranspose(S); pImpl->m_IsDirty = cBuffer.isDirty = true; } void XM_CALLCONV BasicEffect::SetRefShadowMatrix(DirectX::FXMMATRIX RefS) { auto& cBuffer = pImpl->m_CBRarely; cBuffer.data.refShadow = XMMatrixTranspose(RefS); pImpl->m_IsDirty = cBuffer.isDirty = true; } void BasicEffect::SetDirLight(size_t pos, const DirectionalLight & dirLight) { auto& cBuffer = pImpl->m_CBRarely; cBuffer.data.dirLight[pos] = dirLight; pImpl->m_IsDirty = cBuffer.isDirty = true; } void BasicEffect::SetPointLight(size_t pos, const PointLight & pointLight) { auto& cBuffer = pImpl->m_CBRarely; cBuffer.data.pointLight[pos] = pointLight; pImpl->m_IsDirty = cBuffer.isDirty = true; } void BasicEffect::SetSpotLight(size_t pos, const SpotLight & spotLight) { auto& cBuffer = pImpl->m_CBRarely; cBuffer.data.spotLight[pos] = spotLight; pImpl->m_IsDirty = cBuffer.isDirty = true; } void BasicEffect::SetMaterial(const Material & material) { auto& cBuffer = pImpl->m_CBDrawing; cBuffer.data.material = material; pImpl->m_IsDirty = cBuffer.isDirty = true; } void BasicEffect::SetTexture(ID3D11ShaderResourceView * m_pTexture) { pImpl->m_pTexture = m_pTexture; } void XM_CALLCONV BasicEffect::SetEyePos(FXMVECTOR eyePos) { auto& cBuffer = pImpl->m_CBFrame; cBuffer.data.eyePos = eyePos; pImpl->m_IsDirty = cBuffer.isDirty = true; } void BasicEffect::SetReflectionState(bool isOn) { auto& cBuffer = pImpl->m_CBStates; cBuffer.data.isReflection = isOn; pImpl->m_IsDirty = cBuffer.isDirty = true; } void BasicEffect::SetShadowState(bool isOn) { auto& cBuffer = pImpl->m_CBStates; cBuffer.data.isShadow = isOn; pImpl->m_IsDirty = cBuffer.isDirty = true; }
BasicEffect::Apply
首先将所须要用到的缓冲区绑定到渲染管线上,并设置纹理,而后才是视状况更新常量缓冲区。
下面的缓冲区数组索引值同时也对应了以前编译期指定的startSlot
值。
首先检验总的脏标记是否为true
,如有任意数据被修改,则检验每一个常量缓冲区的脏标记,并根据该标记决定是否要更新常量缓冲区。
void BasicEffect::Apply(ID3D11DeviceContext * deviceContext) { auto& pCBuffers = pImpl->m_pCBuffers; // 将缓冲区绑定到渲染管线上 pCBuffers[0]->BindVS(deviceContext); pCBuffers[1]->BindVS(deviceContext); pCBuffers[2]->BindVS(deviceContext); pCBuffers[3]->BindVS(deviceContext); pCBuffers[4]->BindVS(deviceContext); pCBuffers[0]->BindPS(deviceContext); pCBuffers[1]->BindPS(deviceContext); pCBuffers[2]->BindPS(deviceContext); pCBuffers[4]->BindPS(deviceContext); // 设置纹理 deviceContext->PSSetShaderResources(0, 1, pImpl->m_pTexture.GetAddressOf()); if (pImpl->m_IsDirty) { pImpl->m_IsDirty = false; for (auto& pCBuffer : pCBuffers) { pCBuffer->UpdateBuffer(deviceContext); } } }
固然,目前BasicEffect
能作的事情仍是比较有限的,而且还须要随着HLSL代码的变更而随之调整。更多的功能会在后续教程中实现。
使用XMMatrixShadow
能够生成阴影矩阵,根据光照类型和位置对几何体投影到平面上的。
XMMATRIX XMMatrixShadow( FXMVECTOR ShadowPlane, // 平面向量(nx, ny, nz, d) FXMVECTOR LightPosition); // w = 0时表示平行光方向, w = 1时表示光源位置
一般指定的平面会稍微比实际平面高那么一点点,以免深度缓冲区资源争夺致使阴影显示有问题。
一个物体投影到平面上时,投影区域的某些位置可能位于多个三角形以内,这会致使这些位置会有多个像素经过测试并进行混合操做,渲染的次数越多,显示的颜色会越黑。
咱们可使用模板缓冲区来解决这个问题。
RenderStates::DSSNoDoubleBlend
的深度模板状态,当给定的模板值和深度/模板缓冲区的模板值一致时,经过模板测试并对模板值加1,绘制该像素的混合,而后下一次因为给定的模板值比深度/模板缓冲区的模板值小1,不会再经过模板测试,也就阻挡了后续像素的绘制;Basic_PS_2D.hlsl文件变化以下:
#include "Basic.hlsli" // 像素着色器(2D) float4 PS_2D(VertexPosHTex pIn) : SV_Target { float4 color = g_Tex.Sample(g_Sam, pIn.Tex); clip(color.a - 0.1f); return color; }
Basic_PS_3D.hlsl文件变化以下:
#include "Basic.hlsli" // 像素着色器(3D) // 像素着色器(3D) float4 PS_3D(VertexPosHWNormalTex pIn) : SV_Target { // 提早进行裁剪,对不符合要求的像素能够避免后续运算 float4 texColor = g_Tex.Sample(g_Sam, pIn.Tex); clip(texColor.a - 0.1f); // 标准化法向量 pIn.NormalW = normalize(pIn.NormalW); // 顶点指向眼睛的向量 float3 toEyeW = normalize(g_EyePosW - 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 < 5; ++i) { DirectionalLight dirLight = g_DirLight[i]; [flatten] if (g_IsReflection) { dirLight.Direction = mul(dirLight.Direction, (float3x3) (g_Reflection)); } ComputeDirectionalLight(g_Material, g_DirLight[i], pIn.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; } // 若当前在绘制反射物体,须要对光照进行反射矩阵变换 PointLight pointLight; [unroll] for (i = 0; i < 5; ++i) { pointLight = g_PointLight[i]; [flatten] if (g_IsReflection) { pointLight.Position = (float3) mul(float4(pointLight.Position, 1.0f), g_Reflection); } ComputePointLight(g_Material, pointLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; } SpotLight spotLight; // 若当前在绘制反射物体,须要对光照进行反射矩阵变换 [unroll] for (i = 0; i < 5; ++i) { spotLight = g_SpotLight[i]; [flatten] if (g_IsReflection) { spotLight.Position = (float3) mul(float4(spotLight.Position, 1.0f), g_Reflection); spotLight.Direction = mul(spotLight.Direction, (float3x3) g_Reflection); } ComputeSpotLight(g_Material, 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 * g_Material.Diffuse.a; return litColor; }
Basic_VS_2D.hlsl变化以下:
#include "Basic.hlsli" // 顶点着色器(2D) VertexPosHTex VS_2D(VertexPosTex vIn) { VertexPosHTex vOut; vOut.PosH = float4(vIn.PosL, 1.0f); vOut.Tex = vIn.Tex; return vOut; }
Basic_VS_3D.hlsl变化以下:
#include "Basic.hlsli" // 顶点着色器(3D) VertexPosHWNormalTex VS_3D(VertexPosNormalTex vIn) { VertexPosHWNormalTex vOut; matrix viewProj = mul(g_View, g_Proj); float4 posW = mul(float4(vIn.PosL, 1.0f), g_World); float3 normalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose); // 若当前在绘制反射物体,先进行反射操做 [flatten] if (g_IsReflection) { posW = mul(posW, g_Reflection); normalW = mul(normalW, (float3x3) g_Reflection); } // 若当前在绘制阴影,先进行投影操做 [flatten] if (g_IsShadow) { posW = (g_IsReflection ? mul(posW, g_RefShadow) : mul(posW, g_Shadow)); } vOut.PosH = mul(posW, viewProj); vOut.PosW = posW.xyz; vOut.NormalW = normalW; vOut.Tex = vIn.Tex; return vOut; }
因为GameObject
类也承担了绘制方法,那么最后的Apply
也须要交给游戏对象来调用。所以GameObject::Draw
方法变动以下:
void GameObject::Draw(ID3D11DeviceContext * deviceContext, BasicEffect& effect) { // 设置顶点/索引缓冲区 UINT strides = m_VertexStride; UINT offsets = 0; deviceContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &strides, &offsets); deviceContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0); // 更新数据并应用 effect.SetWorldMatrix(XMLoadFloat4x4(&m_WorldMatrix)); effect.SetTexture(m_pTexture); effect.SetMaterial(m_Material); effect.Apply(deviceContext); deviceContext->DrawIndexed(m_IndexCount, 0, 0); }
如今场景只有墙体、地板、木箱和镜面。
// ********************* // 1. 给镜面反射区域写入值1到模板缓冲区 // m_BasicEffect.SetWriteStencilOnly(m_pd3dImmediateContext.Get(), 1); m_Mirror.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
// *********************** // 2. 绘制不透明的反射物体 // // 开启反射绘制 m_BasicEffect.SetReflectionState(true); m_BasicEffect.SetRenderDefaultWithStencil(m_pd3dImmediateContext.Get(), 1); m_Walls[2].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); m_Walls[3].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); m_Walls[4].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); m_Floor.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
// *********************** // 3. 绘制不透明反射物体的阴影 // m_WoodCrate.SetMaterial(m_ShadowMat); m_BasicEffect.SetShadowState(true); // 反射开启,阴影开启 m_BasicEffect.SetRenderNoDoubleBlend(m_pd3dImmediateContext.Get(), 1); m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); // 恢复到原来的状态 m_BasicEffect.SetShadowState(false); m_WoodCrate.SetMaterial(m_WoodCrateMat);
// *********************** // 4. 绘制透明镜面 // // 关闭反射绘制 m_BasicEffect.SetReflectionState(false); m_BasicEffect.SetRenderAlphaBlendWithStencil(m_pd3dImmediateContext.Get(), 1); m_Mirror.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
// ************************ // 5. 绘制不透明的正常物体 // m_BasicEffect.SetRenderDefault(m_pd3dImmediateContext.Get()); for (auto& wall : m_Walls) wall.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); m_Floor.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
// ************************ // 6. 绘制不透明正常物体的阴影 // m_WoodCrate.SetMaterial(m_ShadowMat); m_BasicEffect.SetShadowState(true); // 反射关闭,阴影开启 m_BasicEffect.SetRenderNoDoubleBlend(m_pd3dImmediateContext.Get(), 0); m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect); m_BasicEffect.SetShadowState(false); // 阴影关闭 m_WoodCrate.SetMaterial(m_WoodCrateMat);
最终绘制效果以下:
注意该样例只生成点光灯到地板的阴影。你能够用各类摄像机模式来进行测试。
DirectX11 With Windows SDK完整目录
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。