DirectX11 With Windows SDK 24--Render-To-Texture(RTT)技術的應用、使用ScreenGrab儲存紋理到檔案
前言
儘管在上一章的動態天空盒中用到了Render-To-Texture技術,但那是針對紋理立方體的特化實現。考慮到該技術的應用層面非常廣,在這裡抽出獨立的一章專門來講有關它的通用實現以及各種應用。此外,這裡還會講到如何使用DirectXTex的ScreenGrab來儲存紋理,可以說是乾貨滿滿了。
如果想要看Render-To-Texture在動態天空盒的應用,可以點此回顧:
章節 |
---|
ofollow,noindex" target="_blank">23 立方體對映:動態天空盒的實現 |
DirectX11 With Windows SDK完整目錄
Github專案原始碼
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裡彙報。
再述Render-To-Texture技術
在前面的章節中,我們預設的渲染目標是來自DXGI後備緩衝區,它是一個2D紋理。而Render-To-Texture技術,實際上就是使用一張2D紋理作為渲染目標,但一般是自己新建的2D紋理。與此同時,這個紋理還能夠繫結到著色器資源檢視(SRV)供著色器所使用,即原本用作輸出的紋理現在用作輸入。
它可以用於:
- 小地圖的實現
- 陰影對映(Shadow mapping)
- 螢幕空間環境光遮蔽(Screen Space Ambient Occlusion)
- 利用天空盒實現動態反射/折射(Dynamic reflections/refractions with cube maps)
在這一章,我們將展示下面這三種應用:
- 螢幕淡入/淡出
- 小地圖(有可視範圍的)
- 儲存紋理到檔案
TextureRender類
該類借鑑了上一章 DynamicSkyEffect
的實現,因此也繼承了它簡單易用的特性:
class TextureRender { public: template<class T> using ComPtr = Microsoft::WRL::ComPtr<T>; TextureRender(ComPtr<ID3D11Device> device, int texWidth, int texHeight, bool generateMips = false); ~TextureRender(); // 開始對當前紋理進行渲染 void Begin(ComPtr<ID3D11DeviceContext> deviceContext); // 結束對當前紋理的渲染,還原狀態 void End(ComPtr<ID3D11DeviceContext> deviceContext); // 獲取渲染好的紋理 ComPtr<ID3D11ShaderResourceView> GetOutputTexture(); private: ComPtr<ID3D11ShaderResourceView>mOutputTextureSRV;// 輸出的紋理對應的著色器資源檢視 ComPtr<ID3D11RenderTargetView>mOutputTextureRTV;// 輸出的紋理對應的渲染目標檢視 ComPtr<ID3D11DepthStencilView>mOutputTextureDSV;// 輸出紋理所用的深度/模板檢視 D3D11_VIEWPORTmOutputViewPort;// 輸出所用的視口 ComPtr<ID3D11RenderTargetView>mCacheRTV;// 臨時快取的後備緩衝區 ComPtr<ID3D11DepthStencilView>mCacheDSV;// 臨時快取的深度/模板緩衝區 D3D11_VIEWPORTmCacheViewPort; // 臨時快取的視口 boolmGenerateMips;// 是否生成mipmap鏈 };
它具有如下特點:
- 支援任意寬高的紋理(在初始化時確定),因為它內建了一個獨立的深度/模板緩衝區
- 使用
Begin
和End
方法,確保在這兩個方法呼叫之間的所有繪製都將輸出到該紋理 -
Begin
方法會臨時快取後備緩衝區、深度/模板緩衝區和視口,並在End
方法恢復,因此無需自己去重新設定這些東西
但如果你想要複製出一份渲染好的紋理,請使用 ID3D11DeviceContext::CopyResource
方法。不過既然都講到這份上了,那讓我們看下該方法的介紹。
ID3D11DeviceContext::CopyResource方法--複製一份資源
該方法通過GPU將一份完整的源資源複製到目標資源:
void ID3D11DeviceContext::CopyResource( ID3D11Resource *pDstResource,// [InOut]目標資源 ID3D11Resource *pSrcResource// [In]源資源 );
但是需要注意:
- 不支援以
D3D11_USAGE_IMMUTABLE
建立的目標資源 - 兩者資源型別要一致
- 兩者不能是同一個指標
- 要有一樣的維度(包括寬度,高度,深度,大小)
- 要有相容的DXGI格式,兩者格式最好是能相同,或者至少是相同的組別,比如
DXGI_FORMAT_R32G32B32_FLOAT
,DXGI_FORMAT_R32G32B32_UINT
和DXGI_FORMAT_R32G32B32_TYPELESS
相互間就可以複製。 - 兩者任何一個在呼叫該方法的時候不能被對映(先前呼叫過
ID3D11DeviceContext::Map
方法又沒有Unmap
) - 允許深度/模板緩衝區作為源或目標資源
TextureRender初始化
現在我們需要完成下面5個步驟:
- 建立紋理
- 建立紋理對應的渲染目標檢視
- 建立紋理對應的著色器資源檢視
- 建立與紋理等寬高的深度/模板緩衝區和對應的檢視
- 初始化視口
具體程式碼如下:
TextureRender::TextureRender(ComPtr<ID3D11Device> device, int texWidth, int texHeight, bool generateMips) : mGenerateMips(generateMips) { // ****************** // 1. 建立紋理 // ComPtr<ID3D11Texture2D> texture; D3D11_TEXTURE2D_DESC texDesc; texDesc.Width = texWidth; texDesc.Height = texHeight; texDesc.MipLevels = (mGenerateMips ? 0 : 1);// 0為完整mipmap鏈 texDesc.ArraySize = 1; texDesc.SampleDesc.Count = 1; texDesc.SampleDesc.Quality = 0; texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; texDesc.Usage = D3D11_USAGE_DEFAULT; texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; texDesc.CPUAccessFlags = 0; texDesc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; // 現在texture用於新建紋理 HR(device->CreateTexture2D(&texDesc, nullptr, texture.ReleaseAndGetAddressOf())); // ****************** // 2. 建立紋理對應的渲染目標檢視 // D3D11_RENDER_TARGET_VIEW_DESC rtvDesc; rtvDesc.Format = texDesc.Format; rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; rtvDesc.Texture2D.MipSlice = 0; HR(device->CreateRenderTargetView( texture.Get(), &rtvDesc, mOutputTextureRTV.GetAddressOf())); // ****************** // 3. 建立紋理對應的著色器資源檢視 // D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; srvDesc.Format = texDesc.Format; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MostDetailedMip = 0; srvDesc.TextureCube.MipLevels = -1; // 使用所有的mip等級 HR(device->CreateShaderResourceView( texture.Get(), &srvDesc, mOutputTextureSRV.GetAddressOf())); // ****************** // 4. 建立與紋理等寬高的深度/模板緩衝區和對應的檢視 // texDesc.Width = texWidth; texDesc.Height = texHeight; texDesc.MipLevels = 0; texDesc.ArraySize = 1; texDesc.SampleDesc.Count = 1; texDesc.SampleDesc.Quality = 0; texDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; texDesc.Usage = D3D11_USAGE_DEFAULT; texDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; texDesc.CPUAccessFlags = 0; texDesc.MiscFlags = 0; ComPtr<ID3D11Texture2D> depthTex; device->CreateTexture2D(&texDesc, nullptr, depthTex.GetAddressOf()); D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc; dsvDesc.Format = texDesc.Format; dsvDesc.Flags = 0; dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; dsvDesc.Texture2D.MipSlice = 0; HR(device->CreateDepthStencilView( depthTex.Get(), &dsvDesc, mOutputTextureDSV.GetAddressOf())); // ****************** // 5. 初始化視口 // mOutputViewPort.TopLeftX = 0.0f; mOutputViewPort.TopLeftY = 0.0f; mOutputViewPort.Width = static_cast<float>(texWidth); mOutputViewPort.Height = static_cast<float>(texHeight); mOutputViewPort.MinDepth = 0.0f; mOutputViewPort.MaxDepth = 1.0f; }
TextureRender::Begin方法--開始對當前紋理進行渲染
該方法快取當前渲染管線繫結的渲染目標檢視、深度/模板檢視以及視口,並替換初始化好的這些資源。注意還需要清空一遍緩衝區:
void TextureRender::Begin(ComPtr<ID3D11DeviceContext> deviceContext) { // 快取渲染目標和深度模板檢視 deviceContext->OMGetRenderTargets(1, mCacheRTV.GetAddressOf(), mCacheDSV.GetAddressOf()); // 快取視口 UINT numViewports = 1; deviceContext->RSGetViewports(&numViewports, &mCacheViewPort); // 清空緩衝區 deviceContext->ClearRenderTargetView(mOutputTextureRTV.Get(), reinterpret_cast<const float*>(&Colors::Black)); deviceContext->ClearDepthStencilView(mOutputTextureDSV.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); // 設定渲染目標和深度模板檢視 deviceContext->OMSetRenderTargets(1, mOutputTextureRTV.GetAddressOf(), mOutputTextureDSV.Get()); // 設定視口 deviceContext->RSSetViewports(1, &mOutputViewPort); }
TextureRender::End方法--結束對當前紋理的渲染,還原狀態
在對當前紋理的所有繪製方法呼叫完畢後,就需要呼叫該方法以恢復到原來的渲染目標檢視、深度/模板檢視以及視口。若在初始化時還指定了 generateMips
為 true
,還會給該紋理生成mipmap鏈:
void TextureRender::End(ComPtr<ID3D11DeviceContext> deviceContext) { // 恢復預設設定 deviceContext->RSSetViewports(1, &mCacheViewPort); deviceContext->OMSetRenderTargets(1, mCacheRTV.GetAddressOf(), mCacheDSV.Get()); // 若之前有指定需要mipmap鏈,則生成 if (mGenerateMips) { deviceContext->GenerateMips(mOutputTextureSRV.Get()); } // 清空臨時快取的渲染目標檢視和深度模板檢視 mCacheDSV.Reset(); mCacheRTV.Reset(); }
最後就可以通過 TextureRender::GetOutputTexture
方法獲取渲染好的紋理了。
注意: 不要將紋理既作為渲染目標,又作為著色器資源,雖然不會報錯,但這樣會導致程式執行速度被拖累。 在VS的輸出視窗你可以看到它會將該資源強制從著色器中撤離,置其為NULL,以保證不會同時繫結在輸入和輸出端。
螢幕淡入/淡出效果的實現
該效果對應的特效檔案為 ScreenFadeEffect.cpp
,著色器檔案為 ScreenFade_VS.hlsl
和 ScreenFade_PS.hlsl
。
ScreenFadeEffect
類在這不做講解,有興趣的可以檢視第13章的自定義Effects管理類實現教程,或者去翻看 ScreenFadeEffect
類的原始碼實現。
首先是 ScreenFade.hlsli
// ScreenFade.hlsli Texture2D tex : register(t0); SamplerState sam : register(s0); cbuffer CBChangesEveryFrame : register(b0) { float gFadeAmount;// 顏色程度控制(0.0f-1.0f) float3 gPad; } cbuffer CBChangesRarely : register(b1) { matrix gWorldViewProj; } struct VertexPosNormalTex { float3 PosL : POSITION; float3 NormalW : NORMAL; float2 Tex : TEXCOORD; }; struct VertexPosHTex { float4 PosH : SV_POSITION; float2 Tex : TEXCOORD; };
然後分別是對於的頂點著色器和畫素著色器實現:
// ScreenFade_VS.hlsl #include "ScreenFade.hlsli" // 頂點著色器 VertexPosHTex VS(VertexPosNormalTex vIn) { VertexPosHTex vOut; vOut.PosH = mul(float4(vIn.PosL, 1.0f), gWorldViewProj); vOut.Tex = vIn.Tex; return vOut; }
// ScreenFade_PS.hlsl #include "ScreenFade.hlsli" // 畫素著色器 float4 PS(VertexPosHTex pIn) : SV_Target { return tex.Sample(sam, pIn.Tex) * float4(gFadeAmount, gFadeAmount, gFadeAmount, 1.0f); }
該套著色器通過gFadeAmount來控制最終輸出的顏色,我們可以通過對其進行動態調整來實現一些效果。當 gFadeAmount
從0到1時,螢幕從黑到正常顯示,即淡入效果;而當 gFadeAmount
從1到0時,平面從正常顯示到變暗,即淡出效果。
一開始畫素著色器的返回值採用的是和Rastertek一樣的 tex.Sample(sam, pIn.Tex) * gFadeAmount
,但是在截圖出來的.dds檔案觀看的時候顏色變得很奇怪
原本以為是輸出的檔案格式亂了,但當我把Alpha通道關閉後,圖片卻一切正常了
故這裡應該讓Alpha通道的值乘上1.0f以保持Alpha通道的一致性
為了實現螢幕的淡入淡出效果,我們需要一張渲染好的場景紋理,即通過 TextureRender
來實現。
首先我們看 GameApp::UpdateScene
方法中用於控制螢幕淡入淡出的部分:
// 更新淡入淡出值 if (mFadeUsed) { mFadeAmount += mFadeSign * dt / 2.0f;// 2s時間淡入/淡出 if (mFadeSign > 0.0f && mFadeAmount > 1.0f) { mFadeAmount = 1.0f; mFadeUsed = false;// 結束淡入 } else if (mFadeSign < 0.0f && mFadeAmount < 0.0f) { mFadeAmount = 0.0f; SendMessage(MainWnd(), WM_DESTROY, 0, 0);// 關閉程式 // 這裡不結束淡出是因為傳送關閉視窗的訊息還要過一會才真正關閉 } } // ... // 退出程式,開始淡出 if (mKeyboardTracker.IsKeyPressed(Keyboard::Escape)) { mFadeSign = -1.0f; mFadeUsed = true; }
啟動程式的時候, mFadeSign
的初始值是 1.0f
,這樣就使得開啟程式的時候就在進行螢幕淡入。
而使用者按下 Esc
鍵退出的話,則先觸發螢幕淡出效果,等螢幕變黑後再發送關閉程式的訊息給視窗。注意傳送訊息到真正關閉還相隔一段時間,在這段時間內也不要關閉淡出效果的繪製,否則最後那一瞬間又突然看到場景了。
然後在 GameApp::DrawScene
方法中,我們可以將繪製過程簡化成這樣:
// ****************** // 繪製Direct3D部分 // // 預先清空後備緩衝區 md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black)); md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); if (mFadeUsed) { // 開始淡入/淡出 mScreenFadeRender->Begin(md3dImmediateContext); } // 繪製主場景... if (mFadeUsed) { // 結束淡入/淡出,此時繪製的場景在螢幕淡入淡出渲染的紋理 mScreenFadeRender->End(md3dImmediateContext); // 螢幕淡入淡出特效應用 mScreenFadeEffect.SetRenderDefault(md3dImmediateContext); mScreenFadeEffect.SetFadeAmount(mFadeAmount); mScreenFadeEffect.SetTexture(mScreenFadeRender->GetOutputTexture()); mScreenFadeEffect.SetWorldViewProjMatrix(XMMatrixIdentity()); mScreenFadeEffect.Apply(md3dImmediateContext); // 將儲存的紋理輸出到螢幕 md3dImmediateContext->IASetVertexBuffers(0, 1, mFullScreenShow.modelParts[0].vertexBuffer.GetAddressOf(), strides, offsets); md3dImmediateContext->IASetIndexBuffer(mFullScreenShow.modelParts[0].indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0); md3dImmediateContext->DrawIndexed(6, 0, 0); }
對了,如果視窗被拉伸,那我們之前建立的紋理寬高就不適用了,需要重新建立一個。在 GameApp::OnResize
方法可以看到:
void GameApp::OnResize() { // ... // 攝像機變更顯示 if (mCamera != nullptr) { // ... // 螢幕淡入淡出紋理大小重設 mScreenFadeRender = std::make_unique<TextureRender>(md3dDevice, mClientWidth, mClientHeight, false); } }
由於螢幕淡入淡出效果需要先繪製主場景到紋理,然後再用該紋理完整地繪製到螢幕上,就不說前面還進行了大量的深度測試了,兩次繪製下來使得在渲染淡入淡出效果的時候幀數下降比較明顯。因此不建議經常這麼做。
小地圖的實現
關於小地圖的實現,有許多種方式。常見的如下:
- 美術預先繪製一張地圖紋理,然後再在上面繪製一些2D物件表示場景中的物體
- 捕獲遊戲場景的俯檢視用作紋理,但只保留靜態物體,然後再在上面繪製一些2D物件表示場景中的物體
- 通過俯檢視完全繪製出遊戲場景中的所有物體
可以看出,效能的消耗越往後要求越高。
因為本專案的場景是在夜間森林,並且樹是隨機生成的,因此採用第二種方式,但是地圖可視範圍為攝像機可視區域,並且不考慮額外繪製任何2D物件。
小地圖對應的特效檔案為 MinimapEffect.cpp
,著色器檔案為 Minimap_VS.hlsl
和 Minimap_PS.hlsl
。同樣這裡只關注HLSL實現。
首先是 Minimap.hlsli
:
// Minimap.hlsli Texture2D tex : register(t0); SamplerState sam : register(s0); cbuffer CBChangesEveryFrame : register(b0) { float3 gEyePosW;// 攝像機位置 float gPad; } cbuffer CBDrawingStates : register(b1) { int gFogEnabled;// 是否範圍可視 float gVisibleRange;// 3D世界可視範圍 float2 gPad2; float4 gRectW;// 小地圖xOz平面對應3D世界矩形區域(Left, Front, Right, Back) float4 gInvisibleColor;// 不可視情況下的顏色 } struct VertexPosNormalTex { float3 PosL : POSITION; float3 NormalL : NORMAL; float2 Tex : TEXCOORD; }; struct VertexPosHTex { float4 PosH : SV_POSITION; float2 Tex : TEXCOORD; };
為了能在小地圖中繪製出局部區域可視的效果,還需要依賴3D世界中的一些引數。其中 gRectW
對應的是3D世界中矩形區域(即x最小值, z最大值, x最大值, z最小值)。
然後是頂點著色器和畫素著色器的實現:
// Minimap_VS.hlsl #include "Minimap.hlsli" // 頂點著色器 VertexPosHTex VS(VertexPosNormalTex vIn) { VertexPosHTex vOut; vOut.PosH = float4(vIn.PosL, 1.0f); vOut.Tex = vIn.Tex; return vOut; }
// Minimap_PS.hlsl #include "Minimap.hlsli" // 畫素著色器 float4 PS(VertexPosHTex pIn) : SV_Target { // 要求Tex的取值範圍都在[0.0f, 1.0f], y值對應世界座標z軸 float2 PosW = pIn.Tex * float2(gRectW.zw - gRectW.xy) + gRectW.xy; float4 color = tex.Sample(sam, pIn.Tex); [flatten] if (gFogEnabled && length(PosW - gEyePosW.xz) / gVisibleRange > 1.0f) { return gInvisibleColor; } return color; }
接下來我們需要通過Render-To-Texture技術,捕獲整個場景的俯檢視。關於小地圖的繪製放在了 GameApp::InitResource
中:
bool GameApp::InitResource() { // ... mMinimapRender = std::make_unique<TextureRender>(md3dDevice, 400, 400, true); // 初始化網格,放置在右下角200x200 mMinimap.SetMesh(md3dDevice, Geometry::Create2DShow(0.75f, -0.66666666f, 0.25f, 0.33333333f)); // ... // 小地圖攝像機 mMinimapCamera = std::unique_ptr<FirstPersonCamera>(new FirstPersonCamera); mMinimapCamera->SetViewPort(0.0f, 0.0f, 200.0f, 200.0f);// 200x200小地圖 mMinimapCamera->LookTo( XMVectorSet(0.0f, 10.0f, 0.0f, 1.0f), XMVectorSet(0.0f, -1.0f, 0.0f, 1.0f), XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f)); mMinimapCamera->UpdateViewMatrix(); // ... // 小地圖範圍可視 mMinimapEffect.SetFogState(true); mMinimapEffect.SetInvisibleColor(XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f)); mMinimapEffect.SetMinimapRect(XMVectorSet(-95.0f, 95.0f, 95.0f, -95.0f)); mMinimapEffect.SetVisibleRange(25.0f); // 方向光(預設) DirectionalLight dirLight[4]; dirLight[0].Ambient = XMFLOAT4(0.15f, 0.15f, 0.15f, 1.0f); dirLight[0].Diffuse = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f); dirLight[0].Specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 1.0f); dirLight[0].Direction = XMFLOAT3(-0.577f, -0.577f, 0.577f); dirLight[1] = dirLight[0]; dirLight[1].Direction = XMFLOAT3(0.577f, -0.577f, 0.577f); dirLight[2] = dirLight[0]; dirLight[2].Direction = XMFLOAT3(0.577f, -0.577f, -0.577f); dirLight[3] = dirLight[0]; dirLight[3].Direction = XMFLOAT3(-0.577f, -0.577f, -0.577f); for (int i = 0; i < 4; ++i) mBasicEffect.SetDirLight(i, dirLight[i]); // ****************** // 渲染小地圖紋理 // mBasicEffect.SetViewMatrix(mMinimapCamera->GetViewXM()); mBasicEffect.SetProjMatrix(XMMatrixOrthographicLH(190.0f, 190.0f, 1.0f, 20.0f));// 使用正交投影矩陣(中心在攝像機位置) // 關閉霧效 mBasicEffect.SetFogState(false); mMinimapRender->Begin(md3dImmediateContext); DrawScene(true); mMinimapRender->End(md3dImmediateContext); mMinimapEffect.SetTexture(mMinimapRender->GetOutputTexture()); // ... }
通常小地圖的製作,建議是使用正交投影矩陣, XMMatrixOrthographicLH
函式的中心在攝像機位置,不以攝像機為中心的話可以用 XMMatrixOrthographicOffCenterLH
函式。
然後如果視窗大小調整,為了保證小地圖在螢幕的顯示是在右下角,並且保持200x200,需要在 GameApp::OnResize
重新調整網格模型:
void GameApp::OnResize() { // ... // 攝像機變更顯示 if (mCamera != nullptr) { // ... // 小地圖網格模型重設 mMinimap.SetMesh(md3dDevice, Geometry::Create2DShow(1.0f - 100.0f / mClientWidth * 2,-1.0f + 100.0f / mClientHeight * 2, 100.0f / mClientWidth * 2, 100.0f / mClientHeight * 2)); } }
最後是 GameApp::DrawScene
方法將小地圖紋理繪製到螢幕的部分:
UINT strides[1] = { sizeof(VertexPosNormalTex) }; UINT offsets[1] = { 0 }; // 小地圖特效應用 mMinimapEffect.SetRenderDefault(md3dImmediateContext); mMinimapEffect.Apply(md3dImmediateContext); // 最後繪製小地圖 md3dImmediateContext->IASetVertexBuffers(0, 1, mMinimap.modelParts[0].vertexBuffer.GetAddressOf(), strides, offsets); md3dImmediateContext->IASetIndexBuffer(mMinimap.modelParts[0].indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0); md3dImmediateContext->DrawIndexed(6, 0, 0);
使用ScreenGrab將紋理儲存到檔案
在這一章的專案新增了來自DirectXTex的 ScreenGrab.h
和 ScreenGrab.cpp
。但為了能儲存WIC類別的點陣圖,還需要包含標頭檔案 wincodec.h
以使用裡面一些關於WIC控制元件的GUID。 ScreenGrab
的函式位於名稱空間 DirectX
內。
SaveDDSTextureToFile函式--以.dds格式儲存紋理
HRESULT SaveDDSTextureToFile( ID3D11DeviceContext* pContext,// [In]裝置上下文 ID3D11Resource* pSource,// [In]必須為包含ID3D11Texture2D介面類的指標 const wchar_t* fileName );// [In]輸出檔名
SaveWICTextureToFile函式--以指定WIC型別的格式儲存紋理
HRESULT SaveWICTextureToFile( ID3D11DeviceContext* pContext,// [In]裝置上下文 ID3D11Resource* pSource,// [In]必須為包含ID3D11Texture2D介面類的指標 REFGUID guidContainerFormat,// [In]需要轉換的圖片格式對應的GUID引用 const wchar_t* fileName,// [In]輸出檔名 const GUID* targetFormat = nullptr,// [In]忽略 std::function<void(IPropertyBag2*)> setCustomProps = nullptr ); // [In]忽略
下表給出了常用的GUID:
GUID | 檔案格式 |
---|---|
GUID_ContainerFormatPng | png |
GUID_ContainerFormatJpeg | jpg |
GUID_ContainerFormatBmp | bmp |
GUID_ContainerFormatTiff | tif |
這裡演示瞭如何儲存後備緩衝區紋理到檔案:
// 截圖 if (mKeyboardTracker.IsKeyPressed(Keyboard::Q)) mPrintScreenStarted = true; // ... // 若截圖鍵按下,則分別儲存到output.dds和output.png中 if (mPrintScreenStarted) { ComPtr<ID3D11Texture2D> backBuffer; // 輸出截圖 mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())); HR(SaveDDSTextureToFile(md3dImmediateContext.Get(), backBuffer.Get(), L"Screenshot\\output.dds")); HR(SaveWICTextureToFile(md3dImmediateContext.Get(), backBuffer.Get(), GUID_ContainerFormatPng, L"Screenshot\\output.png")); // 結束截圖 mPrintScreenStarted = false; }
專案演示
本專案的場景沿用了第20章的森林場景,並搭配了夜晚霧效,在開啟程式後可以看到螢幕淡入的效果,按下Esc後則螢幕淡出後退出。
然後人物在移動的時候,小地圖的可視範圍也會跟著移動。
DirectX11 With Windows SDK完整目錄