到如今爲止,全部的教程項目都沒有使用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,以及有什麼問題也能夠在這裏彙報。