天空盒子,分为两个章节来讲解。本章主要讲解D3D12 DDS工具和实现环境纹理
DDS工具介绍
DirectXTex纹理处理库, DDS是一种图片格式,由NVIDIA公司开发。大部分3D游戏引擎都可以使用DDS格式的图片用作贴图,也可以制作法线贴图。通过安装DDS插件后可以在PhotoShop中打开。全部完整源代码下载:GitHub - microsoft/DirectXTex: DirectXTex texture processing library
选择编译DirectXTex_Desktop_2019.sln或DirectXTex_Desktop_2022.sln

Texconv.exe
Texconv.exe -f R8G8B8A8_UNORM 1.jpg -y
把1.jpg换成 1.dds
Texassemble.exe
texassemble.exe cube -o out.dds format:R8G8B8A8_UNORM 1.png 2.png 3.png 4.png 5.png 6.png
输出 立方体贴图dds
准备工作
用Texassemble.exe工具制作立方体贴图
天空盒子纹理通常是一个立方体贴图(Cube Map),它包含了六个面的纹理图像。准备了六张图片,图片自己画上数字分别是12346。观察点是在立方体内面或是中心点,就像进入到房间后关门,能看到的范围。这就是作为背景环境纹理。图片如下:

背景环境纹理-立方体贴图:

工程代码整理
增加publicLib工程,把之前代码整理成一个LIB库,一些通用操作封装成一基类,用继承方式实现差异操作,以提高代码质量。

#pragma once
#include "..\publicLib\CD3D12Basic.h"
#include "..\publicLib\D2DText.h"
#include "..\publicLib\D3D12Observer.h"
struct SSKYBOX_FRAME_MVP_BUFFER
{
XMFLOAT4X4 xm4X4MVP;
};
struct SKYBOX_VERTEX
{
public:
XMFLOAT4 v4Position;
public:
SKYBOX_VERTEX() :v4Position() {}
SKYBOX_VERTEX(float x, float y, float z)
:v4Position(x, y, z, 1.0f)
{
}
SKYBOX_VERTEX& operator = (const SKYBOX_VERTEX& vt)
{
v4Position = vt.v4Position;
}
};
class CD3D12Skybox :public CD3D12Basic
{
public:
void OnInit(HWND h);
void OnRender(void);
void UpdateData(void);
protected:
void PopulateCommandList(void);
private:
void LayoutRootSignature();
void CreateShaderResourceViewInfo();
void InitializeVertexBuffer(ComPtr<ID3D12Resource>& vertexBuffer, D3D12_VERTEX_BUFFER_VIEW& vertexBufferView);
void CreateConstantBufferViewInfo(D3D12_CPU_DESCRIPTOR_HANDLE hCPUDesc);
void CreateGpuGraphicsPipelineState(ComPtr<ID3D12Device>device);
ComPtr<ID3D12Resource> m_ddsBackgroundRes;
SSKYBOX_FRAME_MVP_BUFFER* m_pMVPSkyboxData = nullptr;
CD3D12Observer m_dx12Observer;
};
与第二、三章的代码功能差不多。这里列出资源情况:

