经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 游戏设计 » 查看文章
DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用、使用ScreenGrab保存纹理到文件
来源:cnblogs  作者:X_Jun  时间:2018/11/19 9:03:15  对本文有异议

前言

尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现。考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用。此外,这里还会讲到如何使用DirectXTex的ScreenGrab来保存纹理,可以说是干货满满了。

如果想要看Render-To-Texture在动态天空盒的应用,可以点此回顾:

章节
23 立方体映射:动态天空盒的实现

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

再述Render-To-Texture技术

在前面的章节中,我们默认的渲染目标是来自DXGI后备缓冲区,它是一个2D纹理。而Render-To-Texture技术,实际上就是使用一张2D纹理作为渲染目标,但一般是自己新建的2D纹理。与此同时,这个纹理还能够绑定到着色器资源视图(SRV)供着色器所使用,即原本用作输出的纹理现在用作输入。

它可以用于:

  1. 小地图的实现
  2. 阴影映射(Shadow mapping)
  3. 屏幕空间环境光遮蔽(Screen Space Ambient Occlusion)
  4. 利用天空盒实现动态反射/折射(Dynamic reflections/refractions with cube maps)

在这一章,我们将展示下面这三种应用:

  1. 屏幕淡入/淡出
  2. 小地图(有可视范围的)
  3. 保存纹理到文件

TextureRender类

该类借鉴了上一章DynamicSkyEffect的实现,因此也继承了它简单易用的特性:

  1. class TextureRender
  2. {
  3. public:
  4. template<class T>
  5. using ComPtr = Microsoft::WRL::ComPtr<T>;
  6. TextureRender(ComPtr<ID3D11Device> device,
  7. int texWidth,
  8. int texHeight,
  9. bool generateMips = false);
  10. ~TextureRender();
  11. // 开始对当前纹理进行渲染
  12. void Begin(ComPtr<ID3D11DeviceContext> deviceContext);
  13. // 结束对当前纹理的渲染,还原状态
  14. void End(ComPtr<ID3D11DeviceContext> deviceContext);
  15. // 获取渲染好的纹理
  16. ComPtr<ID3D11ShaderResourceView> GetOutputTexture();
  17. private:
  18. ComPtr<ID3D11ShaderResourceView> mOutputTextureSRV; // 输出的纹理对应的着色器资源视图
  19. ComPtr<ID3D11RenderTargetView> mOutputTextureRTV; // 输出的纹理对应的渲染目标视图
  20. ComPtr<ID3D11DepthStencilView> mOutputTextureDSV; // 输出纹理所用的深度/模板视图
  21. D3D11_VIEWPORT mOutputViewPort; // 输出所用的视口
  22. ComPtr<ID3D11RenderTargetView> mCacheRTV; // 临时缓存的后备缓冲区
  23. ComPtr<ID3D11DepthStencilView> mCacheDSV; // 临时缓存的深度/模板缓冲区
  24. D3D11_VIEWPORT mCacheViewPort; // 临时缓存的视口
  25. bool mGenerateMips; // 是否生成mipmap链
  26. };

它具有如下特点:

  1. 支持任意宽高的纹理(在初始化时确定),因为它内置了一个独立的深度/模板缓冲区
  2. 使用BeginEnd方法,确保在这两个方法调用之间的所有绘制都将输出到该纹理
  3. Begin方法会临时缓存后备缓冲区、深度/模板缓冲区和视口,并在End方法恢复,因此无需自己去重新设置这些东西

但如果你想要复制出一份渲染好的纹理,请使用ID3D11DeviceContext::CopyResource方法。不过既然都讲到这份上了,那让我们看下该方法的介绍。

ID3D11DeviceContext::CopyResource方法--复制一份资源

该方法通过GPU将一份完整的源资源复制到目标资源:

  1. void ID3D11DeviceContext::CopyResource(
  2. ID3D11Resource *pDstResource, // [InOut]目标资源
  3. ID3D11Resource *pSrcResource // [In]源资源
  4. );

