因为天空盒用的是柱子那个场景。后续做裁剪时修复了摄像机的bug,把15和17章的代码整合在一起。
这样保证我们新的项目中底层代码始终是最新的。
项目准备后的效果图:
我们玩游戏,可以看到很远的景色。
那这些很远的景色真的是真实绘制的吗?如果是的话,对于那么遥远的物体绘制,需要调大摄像机的深度,而由此会带来非常大的效率问题。因为深度越大,视野内的物体就越多。
天空盒就是做一个假的远方纹理,当我们看的时候,看的其实就是一张贴图,这样摄像机的深度可以用比较低的数值。
对于玩家在一张地图上游玩,可以提供静态的天空盒,也可以根据坐标范围,绘制不同的天空盒。
静态天空盒的实现非常的简单。
对于柱子上的球,要做的是镜面反射(在ps shader中实现)
总体结果如下:
动态天空盒,就是我们要实时的绘制在某个点的6方向的图,然后存成一个Texture2DCube的纹理结构。
因为MiniEngine中并不没有这个结构的类,所以这里新添加一个类,用于支持天空盒的纹理缓冲
仿照ColorBuff实现ColorCubeBuff
核心代码
void ColorCubeBuffer::CreateDerivedViews(ID3D12Device* Device, DXGI_FORMAT Format, uint32_t ArraySize, uint32_t NumMips)
{
ID3D12Resource* Resource = m_pResource.Get();
// Create the shader resource view
D3D12_SHADER_RESOURCE_VIEW_DESC SRVDesc = {};
SRVDesc.Format = Format;
SRVDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
SRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; // 把资源当作立方体纹理,生成SRV
SRVDesc.TextureCube.MipLevels = 1;
if (m_SRVHandle.ptr == D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN)
m_SRVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
Device->CreateShaderResourceView(Resource, &SRVDesc, m_SRVHandle);
// Create the render target view
for (int i = 0; i < 6; ++i)
{
D3D12_RENDER_TARGET_VIEW_DESC RTVDesc = {};
RTVDesc.Format = Format;
RTVDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY; // 对于RTV来说,该资源解释为6个纹理数组。每个纹理数组1张图
RTVDesc.Texture2DArray.MipSlice = 0;
RTVDesc.Texture2DArray.PlaneSlice = 0;
RTVDesc.Texture2DArray.FirstArraySlice = i; // 数组的切片起点,这样6个数组使用的就是资源中不同的位置了
RTVDesc.Texture2DArray.ArraySize = 1;
if (m_RTVHandle[i].ptr == D3D12_GPU_VIRTUAL_ADDRESS_UNKNOWN)
m_RTVHandle[i] = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
Device->CreateRenderTargetView(Resource, &RTVDesc, m_RTVHandle[i]);
}
}
使用方式跟ColorBuff一样。
动态天空盒实现步骤
效果:
我们可以发现,在给shader传入描述符的时候,天空盒纹理和普通纹理是作为一个数组的整体传进去的。
占据的是t1-t4
而这个t4纹理就是天空盒纹理,而我们要使用的是另外一种类型,这里直接指定就可以。
// 占据t1-t4
Texture2D gDiffuseMap[3] : register(t1);
// gDiffuseMap占据的是t1-t4,而天空盒纹理本身是放在t4位置,这里转成cube类型
TextureCube gCubeMap : register(t4);
这就像以前说过的,srv的table之类的,占据的是线性的结构。上边这种操作就相当于直接把天空盒纹理地址转成了我们需要的对象。
而对于这个cube格式,我认为就是6个texture2D直接组成的,只不过换成这个类型,可以直接使用对应的api
也是依据这个理解,可以看到代码中直接切天空盒纹理写法如下:
// 设置全部的纹理资源
gfxContext.SetDynamicDescriptors(3, 0, 4, &m_srvs[0]);
gfxContext.SetPipelineState(m_mapPSO[E_EPT_DEFAULT]);
drawRenderItems(gfxContext, m_vecRenderItems[(int)RenderLayer::Opaque]);
// 渲染中间的水晶球,输入纹理是上边动态生成的天空盒
// 设置动态的天空盒资源
gfxContext.SetDynamicDescriptors(3, 3, 1, &Graphics::g_SceneCubeBuff.GetSRV());
drawRenderItems(gfxContext, m_vecRenderItems[(int)RenderLayer::OpaqueDynamicReflectors]);
// 绘制天空盒
gfxContext.SetPipelineState(m_mapPSO[E_EPT_SKY]);
// 设置原始的天空盒资源
gfxContext.SetDynamicDescriptors(3, 3, 1, &m_srvs[3]);
drawRenderItems(gfxContext, m_vecRenderItems[(int)RenderLayer::Sky]);
天空盒是个很实用的技术,可以让玩家“看到”很远的景色。
游戏开发中会有很多的Trick,仅仅牺牲一点点用户几乎无法觉察的体验,就可以极大地提升游戏的性能。