前言
儘管在上一章的動態天空盒中用到了Render-To-Texture技術,但那是針對紋理立方體的特化實現。考慮到該技術的應用層面非常廣,在這裏抽出獨立的一章專門來講有關它的通用實現以及各種應用。此外,這裏還會講到如何使用DirectXTex的ScreenGrab來保存紋理,可以說是乾貨滿滿了。
如果想要看Render-To-Texture在動態天空盒的應用,可以點此回顧:
章節 |
---|
23 立方體映射:動態天空盒的實現 |
DirectX11 With Windows SDK完整目錄
歡迎加入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_VIEWPORT mOutputViewPort; // 輸出所用的視口
ComPtr<ID3D11RenderTargetView> mCacheRTV; // 臨時緩存的後備緩衝區
ComPtr<ID3D11DepthStencilView> mCacheDSV; // 臨時緩存的深度/模板緩衝區
D3D11_VIEWPORT mCacheViewPort; // 臨時緩存的視口
bool mGenerateMips; // 是否生成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);
// 務必解除綁定在着色器上的資源,因爲下一幀開始它會作爲渲染目標
mScreenFadeEffect.SetTexture(nullptr);
mScreenFadeEffect.Apply(md3dImmediateContext);
}
對了,如果窗口被拉伸,那我們之前創建的紋理寬高就不適用了,需要重新創建一個。在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完整目錄
歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。