但是需要注意:

  1. 不支持以D3D11_USAGE_IMMUTABLE创建的目标资源
  2. 两者资源类型要一致
  3. 两者不能是同一个指针
  4. 要有一样的维度(包括宽度,高度,深度,大小)
  5. 要有兼容的DXGI格式,两者格式最好是能相同,或者至少是相同的组别,比如DXGI_FORMAT_R32G32B32_FLOAT,DXGI_FORMAT_R32G32B32_UINTDXGI_FORMAT_R32G32B32_TYPELESS相互间就可以复制。
  6. 两者任何一个在调用该方法的时候不能被映射(先前调用过ID3D11DeviceContext::Map方法又没有Unmap)
  7. 允许深度/模板缓冲区作为源或目标资源

TextureRender初始化

现在我们需要完成下面5个步骤:

  1. 创建纹理
  2. 创建纹理对应的渲染目标视图
  3. 创建纹理对应的着色器资源视图
  4. 创建与纹理等宽高的深度/模板缓冲区和对应的视图
  5. 初始化视口

具体代码如下:

  1. TextureRender::TextureRender(ComPtr<ID3D11Device> device, int texWidth, int texHeight, bool generateMips)
  2. : mGenerateMips(generateMips)
  3. {
  4. // ******************
  5. // 1. 创建纹理
  6. //
  7. ComPtr<ID3D11Texture2D> texture;
  8. D3D11_TEXTURE2D_DESC texDesc;
  9. texDesc.Width = texWidth;
  10. texDesc.Height = texHeight;
  11. texDesc.MipLevels = (mGenerateMips ? 0 : 1); // 0为完整mipmap链
  12. texDesc.ArraySize = 1;
  13. texDesc.SampleDesc.Count = 1;
  14. texDesc.SampleDesc.Quality = 0;
  15. texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  16. texDesc.Usage = D3D11_USAGE_DEFAULT;
  17. texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
  18. texDesc.CPUAccessFlags = 0;
  19. texDesc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS;
  20. // 现在texture用于新建纹理
  21. HR(device->CreateTexture2D(&texDesc, nullptr, texture.ReleaseAndGetAddressOf()));
  22. // ******************
  23. // 2. 创建纹理对应的渲染目标视图
  24. //
  25. D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
  26. rtvDesc.Format = texDesc.Format;
  27. rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
  28. rtvDesc.Texture2D.MipSlice = 0;
  29. HR(device->CreateRenderTargetView(
  30. texture.Get(),
  31. &rtvDesc,
  32. mOutputTextureRTV.GetAddressOf()));
  33. // ******************
  34. // 3. 创建纹理对应的着色器资源视图
  35. //
  36. D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
  37. srvDesc.Format = texDesc.Format;
  38. srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
  39. srvDesc.Texture2D.MostDetailedMip = 0;
  40. srvDesc.TextureCube.MipLevels = -1; // 使用所有的mip等级
  41. HR(device->CreateShaderResourceView(
  42. texture.Get(),
  43. &srvDesc,
  44. mOutputTextureSRV.GetAddressOf()));
  45. // ******************
  46. // 4. 创建与纹理等宽高的深度/模板缓冲区和对应的视图
  47. //
  48. texDesc.Width = texWidth;
  49. texDesc.Height = texHeight;
  50. texDesc.MipLevels = 0;
  51. texDesc.ArraySize = 1;
  52. texDesc.SampleDesc.Count = 1;
  53. texDesc.SampleDesc.Quality = 0;
  54. texDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
  55. texDesc.Usage = D3D11_USAGE_DEFAULT;
  56. texDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
  57. texDesc.CPUAccessFlags = 0;
  58. texDesc.MiscFlags = 0;
  59. ComPtr<ID3D11Texture2D> depthTex;
  60. device->CreateTexture2D(&texDesc, nullptr, depthTex.GetAddressOf());
  61. D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
  62. dsvDesc.Format = texDesc.Format;
  63. dsvDesc.Flags = 0;
  64. dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
  65. dsvDesc.Texture2D.MipSlice = 0;
  66. HR(device->CreateDepthStencilView(
  67. depthTex.Get(),
  68. &dsvDesc,
  69. mOutputTextureDSV.GetAddressOf()));
  70. // ******************
  71. // 5. 初始化视口
  72. //
  73. mOutputViewPort.TopLeftX = 0.0f;
  74. mOutputViewPort.TopLeftY = 0.0f;
  75. mOutputViewPort.Width = static_cast<float>(texWidth);
  76. mOutputViewPort.Height = static_cast<float>(texHeight);
  77. mOutputViewPort.MinDepth = 0.0f;
  78. mOutputViewPort.MaxDepth = 1.0f;
  79. }

