前言

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

章节回顾
深入理解与使用2D纹理资源(重点阅读ScreenGrab库)

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的实现,因此也继承了它简单易用的特性:

class TextureRender
{
public:
template<class T>
using ComPtr = Microsoft::WRL::ComPtr<T>; TextureRender() = default;
~TextureRender() = default;
// 不允许拷贝,允许移动
TextureRender(const TextureRender&) = delete;
TextureRender& operator=(const TextureRender&) = delete;
TextureRender(TextureRender&&) = default;
TextureRender& operator=(TextureRender&&) = default; HRESULT InitResource(ID3D11Device* device,
int texWidth,
int texHeight,
bool generateMips = false); // 开始对当前纹理进行渲染
void Begin(ID3D11DeviceContext * deviceContext, const FLOAT backgroundColor[4]);
// 结束对当前纹理的渲染,还原状态
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链
};

它具有如下特点:

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

TextureRender初始化

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

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

具体代码如下:

HRESULT TextureRender::InitResource(ID3D11Device* device, int texWidth, int texHeight, bool generateMips)
{
// 防止重复初始化造成内存泄漏
m_pOutputTextureSRV.Reset();
m_pOutputTextureRTV.Reset();
m_pOutputTextureDSV.Reset();
m_pCacheRTV.Reset();
m_pCacheDSV.Reset(); m_GenerateMips = generateMips;
HRESULT hr;
// ******************
// 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());
if (FAILED(hr))
return hr;
// ******************
// 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.ReleaseAndGetAddressOf());
if (FAILED(hr))
return hr; // ******************
// 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.ReleaseAndGetAddressOf());
if (FAILED(hr))
return hr; // ******************
// 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;
hr = device->CreateTexture2D(&texDesc, nullptr, depthTex.ReleaseAndGetAddressOf());
if (FAILED(hr))
return hr; 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.ReleaseAndGetAddressOf());
if (FAILED(hr))
return hr; // ******************
// 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; return S_OK;
}

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, 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方法--结束对当前纹理的渲染,还原状态

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

void TextureRender::End(ComPtr<ID3D11DeviceContext> deviceContext)
{
// 恢复默认设定
deviceContext->RSSetViewports(1, &m_CacheViewPort);
deviceContext->OMSetRenderTargets(1, m_pCacheRTV.GetAddressOf(), m_pCacheDSV.Get()); // 若之前有指定需要mipmap链,则生成
if (m_GenerateMips)
{
deviceContext->GenerateMips(m_pOutputTextureSRV.Get());
} // 清空临时缓存的渲染目标视图和深度模板视图
m_pCacheDSV.Reset();
m_pCacheRTV.Reset();
}

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

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

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

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

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

首先是ScreenFade.hlsli

// 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, g_FadeAmount, 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 (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, 0); // 关闭程序
// 这里不结束淡出是因为发送关闭窗口的消息还要过一会才真正关闭
}
} // ... // 退出程序,开始淡出
if (m_KeyboardTracker.IsKeyPressed(Keyboard::Escape))
{
m_FadeSign = -1.0f;
m_FadeUsed = true;
}

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

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

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

// ******************
// 绘制Direct3D部分
// // 预先清空后备缓冲区
m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 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, 0);
// 务必解除绑定在着色器上的资源,因为下一帧开始它会作为渲染目标
m_ScreenFadeEffect.SetTexture(nullptr);
m_ScreenFadeEffect.Apply(m_pd3dImmediateContext.Get());
}

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

void GameApp::OnResize()
{
// ... // 摄像机变更显示
if (mCamera != nullptr)
{
// ... // 屏幕淡入淡出纹理大小重设
m_pScreenFadeRender = std::make_unique<TextureRender>(m_pd3dDevice.Get(), m_ClientWidth, m_ClientHeight, false);
}
}

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

小地图的实现

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

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

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

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

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

首先是Minimap.hlsli

// 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世界中的一些参数。其中gRectW对应的是3D世界中矩形区域(即x最小值, z最大值, x最大值, z最小值)。

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

// 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技术,捕获整个场景的俯视图。关于小地图的绘制放在了GameApp::InitResource中:

bool GameApp::InitResource()
{
// ... m_pMinimapRender = std::make_unique<TextureRender>();
HR(m_pMinimapRender->InitResource(m_pd3dDevice.Get(), 400, 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, 0.0f, 200.0f, 200.0f); // 200x200小地图
m_MinimapCamera->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));
m_MinimapCamera->UpdateViewMatrix(); // ... // 小地图范围可视
m_MinimapEffect.SetFogState(true);
m_MinimapEffect.SetInvisibleColor(XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f));
m_MinimapEffect.SetMinimapRect(XMVectorSet(-95.0f, 95.0f, 95.0f, -95.0f));
m_MinimapEffect.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)
m_BasicEffect.SetDirLight(i, dirLight[i]); // ******************
// 渲染小地图纹理
// m_BasicEffect.SetViewMatrix(m_MinimapCamera->GetViewXM());
m_BasicEffect.SetProjMatrix(XMMatrixOrthographicLH(190.0f, 190.0f, 1.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()); // ...
}

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

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

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));
}
}

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

