前言
本篇将展示如何使用DX12实现天空盒
源代码cubemap
创建dds立方体贴图
微软官方提供了一个命令行工具Texassemble,该工具可以将输入的几个图片转换为GPU可识别的DDS贴图,如立方体贴图
语法
texassemble <command>
[-r] [-flist <filename>]
[-w width] [-h height] [-f format]
[-if filter] [-srgb | -srgbi | -srgbo]
[-sepalpha] [-nowic] [-wrap | -mirror] [-alpha]
[-o <outputfile>] [-l] [-y] [-dx10] [-tonemap]
[-nologo]
[-fl feature-level]
[-bgcolor]
[-swizzle rgbamask]
[-stripmips]
<file-name(s)>
常用指令:
- file-name :生成的dds、tga、hdr等格式的纹理名称
- cube:创建一个cubemap,必须有六张图片,顺序为+x, -x, +y, -y, +z, -z
- cubearray:创建一个cubmap数组
- array:创建一个1D/2D的纹理数组,至少有两张图片
- -w:输出纹理的宽
- -h:输出纹理的高
- -f:输出纹理的格式
- -o:输出的纹理名。默认为".DDS"
例子
要点
-
立方体贴图用 6 幅二维 纹理图像构成一个以原点为中心的纹理立方体,每个 2D 纹理是一个立方体的一个面
-
在D3D中,立方体图是一个含有6个元素构成的纹理数组,从索引0~5依次是+x,-x,+y,-y+z,-z。在使用texassemble创建立方体贴图时,按照对应顺序写入即可
-
对立方体贴图进行采样时和2d贴图有些许差别,需要使用3d的方向向量来查找纹理坐标,这是因为纹理坐标(s, t, r)被当作方向向量看待,每个纹素(texel)都表示从原点所看到的纹理立方体上的图像
-
确定纹理坐标:选择具有最大绝对值的纹理坐标对应的相应的面
如,对于给定的矢量(−3.2, 5.1, −8.4),就选择-Z 面,而对剩下的两个坐标除以最大绝对值坐标的绝对值, 即 8.4。 那么就将剩下的两个坐标的范围转换到了-1 到 1,然后重映射到[0,1]范围
-
所谓天空盒其实是对一个超级大的球体进行纹理贴图,并且球体始终以摄像机作为原点,这样才不会露馅
绘制天空盒
生成球体
Model::GeometryGenerator geoGen;
Model::GeometryGenerator::MeshData box = geoGen.CreateBox(1.f, 1.f, 1.f, 5);
// GeometryGenerator'draw
{
auto pDraw = std::make_unique<Model::Geometrie::Draw>();
pDraw->baseVertex = (UINT)totalVertex.size();
pDraw->startIndex = (UINT)totalIndex.size();
pDraw->indexCount = (UINT)box.Indices32.size();
m_draws.push_back(std::move(pDraw));
totalVertex.insert(totalVertex.end(), box.Vertices.begin(), box.Vertices.end());
totalIndex.insert(totalIndex.end(), box.Indices32.begin(), box.Indices32.end());
}
加载立方体纹理
LoadDDSTextureFromFile()会自动识别cube纹理,并存储它
// sky box
{
auto pSkybox = std::make_unique<Core::Texture>();
pSkybox->name = "skybox";
pSkybox->fileName = Util::UTF8ToWideString("ModelFile/skybox/skybox.dds");
OutputDebugStringA("ModelFile/skybox2/skybox.dds");
ThrowIfFailed(LoadDDSTextureFromFile(
m_device.Get(),
pSkybox->fileName.c_str(),
&pSkybox->defaultTexture,
pSkybox->ddsData,
pSkybox->subResources
));
Core::CreateD3DResource(m_device.Get(), m_commandList.Get(), pSkybox->subResources, pSkybox->defaultTexture, pSkybox->uploadTexture);
m_cubemaps[pSkybox->name] = std::move(pSkybox);
}
球体材质
// cubemap's material
{
auto pMaterial = std::make_unique<Core::Material>();
int index = m_models.size();
pMaterial->name = "skybox";
pMaterial->materialCBIndex = index;
pMaterial->diffuseSrvHeapIndex = 2 * index;
pMaterial->numFramesDirty = FrameCount;
pMaterial->specualrShiness = 64;
pMaterial->ambientAlbedo = 0.1;
m_materials.push_back(std::move(pMaterial));
}
draw call项
{
auto pRenderer = std::make_unique<Core::Renderer>();
// 非常大的球体
XMStoreFloat4x4(&pRenderer->world, DirectX::XMMatrixScaling(5000.f, 5000.f, 5000.f));
pRenderer->objectIndex = i;
pRenderer->numFramesDirty = FrameCount;
pRenderer->pGeometry = m_geometries[0].get();
pRenderer->pMaterail = m_materials[i].get();
pRenderer->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
pRenderer->indexCount = m_draws[i]->indexCount;
pRenderer->startIndex = m_draws[i]->startIndex;
pRenderer->baseVertex = m_draws[i]->baseVertex;
m_renderLayers[(int)Core::RenderLayer::Sky].push_back(pRenderer.get());
m_allRenderers.push_back(std::move(pRenderer));
}
SRV
// create a const buffer view(CBV) descriptor heap
D3D12_DESCRIPTOR_HEAP_DESC cbvSrvHeapDesc = {};
cbvSrvHeapDesc.NumDescriptors = m_diffuseTextures.size() + m_specularTextures.size() + m_cubemaps.size();
cbvSrvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvSrvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&cbvSrvHeapDesc, IID_PPV_ARGS(&m_cbvSrvHeap)));
NAME_D3D12_OBJECT(m_cbvSrvHeap);
CD3DX12_CPU_DESCRIPTOR_HANDLE hSRVDescriptor(m_cbvSrvHeap->GetCPUDescriptorHandleForHeapStart());
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Texture2D.MostDetailedMip = 0; // the index of the most detailed mipmap level to use
srvDesc.Texture2D.ResourceMinLODClamp = 0.f; // min mipmap level that you can access. 0.0f means access all of mipmap levels
// cubemap texture
{
auto pCurrTexture = m_cubemaps["skybox"]->defaultTexture;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube.MostDetailedMip = 0;
srvDesc.TextureCube.MipLevels = pCurrTexture->GetDesc().MipLevels;
srvDesc.Format = pCurrTexture->GetDesc().Format;
m_device->CreateShaderResourceView(pCurrTexture.Get(), &srvDesc, hSRVDescriptor);
hSRVDescriptor.Offset(1, m_cbvSrvDescriptorSize);
}
PSO
由于需要先绘制模型,再绘制天空盒(天空盒的深度测试和模型的不同),因此需要两个shader文件和两个PSO
m_shaders["MainVS"] = CompileShader(GetAssetFullPath(L"MainVS.hlsl").c_str(), nullptr, "main", "vs_5_1");
m_shaders["MainPS"] = CompileShader(GetAssetFullPath(L"MainPS.hlsl").c_str(), nullptr, "main", "ps_5_1");
m_shaders["CubemapVS"] = CompileShader(GetAssetFullPath(L"CubemapVS.hlsl").c_str(), nullptr, "main", "vs_5_1");
m_shaders["CubemapPS"] = CompileShader(GetAssetFullPath(L"CubemapPS.hlsl").c_str(), nullptr, "main", "ps_5_1");
// opaque PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePSODesc = {};
opaquePSODesc.InputLayout = { m_inputLayout.data(), (uint32_t)m_inputLayout.size() };
opaquePSODesc.pRootSignature = m_rootSignature.Get();
opaquePSODesc.VS =
{
reinterpret_cast<BYTE*>(m_shaders["MainVS"]->GetBufferPointer()),
m_shaders["MainVS"]->GetBufferSize()
};
opaquePSODesc.PS =
{
reinterpret_cast<BYTE*>(m_shaders["MainPS"]->GetBufferPointer()),
m_shaders["MainPS"]->GetBufferSize()
};
opaquePSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
opaquePSODesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
opaquePSODesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
opaquePSODesc.SampleMask = UINT_MAX; //set the sampling for each pixel(Multiple sampling takes up to 32 samples)
opaquePSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
opaquePSODesc.NumRenderTargets = 1; //The number of render target formats in the RTVFormats member
opaquePSODesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
opaquePSODesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
opaquePSODesc.SampleDesc.Count = 1;
ThrowIfFailed(m_device->CreateGraphicsPipelineState(&opaquePSODesc, IID_PPV_ARGS(&m_PSOs["opaque"])));
NAME_D3D12_OBJECT(m_PSOs["opaque"]);
// skybox PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC cubemapPSODesc = opaquePSODesc;
// forbide back culling
cubemapPSODesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
// let cubemap z = 1 pass z-test, otherwise it'll be failed in z-test because data of zbuffer is 1
cubemapPSODesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
cubemapPSODesc.pRootSignature = m_rootSignature.Get();
cubemapPSODesc.VS =
{
reinterpret_cast<BYTE*>(m_shaders["CubemapVS"]->GetBufferPointer()),
m_shaders["CubemapVS"]->GetBufferSize()
};
cubemapPSODesc.PS =
{
reinterpret_cast<BYTE*>(m_shaders["CubemapPS"]->GetBufferPointer()),
m_shaders["CubemapPS"]->GetBufferSize()
};
ThrowIfFailed(m_device->CreateGraphicsPipelineState(&cubemapPSODesc, IID_PPV_ARGS(&m_PSOs["cubemap"])));
NAME_D3D12_OBJECT(m_PSOs["cubemap"]);
shader
cbuffer ObjectCB : register(b0)
{
float4x4 world;
}
cbuffer PassCB : register(b1)
{
float4x4 view;
float4x4 inverseView;
float4x4 projection;
float4x4 inverseProjection;
float4x4 viewProjection;
float4x4 inverseViewProjection;
float3 eyeWorldPosition;
Light lights[MAX_LIGHT_COUNT];
}
cbuffer MaterialCB : register(b2)
{
int specualrShiness;
float ambientAlbedo;
}
SamplerState g_SamperPointWrap : register(s0);
SamplerState g_SamperPointClamp : register(s1);
SamplerState g_SamperLinerWrap : register(s2);
SamplerState g_SamperLinerClamp : register(s3);
SamplerState g_SamperAnisotropyWrap : register(s4);
SamplerState g_SamperAnisotropyClamp : register(s5);
Texture2D g_diffuseTexture : register(t0, space0);
Texture2D g_specularTexture : register(t1, space0);
TextureCube g_cubemap : register(t2, space0);
struct VSInput
{
float3 positionL : POSITION;
float3 normalL : NORMAL;
float2 uv : TEXCOORD;
};
struct PSInput
{
float3 positionL : POSITION;
float4 positionH : SV_POSITION;
};
// VS
PSInput main(VSInput input)
{
PSInput output;
// 用于立方体贴图采样的查找向量
output.positionL = input.positionL;
float4 positionW = mul(float4(output.positionL, 1.f), world);
// 天空球始终以相机为中心
positionW.xyz += eyeWorldPosition;
// Set z = w so that z/w = 1,使得立方体纹理始终位于远平面
output.positionH = mul(positionW, viewProjection).xyww;
return output;
}
// PS
float4 main(PSInput input) : SV_TARGET
{
return g_cubemap.Sample(g_SamperLinerWrap, input.positionL);
}
输出
reference
Texassemble · microsoft/DirectXTex Wiki)
[Game-Programmer-Study-Notes/README.md at master · QianMo](https://github.com/QianMo/Game-Programmer-Study-Notes/blob/master/Content/《Real-Time Rendering 3rd》读书笔记/README.md)
标签:天空,pRenderer,实现,pSkybox,register,纹理,DX12,opaquePSODesc,D3D12 From: https://www.cnblogs.com/chenglixue/p/17374050.html