DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的
原文:
DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用
前言尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现。考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用。
DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。 再述Render-To-Texture技术在前面的章节中,我们默认的渲染目标是来自DXGI后备缓冲区,它是一个2D纹理。而Render-To-Texture技术,实际上就是使用一张2D纹理作为渲染目标,但一般是自己新建的2D纹理。与此同时,这个纹理还能够绑定到着色器资源视图(SRV)供着色器所使用,即原本用作输出的纹理现在用作输入。 它可以用于:
在这一章,我们将展示下面这三种应用:
TextureRender类该类借鉴了上一章 class TextureRender { public: template<class T> using ComPtr = Microsoft::WRL::ComPtr<T>; TextureRender(ID3D11Device * device,int texWidth,int texHeight,bool generateMips = false); ~TextureRender(); // 开始对当前纹理进行渲染 void Begin(ID3D11DeviceContext * deviceContext); // 结束对当前纹理的渲染,还原状态 void End(ID3D11DeviceContext * deviceContext); // 获取渲染好的纹理 ID3D11ShaderResourceView * GetOutputTexture(); // 设置调试对象名 void SetDebugObjectName(const std::string& name); private: ComPtr<ID3D11ShaderResourceView> m_pOutputTextureSRV; // 输出的纹理对应的着色器资源视图 ComPtr<ID3D11RenderTargetView> m_pOutputTextureRTV; // 输出的纹理对应的渲染目标视图 ComPtr<ID3D11DepthStencilView> m_pOutputTextureDSV; // 输出纹理所用的深度/模板视图 D3D11_VIEWPORT m_OutputViewPort; // 输出所用的视口 ComPtr<ID3D11RenderTargetView> m_pCacheRTV; // 临时缓存的后备缓冲区 ComPtr<ID3D11DepthStencilView> m_pCacheDSV; // 临时缓存的深度/模板缓冲区 D3D11_VIEWPORT m_CacheViewPort; // 临时缓存的视口 bool m_GenerateMips; // 是否生成mipmap链 }; 它具有如下特点:
TextureRender初始化现在我们需要完成下面5个步骤:
具体代码如下: TextureRender::TextureRender(ID3D11Device * device,bool generateMips) : m_GenerateMips(generateMips),m_CacheViewPort() { // ****************** // 1. 创建纹理 // ComPtr<ID3D11Texture2D> texture; D3D11_TEXTURE2D_DESC texDesc; texDesc.Width = texWidth; texDesc.Height = texHeight; texDesc.MipLevels = (m_GenerateMips ? 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,m_pOutputTextureRTV.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,m_pOutputTextureSRV.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,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,m_pOutputTextureDSV.GetAddressOf())); // ****************** // 5. 初始化视口 // m_OutputViewPort.TopLeftX = 0.0f; m_OutputViewPort.TopLeftY = 0.0f; m_OutputViewPort.Width = static_cast<float>(texWidth); m_OutputViewPort.Height = static_cast<float>(texHeight); m_OutputViewPort.MinDepth = 0.0f; m_OutputViewPort.MaxDepth = 1.0f; } TextureRender::Begin方法--开始对当前纹理进行渲染该方法缓存当前渲染管线绑定的渲染目标视图、深度/模板视图以及视口,并替换初始化好的这些资源。注意还需要清空一遍缓冲区: void TextureRender::Begin(ID3D11DeviceContext * deviceContext) { // 缓存渲染目标和深度模板视图 deviceContext->OMGetRenderTargets(1,m_pCacheRTV.GetAddressOf(),m_pCacheDSV.GetAddressOf()); // 缓存视口 UINT num_Viewports = 1; deviceContext->RSGetViewports(&num_Viewports,&m_CacheViewPort); // 清空缓冲区 float black[4] = { 0.0f,0.0f,1.0f }; deviceContext->ClearRenderTargetView(m_pOutputTextureRTV.Get(),black); deviceContext->ClearDepthStencilView(m_pOutputTextureDSV.Get(),D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,1.0f,0); // 设置渲染目标和深度模板视图 deviceContext->OMSetRenderTargets(1,m_pOutputTextureRTV.GetAddressOf(),m_pOutputTextureDSV.Get()); // 设置视口 deviceContext->RSSetViewports(1,&m_OutputViewPort); } TextureRender::End方法--结束对当前纹理的渲染,还原状态在对当前纹理的所有绘制方法调用完毕后,就需要调用该方法以恢复到原来的渲染目标视图、深度/模板视图以及视口。若在初始化时还指定了 void TextureRender::End(ComPtr<ID3D11DeviceContext> deviceContext) { // 恢复默认设定 deviceContext->RSSetViewports(1,&m_CacheViewPort); deviceContext->OMSetRenderTargets(1,m_pCacheDSV.Get()); // 若之前有指定需要mipmap链,则生成 if (m_GenerateMips) { deviceContext->GenerateMips(m_pOutputTextureSRV.Get()); } // 清空临时缓存的渲染目标视图和深度模板视图 m_pCacheDSV.Reset(); m_pCacheRTV.Reset(); } 最后就可以通过
屏幕淡入/淡出效果的实现该效果对应的特效文件为
首先是 // ScreenFade.hlsli Texture2D gTex : register(t0); SamplerState gSam : register(s0); cbuffer CBChangesEveryFrame : register(b0) { float g_FadeAmount; // 颜色程度控制(0.0f-1.0f) float3 g_Pad; } cbuffer CBChangesRarely : register(b1) { matrix g_WorldViewProj; } struct VertexPosTex { float3 PosL : POSITION; float2 Tex : TEXCOORD; }; struct VertexPosHTex { float4 PosH : SV_POSITION; float2 Tex : TEXCOORD; }; 然后分别是对于的顶点着色器和像素着色器实现: // ScreenFade_VS.hlsl #include "ScreenFade.hlsli" // 顶点着色器 VertexPosHTex VS(VertexPosTex vIn) { VertexPosHTex vOut; vOut.PosH = mul(float4(vIn.PosL,1.0f),g_WorldViewProj); vOut.Tex = vIn.Tex; return vOut; } // ScreenFade_PS.hlsl #include "ScreenFade.hlsli" // 像素着色器 float4 PS(VertexPosHTex pIn) : SV_Target { return g_Tex.Sample(g_Sam,pIn.Tex) * float4(g_FadeAmount,g_FadeAmount,1.0f); } 该套着色器通过gFadeAmount来控制最终输出的颜色,我们可以通过对其进行动态调整来实现一些效果。当 一开始像素着色器的返回值采用的是和Rastertek一样的 原本以为是输出的文件格式乱了,但当我把Alpha通道关闭后,图片却一切正常了 故这里应该让Alpha通道的值乘上1.0f以保持Alpha通道的一致性 为了实现屏幕的淡入淡出效果,我们需要一张渲染好的场景纹理,即通过 首先我们看 // 更新淡入淡出值 if (m_FadeUsed) { m_FadeAmount += m_FadeSign * dt / 2.0f; // 2s时间淡入/淡出 if (m_FadeSign > 0.0f && m_FadeAmount > 1.0f) { m_FadeAmount = 1.0f; m_FadeUsed = false; // 结束淡入 } else if (m_FadeSign < 0.0f && m_FadeAmount < 0.0f) { m_FadeAmount = 0.0f; SendMessage(MainWnd(),WM_DESTROY,0); // 关闭程序 // 这里不结束淡出是因为发送关闭窗口的消息还要过一会才真正关闭 } } // ... // 退出程序,开始淡出 if (m_KeyboardTracker.IsKeyPressed(Keyboard::Escape)) { m_FadeSign = -1.0f; m_FadeUsed = true; } 启动程序的时候, 而用户按下 然后在 // ****************** // 绘制Direct3D部分 // // 预先清空后备缓冲区 m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(),reinterpret_cast<const float*>(&Colors::Black)); m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(),0); if (mFadeUsed) { // 开始淡入/淡出 m_pScreenFadeRender->Begin(m_pd3dImmediateContext.Get()); } // 绘制主场景... if (mFadeUsed) { // 结束淡入/淡出,此时绘制的场景在屏幕淡入淡出渲染的纹理 m_pScreenFadeRender->End(m_pd3dImmediateContext.Get()); // 屏幕淡入淡出特效应用 m_ScreenFadeEffect.SetRenderDefault(m_pd3dImmediateContext.Get()); m_ScreenFadeEffect.SetFadeAmount(m_FadeAmount); m_ScreenFadeEffect.SetTexture(m_pScreenFadeRender->GetOutputTexture()); m_ScreenFadeEffect.SetWorldViewProjMatrix(XMMatrixIdentity()); m_ScreenFadeEffect.Apply(m_pd3dImmediateContext.Get()); // 将保存的纹理输出到屏幕 m_pd3dImmediateContext->IASetVertexBuffers(0,1,m_FullScreenShow.modelParts[0].vertexBuffer.GetAddressOf(),strides,offsets); m_pd3dImmediateContext->IASetIndexBuffer(m_FullScreenShow.modelParts[0].indexBuffer.Get(),DXGI_FORMAT_R16_UINT,0); m_pd3dImmediateContext->DrawIndexed(6,0); // 务必解除绑定在着色器上的资源,因为下一帧开始它会作为渲染目标 m_ScreenFadeEffect.SetTexture(nullptr); m_ScreenFadeEffect.Apply(m_pd3dImmediateContext.Get()); } 对了,如果窗口被拉伸,那我们之前创建的纹理宽高就不适用了,需要重新创建一个。在 void GameApp::OnResize() { // ... // 摄像机变更显示 if (mCamera != nullptr) { // ... // 屏幕淡入淡出纹理大小重设 m_pScreenFadeRender = std::make_unique<TextureRender>(m_pd3dDevice.Get(),m_ClientWidth,m_ClientHeight,false); } } 由于屏幕淡入淡出效果需要先绘制主场景到纹理,然后再用该纹理完整地绘制到屏幕上,就不说前面还进行了大量的深度测试了,两次绘制下来使得在渲染淡入淡出效果的时候帧数下降比较明显。因此不建议经常这么做。 小地图的实现关于小地图的实现,有许多种方式。常见的如下:
可以看出,性能的消耗越往后要求越高。 因为本项目的场景是在夜间森林,并且树是随机生成的,因此采用第二种方式,但是地图可视范围为摄像机可视区域,并且不考虑额外绘制任何2D物件。 小地图对应的特效文件为 首先是 // Minimap.hlsli Texture2D g_Tex : register(t0); SamplerState g_Sam : register(s0); cbuffer CBChangesEveryFrame : register(b0) { float3 g_EyePosW; // 摄像机位置 float g_Pad; } cbuffer CBDrawingStates : register(b1) { int g_FogEnabled; // 是否范围可视 float g_VisibleRange; // 3D世界可视范围 float2 g_Pad2; float4 g_RectW; // 小地图xOz平面对应3D世界矩形区域(Left,Front,Right,Back) float4 g_InvisibleColor; // 不可视情况下的颜色 } struct VertexPosTex { float3 PosL : POSITION; float2 Tex : TEXCOORD; }; struct VertexPosHTex { float4 PosH : SV_POSITION; float2 Tex : TEXCOORD; }; 为了能在小地图中绘制出局部区域可视的效果,还需要依赖3D世界中的一些参数。其中 然后是顶点着色器和像素着色器的实现: // Minimap_VS.hlsl #include "Minimap.hlsli" // 顶点着色器 VertexPosHTex VS(VertexPosTex 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(g_RectW.zw - g_RectW.xy) + g_RectW.xy; float4 color = g_Tex.Sample(g_Sam,pIn.Tex); [flatten] if (g_FogEnabled && length(PosW - g_EyePosW.xz) / g_VisibleRange > 1.0f) { return g_InvisibleColor; } return color; } 接下来我们需要通过Render-To-Texture技术,捕获整个场景的俯视图。关于小地图的绘制放在了 bool GameApp::InitResource() { // ... m_pMinimapRender = std::make_unique<TextureRender>(m_pd3dDevice.Get(),400,true); // 初始化网格,放置在右下角200x200 m_Minimap.SetMesh(m_pd3dDevice,Geometry::Create2DShow(0.75f,-0.66666666f,0.25f,0.33333333f)); // ... // 小地图摄像机 m_MinimapCamera = std::unique_ptr<FirstPersonCamera>(new FirstPersonCamera); m_MinimapCamera->SetViewPort(0.0f,200.0f,200.0f); // 200x200小地图 m_MinimapCamera->LookTo( XMVectorSet(0.0f,10.0f,XMVectorSet(0.0f,-1.0f,0.0f)); m_MinimapCamera->UpdateViewMatrix(); // ... // 小地图范围可视 m_MinimapEffect.SetFogState(true); m_MinimapEffect.SetInvisibleColor(XMVectorSet(0.0f,1.0f)); m_MinimapEffect.SetMinimapRect(XMVectorSet(-95.0f,95.0f,-95.0f)); m_MinimapEffect.SetVisibleRange(25.0f); // 方向光(默认) DirectionalLight dirLight[4]; dirLight[0].Ambient = XMFLOAT4(0.15f,0.15f,1.0f); dirLight[0].Diffuse = XMFLOAT4(0.25f,1.0f); dirLight[0].Specular = XMFLOAT4(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); dirLight[2] = dirLight[0]; dirLight[2].Direction = XMFLOAT3(0.577f,-0.577f); dirLight[3] = dirLight[0]; dirLight[3].Direction = XMFLOAT3(-0.577f,-0.577f); for (int i = 0; i < 4; ++i) m_BasicEffect.SetDirLight(i,dirLight[i]); // ****************** // 渲染小地图纹理 // m_BasicEffect.SetViewMatrix(m_MinimapCamera->GetViewXM()); m_BasicEffect.SetProjMatrix(XMMatrixOrthographicLH(190.0f,190.0f,20.0f)); // 使用正交投影矩阵(中心在摄像机位置) // 关闭雾效 m_BasicEffect.SetFogState(false); m_pMinimapRender->Begin(m_pd3dImmediateContext.Get()); DrawScene(true); m_pMinimapRender->End(m_pd3dImmediateContext.Get()); m_MinimapEffect.SetTexture(m_pMinimapRender->GetOutputTexture()); // ... } 通常小地图的制作,建议是使用正交投影矩阵, 然后如果窗口大小调整,为了保证小地图在屏幕的显示是在右下角,并且保持200x200,需要在 void GameApp::OnResize() { // ... // 摄像机变更显示 if (mCamera != nullptr) { // ... // 小地图网格模型重设 m_Minimap.SetMesh(m_pd3dDevice.Get(),Geometry::Create2DShow(1.0f - 100.0f / m_ClientWidth * 2,-1.0f + 100.0f / m_ClientHeight * 2,100.0f / m_ClientWidth * 2,100.0f / m_ClientHeight * 2)); } } 最后是 // 此处用于小地图和屏幕绘制 UINT strides[1] = { sizeof(VertexPosTex) }; UINT offsets[1] = { 0 }; // 小地图特效应用 m_MinimapEffect.SetRenderDefault(m_pd3dImmediateContext.Get()); m_MinimapEffect.Apply(m_pd3dImmediateContext.Get()); // 最后绘制小地图 m_pd3dImmediateContext->IASetVertexBuffers(0,m_Minimap.modelParts[0].vertexBuffer.GetAddressOf(),offsets); m_pd3dImmediateContext->IASetIndexBuffer(m_Minimap.modelParts[0].indexBuffer.Get(),0); m_pd3dImmediateContext->DrawIndexed(6,0); 项目演示本项目的场景沿用了第20章的森林场景,并搭配了夜晚雾效,在打开程序后可以看到屏幕淡入的效果,按下Esc后则屏幕淡出后退出。 然后人物在移动的时候,小地图的可视范围也会跟着移动。 DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- windows – 强制关闭文件而不是进程
- .net – 有没有办法检测“几乎”远程桌面断开连接(即短暂的
- 如何运行heat.exe并在wix中注册一个dll
- POJ2823 Sliding Window(单调队列,线段树,set,deque)
- windows-server-2008 – 什么是Site Link Bridges
- windows-server-2008 – NTBackup(在WS2k3上)无法备份远程服
- Windows-runtime – WinRT中的MAX_PATH
- Windows中的不朽过程;无法杀死它
- windows-8 – Chrome如何侧载“存储应用”?
- 一款VS开发利器-Visual Assist X