// 此处用于小地图和屏幕绘制
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, 1, m_Minimap.modelParts[0].vertexBuffer.GetAddressOf(), strides, offsets);
m_pd3dImmediateContext->IASetIndexBuffer(m_Minimap.modelParts[0].indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
m_pd3dImmediateContext->DrawIndexed(6, 0, 0);

项目演示

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

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

DirectX11 With Windows SDK完整目录

Github项目源码

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

DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用的更多相关文章

  1. 粒子系统与雨的效果 (DirectX11 with Windows SDK)

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  2. 什么是渲染目标(render target)&& 渲染到纹理(Render To Texture, RTT)详解

    渲染到纹理(Render To Texture, RTT)详解 RTT是现在很多特效里面都会用到的一项很基本的技术,实现起来很简单,也很重要.但是让人不解的是网上搜索了半天只找到很少的文章说这个事儿, ...

  3. DirectX11 With Windows SDK--09 纹理映射与采样器状态

    前言 在之前的DirectX SDK中,纹理的读取使用的是D3DX11CreateShaderResourceViewFromFile函数,现在在Windows SDK中已经没有这些函数,我们需要找到 ...

  4. DirectX11 With Windows SDK--10 摄像机类

    前言 DirectX11 With Windows SDK完整目录:http://www.cnblogs.com/X-Jun/p/9028764.html 由于考虑后续的项目需要有一个比较好的演示环境 ...

  5. DirectX11 With Windows SDK--26 计算着色器:入门

    前言 现在开始迎来所谓的高级篇了,目前计划是计算着色器部分的内容视项目情况,大概会分3-5章来讲述. DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群 ...

  6. DirectX11 With Windows SDK--25 法线贴图

    前言 在很早之前的纹理映射中,纹理存放的元素是像素的颜色,通过纹理坐标映射到目标像素以获取其颜色.但是我们的法向量依然只是定义在顶点上,对于三角形面内一点的法向量,也只是通过比较简单的插值法计算出相应 ...

  7. DirectX11 With Windows SDK--23 立方体映射:动态天空盒的实现

    前言 上一章的静态天空盒已经可以满足绝大部分日常使用了.但对于自带反射/折射属性的物体来说,它需要依赖天空盒进行绘制,但静态天空盒并不会记录周边的物体,更不用说正在其周围运动的物体了.因此我们需要在运 ...

  8. DirectX11 With Windows SDK--07 添加光照与常用几何模型

    前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的光照模型.除此之外,该项目还用到了多个常量缓冲区,因此还会提及HLSL的常量缓冲区打包规则以及如何设置多 ...

  9. DirectX11 With Windows SDK--11 混合状态与光栅化状态

    前言 虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下四种: 光栅化状态(光栅化阶段) 采样器状态(像素着色阶段) 混合状态(输出合并阶段 ...

随机推荐

  1. Chromium被用于Microsoft Edge与ChakraCore的未来【译】

    注:英语不好,力求大概能懂.持笔人是:Limin Zhu,好像是中国人,但是没有提供中文版本. 大家好,ChakraCore的朋友们: 昨天,微软公布,Microsoft Edge桌面浏览器采用Chr ...

  2. idea Maven项目找不到相关依赖包(红色波浪线)

    前两天做项目的时候,把团队其他人的代码从git同步到自己电脑上,出现了冲突.发现是maven依赖出现了问题,之前的截图找不到了,我就简单描述一下.就是下图箭头所示位置出现了红色波浪线. 在网上找了很多 ...

  3. TSC条码打印机C#例程(tsclib.dll调用) 【转】

    //----  program.cs using System;using System.Collections.Generic;using System.Windows.Forms; using S ...

  4. 从0开始的Python学习011模块

    简介 你已经学习了如何在你的程序中定义一次函数而重用代码.如果你想要在其他程序中重用很多函数,那么你该如何编写程序呢?你可能已经猜到了,答案是使用模块.模块基本上就是一个包含了所有你定义的函数和变量的 ...

  5. git简单提交操作

    一.本地仓库操作 1.打开git命令行,先'到需要提交的目录 2.输入git init,初始化本地仓库 3.输入git add <file> 命令添加提交文件 4.输入git status ...

  6. June. 25th 2018, Week 26th. Monday

    Change in all things is sweet. 有改变就会有美好. From Aristole. Change is always good, but embracing change ...

  7. php之微信公众号发送模板消息参观模仿

    上篇文章中鞋到了公众号发送末班消息,他是最后调用两个方法,本文章简化一下 将下面的php方法放到一个可以引入的公共类中即可 构建模板消息: 我把需要用到的模板消息 都写成一个个方法,放在公共类文件中了 ...

  8. day正则表达式补充

    # 2.正则 # 方法:findall | match | split | sub# a = 10# print(a.__hash__())# def fn():# pass# print(fn.__ ...

  9. robotframework上的字体放大和缩小是ctr++和ctl--

  10. selenium跳过webdriver检测并模拟登录淘宝

    目录 简介 编写思路 使用教程 演示图片 源代码 @(文章目录) 简介 模拟登录淘宝已经不是一件新鲜的事情了,过去我曾经使用get/post方式进行爬虫,同时也加入IP代理池进行跳过检验,但随着大型网 ...