TextureRender::Begin方法--开始对当前纹理进行渲染

该方法缓存当前渲染管线绑定的渲染目标视图、深度/模板视图以及视口,并替换初始化好的这些资源。注意还需要清空一遍缓冲区:

  1. void TextureRender::Begin(ComPtr<ID3D11DeviceContext> deviceContext)
  2. {
  3. // 缓存渲染目标和深度模板视图
  4. deviceContext->OMGetRenderTargets(1, mCacheRTV.GetAddressOf(), mCacheDSV.GetAddressOf());
  5. // 缓存视口
  6. UINT numViewports = 1;
  7. deviceContext->RSGetViewports(&numViewports, &mCacheViewPort);
  8. // 清空缓冲区
  9. deviceContext->ClearRenderTargetView(mOutputTextureRTV.Get(), reinterpret_cast<const float*>(&Colors::Black));
  10. deviceContext->ClearDepthStencilView(mOutputTextureDSV.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
  11. // 设置渲染目标和深度模板视图
  12. deviceContext->OMSetRenderTargets(1, mOutputTextureRTV.GetAddressOf(), mOutputTextureDSV.Get());
  13. // 设置视口
  14. deviceContext->RSSetViewports(1, &mOutputViewPort);
  15. }

TextureRender::End方法--结束对当前纹理的渲染,还原状态

在对当前纹理的所有绘制方法调用完毕后,就需要调用该方法以恢复到原来的渲染目标视图、深度/模板视图以及视口。若在初始化时还指定了generateMipstrue,还会给该纹理生成mipmap链:

  1. void TextureRender::End(ComPtr<ID3D11DeviceContext> deviceContext)
  2. {
  3. // 恢复默认设定
  4. deviceContext->RSSetViewports(1, &mCacheViewPort);
  5. deviceContext->OMSetRenderTargets(1, mCacheRTV.GetAddressOf(), mCacheDSV.Get());
  6. // 若之前有指定需要mipmap链,则生成
  7. if (mGenerateMips)
  8. {
  9. deviceContext->GenerateMips(mOutputTextureSRV.Get());
  10. }
  11. // 清空临时缓存的渲染目标视图和深度模板视图
  12. mCacheDSV.Reset();
  13. mCacheRTV.Reset();
  14. }

最后就可以通过TextureRender::GetOutputTexture方法获取渲染好的纹理了。

注意:不要将纹理既作为渲染目标,又作为着色器资源,虽然不会报错,但这样会导致程序运行速度被拖累。在VS的输出窗口你可以看到它会将该资源强制从着色器中撤离,置其为NULL,以保证不会同时绑定在输入和输出端。

屏幕淡入/淡出效果的实现

该效果对应的特效文件为ScreenFadeEffect.cpp,着色器文件为ScreenFade_VS.hlslScreenFade_PS.hlsl

ScreenFadeEffect类在这不做讲解,有兴趣的可以查看第13章的自定义Effects管理类实现教程,或者去翻看ScreenFadeEffect类的源码实现。

首先是ScreenFade.hlsli

  1. // ScreenFade.hlsli
  2. Texture2D tex : register(t0);
  3. SamplerState sam : register(s0);
  4. cbuffer CBChangesEveryFrame : register(b0)
  5. {
  6. float gFadeAmount; // 颜色程度控制(0.0f-1.0f)
  7. float3 gPad;
  8. }
  9. cbuffer CBChangesRarely : register(b1)
  10. {
  11. matrix gWorldViewProj;
  12. }
  13. struct VertexPosNormalTex
  14. {
  15. float3 PosL : POSITION;
  16. float3 NormalW : NORMAL;
  17. float2 Tex : TEXCOORD;
  18. };
  19. struct VertexPosHTex
  20. {
  21. float4 PosH : SV_POSITION;
  22. float2 Tex : TEXCOORD;
  23. };

然后分别是对于的顶点着色器和像素着色器实现:

  1. // ScreenFade_VS.hlsl
  2. #include "ScreenFade.hlsli"
  3. // 顶点着色器
  4. VertexPosHTex VS(VertexPosNormalTex vIn)
  5. {
  6. VertexPosHTex vOut;
  7. vOut.PosH = mul(float4(vIn.PosL, 1.0f), gWorldViewProj);
  8. vOut.Tex = vIn.Tex;
  9. return vOut;
  10. }
  1. // ScreenFade_PS.hlsl
  2. #include "ScreenFade.hlsli"
  3. // 像素着色器
  4. float4 PS(VertexPosHTex pIn) : SV_Target
  5. {
  6. return tex.Sample(sam, pIn.Tex) * float4(gFadeAmount, gFadeAmount, gFadeAmount, 1.0f);
  7. }

该套着色器通过gFadeAmount来控制最终输出的颜色,我们可以通过对其进行动态调整来实现一些效果。当gFadeAmount从0到1时,屏幕从黑到正常显示,即淡入效果;而当gFadeAmount从1到0时,平面从正常显示到变暗,即淡出效果。

一开始像素着色器的返回值采用的是和Rastertek一样的tex.Sample(sam, pIn.Tex) * gFadeAmount,但是在截屏出来的.dds文件观看的时候颜色变得很奇怪

原本以为是输出的文件格式乱了,但当我把Alpha通道关闭后,图片却一切正常了

故这里应该让Alpha通道的值乘上1.0f以保持Alpha通道的一致性

为了实现屏幕的淡入淡出效果,我们需要一张渲染好的场景纹理,即通过TextureRender来实现。

首先我们看GameApp::UpdateScene方法中用于控制屏幕淡入淡出的部分:

  1. // 更新淡入淡出值
  2. if (mFadeUsed)
  3. {
  4. mFadeAmount += mFadeSign * dt / 2.0f; // 2s时间淡入/淡出
  5. if (mFadeSign > 0.0f && mFadeAmount > 1.0f)
  6. {
  7. mFadeAmount = 1.0f;
  8. mFadeUsed = false; // 结束淡入
  9. }
  10. else if (mFadeSign < 0.0f && mFadeAmount < 0.0f)
  11. {
  12. mFadeAmount = 0.0f;
  13. SendMessage(MainWnd(), WM_DESTROY, 0, 0); // 关闭程序
  14. // 这里不结束淡出是因为发送关闭窗口的消息还要过一会才真正关闭
  15. }
  16. }
  17. // ...
  18. // 退出程序,开始淡出
  19. if (mKeyboardTracker.IsKeyPressed(Keyboard::Escape))
  20. {
  21. mFadeSign = -1.0f;
  22. mFadeUsed = true;
  23. }

启动程序的时候,mFadeSign的初始值是1.0f,这样就使得打开程序的时候就在进行屏幕淡入。

而用户按下Esc键退出的话,则先触发屏幕淡出效果,等屏幕变黑后再发送关闭程序的消息给窗口。注意发送消息到真正关闭还相隔一段时间,在这段时间内也不要关闭淡出效果的绘制,否则最后那一瞬间又突然看到场景了。

然后在GameApp::DrawScene方法中,我们可以将绘制过程简化成这样:

  1. // ******************
  2. // 绘制Direct3D部分
  3. //
  4. // 预先清空后备缓冲区
  5. md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
  6. md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
  7. if (mFadeUsed)
  8. {
  9. // 开始淡入/淡出
  10. mScreenFadeRender->Begin(md3dImmediateContext);
  11. }
  12. // 绘制主场景...
  13. if (mFadeUsed)
  14. {
  15. // 结束淡入/淡出,此时绘制的场景在屏幕淡入淡出渲染的纹理
  16. mScreenFadeRender->End(md3dImmediateContext);
  17. // 屏幕淡入淡出特效应用
  18. mScreenFadeEffect.SetRenderDefault(md3dImmediateContext);
  19. mScreenFadeEffect.SetFadeAmount(mFadeAmount);
  20. mScreenFadeEffect.SetTexture(mScreenFadeRender->GetOutputTexture());
  21. mScreenFadeEffect.SetWorldViewProjMatrix(XMMatrixIdentity());
  22. mScreenFadeEffect.Apply(md3dImmediateContext);
  23. // 将保存的纹理输出到屏幕
  24. md3dImmediateContext->IASetVertexBuffers(0, 1, mFullScreenShow.modelParts[0].vertexBuffer.GetAddressOf(), strides, offsets);
  25. md3dImmediateContext->IASetIndexBuffer(mFullScreenShow.modelParts[0].indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
  26. md3dImmediateContext->DrawIndexed(6, 0, 0);
  27. // 务必解除绑定在着色器上的资源,因为下一帧开始它会作为渲染目标
  28. mScreenFadeEffect.SetTexture(nullptr);
  29. mScreenFadeEffect.Apply(md3dImmediateContext);
  30. }

对了,如果窗口被拉伸,那我们之前创建的纹理宽高就不适用了,需要重新创建一个。在GameApp::OnResize方法可以看到:

  1. void GameApp::OnResize()
  2. {
  3. // ...
  4. // 摄像机变更显示
  5. if (mCamera != nullptr)
  6. {
  7. // ...
  8. // 屏幕淡入淡出纹理大小重设
  9. mScreenFadeRender = std::make_unique<TextureRender>(md3dDevice, mClientWidth, mClientHeight, false);
  10. }
  11. }

由于屏幕淡入淡出效果需要先绘制主场景到纹理,然后再用该纹理完整地绘制到屏幕上,就不说前面还进行了大量的深度测试了,两次绘制下来使得在渲染淡入淡出效果的时候帧数下降比较明显。因此不建议经常这么做。

小地图的实现

关于小地图的实现,有许多种方式。常见的如下:

  1. 美术预先绘制一张地图纹理,然后再在上面绘制一些2D物件表示场景中的物体
  2. 捕获游戏场景的俯视图用作纹理,但只保留静态物体,然后再在上面绘制一些2D物件表示场景中的物体
  3. 通过俯视图完全绘制出游戏场景中的所有物体

可以看出,性能的消耗越往后要求越高。

因为本项目的场景是在夜间森林,并且树是随机生成的,因此采用第二种方式,但是地图可视范围为摄像机可视区域,并且不考虑额外绘制任何2D物件。

小地图对应的特效文件为MinimapEffect.cpp,着色器文件为Minimap_VS.hlslMinimap_PS.hlsl。同样这里只关注HLSL实现。

首先是Minimap.hlsli

  1. // Minimap.hlsli
  2. Texture2D tex : register(t0);
  3. SamplerState sam : register(s0);
  4. cbuffer CBChangesEveryFrame : register(b0)
  5. {
  6. float3 gEyePosW; // 摄像机位置
  7. float gPad;
  8. }
  9. cbuffer CBDrawingStates : register(b1)
  10. {
  11. int gFogEnabled; // 是否范围可视
  12. float gVisibleRange; // 3D世界可视范围
  13. float2 gPad2;
  14. float4 gRectW; // 小地图xOz平面对应3D世界矩形区域(Left, Front, Right, Back)
  15. float4 gInvisibleColor; // 不可视情况下的颜色
  16. }
  17. struct VertexPosNormalTex
  18. {
  19. float3 PosL : POSITION;
  20. float3 NormalL : NORMAL;
  21. float2 Tex : TEXCOORD;
  22. };
  23. struct VertexPosHTex
  24. {
  25. float4 PosH : SV_POSITION;
  26. float2 Tex : TEXCOORD;
  27. };

为了能在小地图中绘制出局部区域可视的效果,还需要依赖3D世界中的一些参数。其中gRectW对应的是3D世界中矩形区域(即x最小值, z最大值, x最大值, z最小值)。

然后是顶点着色器和像素着色器的实现:

  1. // Minimap_VS.hlsl
  2. #include "Minimap.hlsli"
  3. // 顶点着色器
  4. VertexPosHTex VS(VertexPosNormalTex vIn)
  5. {
  6. VertexPosHTex vOut;
  7. vOut.PosH = float4(vIn.PosL, 1.0f);
  8. vOut.Tex = vIn.Tex;
  9. return vOut;
  10. }
  1. // Minimap_PS.hlsl
  2. #include "Minimap.hlsli"
  3. // 像素着色器
  4. float4 PS(VertexPosHTex pIn) : SV_Target
  5. {
  6. // 要求Tex的取值范围都在[0.0f, 1.0f], y值对应世界坐标z轴
  7. float2 PosW = pIn.Tex * float2(gRectW.zw - gRectW.xy) + gRectW.xy;
  8. float4 color = tex.Sample(sam, pIn.Tex);
  9. [flatten]
  10. if (gFogEnabled && length(PosW - gEyePosW.xz) / gVisibleRange > 1.0f)
  11. {
  12. return gInvisibleColor;
  13. }
  14. return color;
  15. }

接下来我们需要通过Render-To-Texture技术,捕获整个场景的俯视图。关于小地图的绘制放在了GameApp::InitResource中:

  1. bool GameApp::InitResource()
  2. {
  3. // ...
  4. mMinimapRender = std::make_unique<TextureRender>(md3dDevice, 400, 400, true);
  5. // 初始化网格,放置在右下角200x200
  6. mMinimap.SetMesh(md3dDevice, Geometry::Create2DShow(0.75f, -0.66666666f, 0.25f, 0.33333333f));
  7. // ...
  8. // 小地图摄像机
  9. mMinimapCamera = std::unique_ptr<FirstPersonCamera>(new FirstPersonCamera);
  10. mMinimapCamera->SetViewPort(0.0f, 0.0f, 200.0f, 200.0f); // 200x200小地图
  11. mMinimapCamera->LookTo(
  12. XMVectorSet(0.0f, 10.0f, 0.0f, 1.0f),
  13. XMVectorSet(0.0f, -1.0f, 0.0f, 1.0f),
  14. XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f));
  15. mMinimapCamera->UpdateViewMatrix();
  16. // ...
  17. // 小地图范围可视
  18. mMinimapEffect.SetFogState(true);
  19. mMinimapEffect.SetInvisibleColor(XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f));
  20. mMinimapEffect.SetMinimapRect(XMVectorSet(-95.0f, 95.0f, 95.0f, -95.0f));
  21. mMinimapEffect.SetVisibleRange(25.0f);
  22. // 方向光(默认)
  23. DirectionalLight dirLight[4];
  24. dirLight[0].Ambient = XMFLOAT4(0.15f, 0.15f, 0.15f, 1.0f);
  25. dirLight[0].Diffuse = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);
  26. dirLight[0].Specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 1.0f);
  27. dirLight[0].Direction = XMFLOAT3(-0.577f, -0.577f, 0.577f);
  28. dirLight[1] = dirLight[0];
  29. dirLight[1].Direction = XMFLOAT3(0.577f, -0.577f, 0.577f);
  30. dirLight[2] = dirLight[0];
  31. dirLight[2].Direction = XMFLOAT3(0.577f, -0.577f, -0.577f);
  32. dirLight[3] = dirLight[0];
  33. dirLight[3].Direction = XMFLOAT3(-0.577f, -0.577f, -0.577f);
  34. for (int i = 0; i < 4; ++i)
  35. mBasicEffect.SetDirLight(i, dirLight[i]);
  36. // ******************
  37. // 渲染小地图纹理
  38. //
  39. mBasicEffect.SetViewMatrix(mMinimapCamera->GetViewXM());
  40. mBasicEffect.SetProjMatrix(XMMatrixOrthographicLH(190.0f, 190.0f, 1.0f, 20.0f)); // 使用正交投影矩阵(中心在摄像机位置)
  41. // 关闭雾效
  42. mBasicEffect.SetFogState(false);
  43. mMinimapRender->Begin(md3dImmediateContext);
  44. DrawScene(true);
  45. mMinimapRender->End(md3dImmediateContext);
  46. mMinimapEffect.SetTexture(mMinimapRender->GetOutputTexture());
  47. // ...
  48. }