1. 新增点 “深度模板视图” CommittedResource默认堆资源,这里没有用上传堆,也就是默认设置,创建代码
void CD3D12Basic::CreateDepthStencilViewDefault(void)
{
CreateDescriptorHeap(m_device, m_depthStencilViewHeap, D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
CD3DX12_HEAP_PROPERTIES heapdes = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT,0,0);
CD3DX12_RESOURCE_DESC resDesc = CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, m_nW, m_nH);
resDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE depthOptimizedClearValue = {};
depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT;
depthOptimizedClearValue.DepthStencil.Depth = 1.0f;
depthOptimizedClearValue.DepthStencil.Stencil = 0;
CreateCommittedResource(m_device, heapdes, D3D12_HEAP_FLAG_NONE,resDesc, D3D12_RESOURCE_STATE_DEPTH_WRITE,m_depthStencilViewBuffer, &depthOptimizedClearValue);
D3D12_DEPTH_STENCIL_VIEW_DESC stDepthStencilDesc = {};
stDepthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT;
stDepthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
stDepthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;
m_device->CreateDepthStencilView(m_depthStencilViewBuffer.Get()
, &stDepthStencilDesc
, m_depthStencilViewHeap->GetCPUDescriptorHandleForHeapStart());
}
2. 交换链与RTV其实是绑定在一起的,RTV要引用交换链的资源。
3.CBV与SRV,共用一个描述符堆,SRV的资源必须是默认堆资源(位GPU上),CBV的资源可以是上传堆(简单化)
环境纹理
1.加载立方体贴图
api函数LoadDDSTextureFromFileEx(),会返一个CommittedResource默认堆资源和资源数据,
我们要创建一个CommittedResource上传资源,然后提交资源数据到默认堆资源上。代码:
void CD3D12Basic::LoadCommittedResourceFromDDSFile(wstring pathFile, ComPtr<ID3D12Resource>& ddsRes, ComPtr<ID3D12CommandQueue> commandQueue, ComPtr<ID3D12GraphicsCommandList> commandList, ComPtr<ID3D12Fence> fence, UINT64 fenceValue,D3D12_RESOURCE_STATES stateAfter)
{
unique_ptr<uint8_t[]> ddsData;
vector<D3D12_SUBRESOURCE_DATA> subresources;
LoadDDSFromFile(pathFile, ddsRes, ddsData, subresources);
HANDLE fenceEvent = CreateEvent(NULL,false,false,NULL);
ComPtr<ID3D12Resource> uploadRes;
auto size = GetRequiredIntermediateSize(ddsRes.Get(), 0, subresources.size());
CreateCommittedResource(m_device, CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD)
, D3D12_HEAP_FLAG_NONE, CD3DX12_RESOURCE_DESC::Buffer(size), D3D12_RESOURCE_STATE_GENERIC_READ, uploadRes);
UpdateSubresources(commandList.Get(), ddsRes.Get(), uploadRes.Get(), 0, 0, subresources.size(), subresources.data());
auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(ddsRes.Get(), D3D12_RESOURCE_STATE_COPY_DEST, stateAfter);
commandList->ResourceBarrier(1, &barrier);
ThrowIfFailed(commandList->Close());
ID3D12CommandList* ppCommandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
WaitForFenceCompletion(commandQueue, fence, fenceEvent, fenceValue);
CloseHandle(fenceEvent);
}
1. UpdateSubresources()去复制资源到上传堆上
2.ExecuteCommandLists()命令队列去执行把上传堆数据 复制 到 默认堆 上
3. WaitForFenceCompletion()用围栏等GPU执行完成
4. 创建SRV, 指定用默认堆资源,代码:
void CD3D12Skybox::CreateShaderResourceViewInfo()
{
D3D12_RESOURCE_DESC resDesc = m_ddsBackgroundRes->GetDesc();
D3D12_SHADER_RESOURCE_VIEW_DESC stSRVDesc = {};
stSRVDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
stSRVDesc.Format = resDesc.Format;
stSRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
stSRVDesc.Texture2D.MipLevels = resDesc.MipLevels;
CreateShaderResourceView(m_device, m_ddsBackgroundRes.Get(), stSRVDesc, m_cbvSrvUavDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
}
2.顶点数据Vertex
1.定义顶点数据结构
struct SKYBOX_VERTEX
{
public:
XMFLOAT4 v4Position;
public:
SKYBOX_VERTEX() :v4Position() {}
SKYBOX_VERTEX(float x, float y, float z)
:v4Position(x, y, z, 1.0f)
{
}
SKYBOX_VERTEX& operator = (const SKYBOX_VERTEX& vt)
{
v4Position = vt.v4Position;
}
};
2. 设置显示视图区大小
环境纹理要满窗口显示 D3D12可视图范围是是-1到1,要定义两个三角形顶点,代码如下
SKYBOX_VERTEX data[4] = { };
float pos = 1.125f;
data[0].v4Position = XMFLOAT4(pos, pos, 1.0f, 1.0f);
data[1].v4Position = XMFLOAT4(pos, -pos, 1.0f, 1.0f);
data[2].v4Position = XMFLOAT4(-pos, pos, 1.0f, 1.0f);
data[3].v4Position = XMFLOAT4(-pos, -pos, 1.0f, 1.0f);
1x,y设置为1.125或-1.125 是为窗口只能显示部分环境纹理
XMFLOAT4使用左手坐标,z值设置1最里面(最远端)从观察点看先看到 -1点 再到0点,最后1
3. 增加顶点缓冲资源 为了简单化用上传堆,填充D3D12_VERTEX_BUFFER_VIEW 结构,完整代码
void CD3D12Skybox::InitializeVertexBuffer(ComPtr<ID3D12Resource>& vertexBuffer, D3D12_VERTEX_BUFFER_VIEW& vertexBufferView)
{
SKYBOX_VERTEX data[4] = { };
float pos = 1.125f;
data[0].v4Position = XMFLOAT4(pos, pos, 1.0f, 1.0f);
data[1].v4Position = XMFLOAT4(pos, -pos, 1.0f, 1.0f);
data[2].v4Position = XMFLOAT4(-pos, pos, 1.0f, 1.0f);
data[3].v4Position = XMFLOAT4(-pos, -pos, 1.0f, 1.0f);
int cbvSize = sizeof(data);
CreateCommittedResource(m_device, CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, CD3DX12_RESOURCE_DESC::Buffer(cbvSize), D3D12_RESOURCE_STATE_GENERIC_READ, vertexBuffer);
SKYBOX_VERTEX* pVertexDataBegin = nullptr;
vertexBuffer->Map(0, nullptr, (void**)&pVertexDataBegin);
memcpy(pVertexDataBegin, &data, cbvSize);
vertexBuffer->Unmap(0, nullptr);
vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.StrideInBytes = sizeof(SKYBOX_VERTEX);
vertexBufferView.SizeInBytes = cbvSize;
}
3.创建常量缓冲区
设置常量缓冲区目的是cpu计算出 计算3D世界空间矩阵转换,投影空间矩阵 相乘 观察空间矩阵 的结果放在常量缓冲区, HSLS程序 再乘 局部空间矩阵最终得到 裁剪空间(第三章有这部份说明)
1.定义数据结构
struct SSKYBOX_FRAME_MVP_BUFFER
{
XMFLOAT4X4 xm4X4MVP;
};
2. 创建常量缓冲区 数据源引用 一个上传堆资源 ,代码
void CD3D12Skybox::CreateConstantBufferViewInfo(D3D12_CPU_DESCRIPTOR_HANDLE hCPUDesc)
{
int cbvSize = sizeof(SSKYBOX_FRAME_MVP_BUFFER);
cbvSize = UPPER_ALING_DIV(cbvSize, 256);
CreateCommittedResource(m_device, CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, CD3DX12_RESOURCE_DESC::Buffer(cbvSize), D3D12_RESOURCE_STATE_GENERIC_READ, m_cbvResource);
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = m_cbvResource->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = cbvSize;
D3D12_CPU_DESCRIPTOR_HANDLE startCbv = GetCPUDescriptorHandleForHeapStart(m_cbvSrvUavDescriptorHeap,1);
CreateConstantBufferView(m_device, cbvDesc, startCbv);
m_cbvResource->Map(0, nullptr, (void**)&m_pMVPSkyboxData);
}
3. 计算 观察空间矩阵 相乘 投影空间矩阵 的代码
void CD3D12Skybox::UpdateData(void)
{
#if 0
static ULONGLONG n64tmFrameStart = 0;
static double dYAngle = 0;
XMFLOAT3 move = {};
double dbPalstance = 15.0f * XM_PI / 180.0f;
ULONGLONG n64tmCurrent = ::GetTickCount64();
dYAngle += ((n64tmCurrent - n64tmFrameStart) / 1000.0f) * dbPalstance;
n64tmFrameStart = n64tmCurrent;
if (dYAngle > XM_2PI)
{
dYAngle = fmod(dYAngle, XM_2PI);
}
XMMATRIX model = XMMatrixRotationY(static_cast<float>(dYAngle));
XMMATRIX xmView = m_dx12Observer.GetViewMatrix();
XMMATRIX xmProj = m_dx12Observer.GetProjectionMatrix(1.2f, (FLOAT)m_nW / (FLOAT)m_nH);
XMMATRIX modelView = XMMatrixMultiply(model, xmView);
XMMATRIX xmMVp = XMMatrixMultiply(modelView, xmProj);
xmMVp = XMMatrixInverse(nullptr, xmMVp);
//xmMVp = XMMatrixTranspose(xmMVp);
XMStoreFloat4x4(&m_pMVPSkyboxData->xm4X4MVP, xmMVp);
#else
XMMATRIX xmView = m_dx12Observer.GetViewMatrix();
XMMATRIX xmProj = m_dx12Observer.GetProjectionMatrix(1.2f, (FLOAT)m_nW / (FLOAT)m_nH);
XMMATRIX xmMVp = XMMatrixMultiply(xmView, xmProj);
xmMVp = XMMatrixInverse(nullptr, xmMVp);
XMStoreFloat4x4(&m_pMVPSkyboxData->xm4X4MVP, xmMVp);
#endif
}
注释部分是 实现一个自动旋转功能,世界空间 乘 观察空间 乘 投影空间矩阵的结HSLS
4.HSLS程序
1.自定义数据结构 skybox.hlsli
struct VSInput
{
float4 pos : POSITION;
};
struct PSInput
{
float4 pos : SV_POSITION;
float3 tex : TEXCOORD;
};
2.顶点着色器 SkyboxVs.hlsl
cbuffer SceneConstantBuffer : register(b0)
{
row_major matrix viewProjection;
}
PSInput main(VSInput pos : VSInput)
{
PSInput output;
output.pos = pos.pos;
output.tex = normalize(mul(pos.pos, viewProjection));
return output;
}
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "VSInput", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
viewProjection是常量缓冲区的值,顶点着色器要输出顶点位置,PSInput 做为参数传到像素着色器,所以用SV_POSITION标记输出.(SV_POSITION表示顶点在屏幕空间中的位置,POSITION表示模型空间中的位置),如果用
struct PSInput
{
//float4 pos : SV_POSITION;
float4 pos : POSITION;
float3 tex : TEXCOORD;
};
运行时就会报错
D3D12 ERROR: ID3D12Device::CreateGraphicsPipelineState: Rasterization Unit is enabled (PixelShader is not NULL or Depth/Stencil test is enabled and RasterizedStream is not D3D12_SO_NO_RASTERIZED_STREAM) but position is not provided by the last shader before the Rasterization Unit. [ STATE_CREATION ERROR #682: CREATEGRAPHICSPIPELINESTATE_POSITION_NOT_PRESENT]
3.像素着色器 SkyboxPs.hlsl
#include "skybox.hlsli"
TextureCube environmentCube : register(t0);
SamplerState samp : register(s0);
float4 main(PSInput input) : SV_TARGET
{
return environmentCube.Sample(samp, input.tex);
}
SRV用了立方体贴图所以 hlsl用TextureCube 纹理
逆矩阵
现在知道屏幕坐标pos.pos ,求世界坐标,通过 逆矩阵 xmMVp = XMMatrixInverse(nullptr, xmMVp); 世界坐标= 屏幕坐标 * MVP的逆矩阵, output.tex = normalize(mul(pos.pos, viewProjection)); 详情见第三章-逆矩阵
运行效果
1.设置透视投影矩阵 视场角度2.0
XMMATRIX xmProj = m_dx12Observer.GetProjectionMatrix(1.2f, (FLOAT)m_nW / (FLOAT)m_nH);
效果:
2.设置透视投影矩阵 视场角度0.8
XMMATRIX xmProj = m_dx12Observer.GetProjectionMatrix(0.8f, (FLOAT)m_nW / (FLOAT)m_nH);
效果:
3. 更换资源立方体贴图,透视投影矩阵 视场角度0.8

感谢大家的支持,如要问题欢迎提问指正。
3313

被折叠的 条评论
为什么被折叠?