通常小地图的制作,建议是使用正交投影矩阵,XMMatrixOrthographicLH函数的中心在摄像机位置,不以摄像机为中心的话可以用XMMatrixOrthographicOffCenterLH函数。

然后如果窗口大小调整,为了保证小地图在屏幕的显示是在右下角,并且保持200x200,需要在GameApp::OnResize重新调整网格模型:

  1. void GameApp::OnResize()
  2. {
  3. // ...
  4. // 摄像机变更显示
  5. if (mCamera != nullptr)
  6. {
  7. // ...
  8. // 小地图网格模型重设
  9. mMinimap.SetMesh(md3dDevice, Geometry::Create2DShow(1.0f - 100.0f / mClientWidth * 2, -1.0f + 100.0f / mClientHeight * 2,
  10. 100.0f / mClientWidth * 2, 100.0f / mClientHeight * 2));
  11. }
  12. }

最后是GameApp::DrawScene方法将小地图纹理绘制到屏幕的部分:

  1. UINT strides[1] = { sizeof(VertexPosNormalTex) };
  2. UINT offsets[1] = { 0 };
  3. // 小地图特效应用
  4. mMinimapEffect.SetRenderDefault(md3dImmediateContext);
  5. mMinimapEffect.Apply(md3dImmediateContext);
  6. // 最后绘制小地图
  7. md3dImmediateContext->IASetVertexBuffers(0, 1, mMinimap.modelParts[0].vertexBuffer.GetAddressOf(), strides, offsets);
  8. md3dImmediateContext->IASetIndexBuffer(mMinimap.modelParts[0].indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
  9. md3dImmediateContext->DrawIndexed(6, 0, 0);

使用ScreenGrab将纹理保存到文件

在这一章的项目新增了来自DirectXTex的ScreenGrab.hScreenGrab.cpp。但为了能保存WIC类别的位图,还需要包含头文件wincodec.h以使用里面一些关于WIC控件的GUID。ScreenGrab的函数位于名称空间DirectX内。

SaveDDSTextureToFile函数--以.dds格式保存纹理

  1. HRESULT SaveDDSTextureToFile(
  2. ID3D11DeviceContext* pContext, // [In]设备上下文
  3. ID3D11Resource* pSource, // [In]必须为包含ID3D11Texture2D接口类的指针
  4. const wchar_t* fileName ); // [In]输出文件名

SaveWICTextureToFile函数--以指定WIC型别的格式保存纹理

  1. HRESULT SaveWICTextureToFile(
  2. ID3D11DeviceContext* pContext, // [In]设备上下文
  3. ID3D11Resource* pSource, // [In]必须为包含ID3D11Texture2D接口类的指针
  4. REFGUID guidContainerFormat, // [In]需要转换的图片格式对应的GUID引用
  5. const wchar_t* fileName, // [In]输出文件名
  6. const GUID* targetFormat = nullptr, // [In]忽略
  7. std::function<void(IPropertyBag2*)> setCustomProps = nullptr ); // [In]忽略

下表给出了常用的GUID:

GUID 文件格式
GUID_ContainerFormatPng png
GUID_ContainerFormatJpeg jpg
GUID_ContainerFormatBmp bmp
GUID_ContainerFormatTiff tif

这里演示了如何保存后备缓冲区纹理到文件:

  1. // 截屏
  2. if (mKeyboardTracker.IsKeyPressed(Keyboard::Q))
  3. mPrintScreenStarted = true;
  4. // ...
  5. // 若截屏键按下,则分别保存到output.dds和output.png中
  6. if (mPrintScreenStarted)
  7. {
  8. ComPtr<ID3D11Texture2D> backBuffer;
  9. // 输出截屏
  10. mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf()));
  11. HR(SaveDDSTextureToFile(md3dImmediateContext.Get(), backBuffer.Get(), L"Screenshot\\output.dds"));
  12. HR(SaveWICTextureToFile(md3dImmediateContext.Get(), backBuffer.Get(), GUID_ContainerFormatPng, L"Screenshot\\output.png"));
  13. // 结束截屏
  14. mPrintScreenStarted = false;
  15. }

项目演示

本项目的场景沿用了第20章的森林场景,并搭配了夜晚雾效,在打开程序后可以看到屏幕淡入的效果,按下Esc后则屏幕淡出后退出。

然后人物在移动的时候,小地图的可视范围也会跟着移动。

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号