第 7 章 利用 Direct3D 绘制几何体(续)
本章继续深入探讨 Direct3D 几何体绘制的高级主题,包括纹理映射、不同几何体生成算法、根签名详解以及地形系统的实现。这些技术为创建复杂、真实的 3D 场景提供了必要的基础。
7.1 纹理资源
纹理是为几何体表面添加细节和真实感的重要手段。在 Direct3D 中,纹理作为一种特殊的资源,需要特定的创建、加载和使用方法。
7.1.1 纹理类型与格式
Direct3D 支持多种纹理类型:
2D 纹理:最常见的纹理类型,用于表面贴图
1D 纹理:通常用于梯度或查找表
3D 纹理:体积纹理,存储三维空间中的数据
立方体纹理:环境映射的特殊纹理形式
纹理格式决定了每个纹素(texel)如何存储在内存中:
cpp
// 常见纹理格式及其用途
struct TextureFormatInfo {
DXGI_FORMAT format;
const char* description;
const char* commonUse;
};
TextureFormatInfo formatTable[] = {
{ DXGI_FORMAT_R8G8B8A8_UNORM, "8位/通道 RGBA", "标准颜色纹理" },
{ DXGI_FORMAT_BC1_UNORM, "块压缩1(DXT1)", "不透明或一位alpha纹理" },
{ DXGI_FORMAT_BC3_UNORM, "块压缩3(DXT5)", "带alpha通道的纹理" },
{ DXGI_FORMAT_BC5_UNORM, "块压缩5", "法线贴图(RG通道)" },
{ DXGI_FORMAT_BC6H_UF16, "块压缩6H", "HDR纹理" },
{ DXGI_FORMAT_BC7_UNORM, "块压缩7", "高质量压缩纹理" },
{ DXGI_FORMAT_R16G16B16A16_FLOAT, "16位/通道浮点RGBA", "HDR渲染和后处理" },
{ DXGI_FORMAT_R32G32B32A32_FLOAT, "32位/通道浮点RGBA", "高精度计算" },
{ DXGI_FORMAT_D24_UNORM_S8_UINT, "24位深度+8位模板", "深度/模板缓冲区" }
};
7.1.2 创建和加载纹理
从文件加载纹理是常见操作,DirectX 提供了 DirectX Texture Library (DirectXTex) 来简化这一过程:
cpp
// 使用 DirectXTex 加载纹理的辅助函数
ComPtr<ID3D12Resource> LoadTexture(
ID3D12Device* device,
ID3D12GraphicsCommandList* cmdList,
const wchar_t* filename,
ComPtr<ID3D12Resource>& uploadBuffer) {
// 加载并解码图像
DirectX::ScratchImage scratchImage;
DirectX::LoadFromWICFile(
filename,
DirectX::WIC_FLAGS_NONE,
nullptr,
scratchImage
);
// 获取顶层MIP
const DirectX::Image* img = scratchImage.GetImage(0, 0, 0);
// 创建纹理资源
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
textureDesc.Width = img->width;
textureDesc.Height = img->height;
textureDesc.DepthOrArraySize = 1;
textureDesc.MipLevels = scratchImage.GetMetadata().mipLevels;
textureDesc.Format = img->format;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ComPtr<ID3D12Resource> textureBuffer;
// 创建默认堆上的纹理资源
CD3DX12_HEAP_PROPERTIES defaultHeapProps(D3D12_HEAP_TYPE_DEFAULT);
device->CreateCommittedResource(
&defaultHeapProps,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&textureBuffer)
);
// 创建上传缓冲区
UINT64 uploadBufferSize = GetRequiredIntermediateSize(
textureBuffer.Get(), 0, scratchImage.GetImageCount()
);
CD3DX12_HEAP_PROPERTIES uploadHeapProps(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC uploadBufferDesc =
CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);
device->CreateCommittedResource(
&uploadHeapProps,
D3D12_HEAP_FLAG_NONE,
&uploadBufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&uploadBuffer)
);
// 准备上传纹理数据
D3D12_SUBRESOURCE_DATA subresourceData = {};
subresourceData.pData = img->pixels;
subresourceData.RowPitch = img->rowPitch;
subresourceData.SlicePitch = img->slicePitch;
// 复制数据到默认堆
UpdateSubresources(
cmdList,
textureBuffer.Get(),
uploadBuffer.Get(),
0, 0, 1,
&subresourceData
);
// 转换纹理为着色器资源状态
CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
textureBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
);
cmdList->ResourceBarrier(1, &barrier);
return textureBuffer;
}
7.1.3 纹理视图和采样器
要在着色器中使用纹理,需要创建着色器资源视图 (SRV) 和采样器状态:
cpp
// 创建纹理SRV
void CreateTextureSRV(
ID3D12Device* device,
ID3D12Resource* texture,
CD3DX12_CPU_DESCRIPTOR_HANDLE cpuHandle) {
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = texture->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = texture->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
device->CreateShaderResourceView(texture.Get(), &srvDesc, cpuHandle);
}
// 创建静态采样器描述
D3D12_STATIC_SAMPLER_DESC CreateStaticSamplerDesc(
UINT shaderRegister,
D3D12_FILTER filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE addressMode = D3D12_TEXTURE_ADDRESS_MODE_WRAP) {
D3D12_STATIC_SAMPLER_DESC samplerDesc = {};
samplerDesc.Filter = filter;
samplerDesc.AddressU = addressMode;
samplerDesc.AddressV = addressMode;
samplerDesc.AddressW = addressMode;
samplerDesc.MipLODBias = 0;
samplerDesc.MaxAnisotropy = 16;
samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
samplerDesc.BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE;
samplerDesc.MinLOD = 0.0f;
samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc.ShaderRegister = shaderRegister;
samplerDesc.RegisterSpace = 0;
samplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
return samplerDesc;
}
7.1.4 在着色器中使用纹理
在HLSL着色器中可以声明和使用纹理资源:
hlsl
// 纹理和采样器在着色器中的定义
Texture2D diffuseMap : register(t0);
SamplerState samplerState : register(s0);
// 像素着色器示例
float4 PS(PixelInput pin) : SV_TARGET
{
// 采样纹理
float4 diffuseColor = diffuseMap.Sample(samplerState, pin.TexCoord);
// 应用简单的照明
float3 lightDir = normalize(float3(0.5, -0.5, 0.5));
float NdotL = max(dot(pin.Normal, -lightDir), 0.0f);
float3 ambient = diffuseColor.rgb * 0.3f;
float3 diffuse = diffuseColor.rgb * NdotL * 0.7f;
return float4(ambient + diffuse, diffuseColor.a);
}
7.2 渲染项
随着场景复杂性增加,需要一种方式来组织和批量绘制几何体。渲染项(RenderItem)是一种常见的抽象,用于表示场景中的单个可渲染对象。
7.2.1 渲染项结构
cpp
// 渲染项结构
struct RenderItem {
// 世界矩阵
XMFLOAT4X4 World = MathHelper::Identity4x4();
// 对象常量缓冲区索引
UINT ObjCBIndex = -1;
// 材质索引
UINT MaterialIndex = 0;
// 几何体
MeshGeometry* Geo = nullptr;
// 图元类型
D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
// 绘制参数
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
INT BaseVertexLocation = 0;
// 构造函数
RenderItem() = default;
};
7.2.2 渲染项管理
管理渲染项可以通过创建渲染项容器,并将它们按材质和透明度等因素分类:
cpp
// 渲染项管理类
class RenderItemManager {
public:
RenderItemManager() = default;
// 添加新的渲染项
std::shared_ptr<RenderItem> AddRenderItem() {
auto renderItem = std::make_shared<RenderItem>();
mAllRenderItems.push_back(renderItem);
return renderItem;
}
// 按层次对渲染项进行分类
void SortRenderItems() {
mOpaqueItems.clear();
mTransparentItems.clear();
for (auto& ri : mAllRenderItems) {
// 检查材质是否透明
if (mMaterialMap[ri->MaterialIndex].IsTransparent()) {
mTransparentItems.push_back(ri);
} else {
mOpaqueItems.push_back(ri);
}
}
}
// 渲染所有项
void DrawRenderItems(
ID3D12GraphicsCommandList* cmdList,
const std::vector<std::shared_ptr<RenderItem>>& renderItems) {
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
auto objectCB = mCurrFrameResource->ObjectCB->Resource();
// 遍历所有渲染项
for (auto& ri : renderItems) {
// 设置顶点缓冲区
cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
// 设置索引缓冲区
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
// 设置图元类型
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);
// 设置根常量
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress();
objCBAddress += ri->ObjCBIndex * objCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);
// 绘制
cmdList->DrawIndexedInstanced(
ri->IndexCount,
1,
ri->StartIndexLocation,
ri->BaseVertexLocation,
0
);
}
}
private:
// 所有渲染项
std::vector<std::shared_ptr<RenderItem>> mAllRenderItems;
// 按不同类型分类的渲染项
std::vector<std::shared_ptr<RenderItem>> mOpaqueItems;
std::vector<std::shared_ptr<RenderItem>> mTransparentItems;
// 材质映射
std::unordered_map<UINT, Material> mMaterialMap;
// 当前帧资源
FrameResource* mCurrFrameResource = nullptr;
};
7.3 渲染过程中所用到的常量数据
渲染时需要向着色器传递多种常量数据,如变换矩阵、材质参数和光照信息。
7.3.1 常量缓冲区类型
不同类型的常量数据通常组织在不同的缓冲区中:
cpp
// 对象常量 - 对每个对象都不同
struct ObjectConstants {
XMFLOAT4X4 World = MathHelper::Identity4x4();
XMFLOAT4X4 TexTransform = MathHelper::Identity4x4();
UINT MaterialIndex;
UINT ObjPad0;
UINT ObjPad1;
UINT ObjPad2;
};
// 通过常量 - 对整个通过相同
struct PassConstants {
XMFLOAT4X4 View = MathHelper::Identity4x4();
XMFLOAT4X4 InvView = MathHelper::Identity4x4();
XMFLOAT4X4 Proj = MathHelper::Identity4x4();
XMFLOAT4X4 InvProj = MathHelper::Identity4x4();
XMFLOAT4X4 ViewProj = MathHelper::Identity4x4();
XMFLOAT4X4 InvViewProj = MathHelper::Identity4x4();
XMFLOAT3 EyePosW = {0.0f, 0.0f, 0.0f};
float PassPad0 = 0.0f;
XMFLOAT2 RenderTargetSize = {0.0f, 0.0f};
XMFLOAT2 InvRenderTargetSize = {0.0f, 0.0f};
float NearZ = 0.0f;
float FarZ = 0.0f;
float TotalTime = 0.0f;
float DeltaTime = 0.0f;
// 光照参数
XMFLOAT4 AmbientLight = {0.0f, 0.0f, 0.0f, 1.0f};
// 方向光源
XMFLOAT3 LightDirection = {0.0f, -1.0f, 0.0f};
float LightPad0;
XMFLOAT3 LightStrength = {1.0f, 1.0f, 1.0f};
float LightPad1;
// 点光源
XMFLOAT3 PointLightPosition = {0.0f, 0.0f, 0.0f};
float PointLightPad0;
XMFLOAT3 PointLightStrength = {0.0f, 0.0f, 0.0f};
float PointLightRange;
// 聚光灯
XMFLOAT3 SpotLightPosition = {0.0f, 0.0f, 0.0f};
float SpotLightPad0;
XMFLOAT3 SpotLightDirection = {0.0f, -1.0f, 0.0f};
float SpotLightPad1;
XMFLOAT3 SpotLightStrength = {0.0f, 0.0f, 0.0f};
float SpotLightRange;
float SpotLightInnerCone;
float SpotLightOuterCone;
float SpotLightPad2;
float SpotLightPad3;
};
// 材质常量 - 描述表面属性
struct MaterialConstants {
XMFLOAT4 DiffuseAlbedo = {1.0f, 1.0f, 1.0f, 1.0f};
XMFLOAT3 FresnelR0 = {0.01f, 0.01f, 0.01f};
float Roughness = 0.5f;
XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();
UINT DiffuseMapIndex = 0;
UINT NormalMapIndex = 0;
UINT MaterialPad0;
UINT MaterialPad1;
};
7.3.2 帧资源管理
为避免CPU等待GPU,可以使用多个帧资源:
cpp
// 帧资源类 - 包含每帧需要更新的缓冲区
struct FrameResource {
// 帧资源应有自己的命令分配器
ComPtr<ID3D12CommandAllocator> CmdListAlloc;
// 常量缓冲区
std::unique_ptr<UploadBuffer<ObjectConstants>> ObjectCB = nullptr;
std::unique_ptr<UploadBuffer<PassConstants>> PassCB = nullptr;
std::unique_ptr<UploadBuffer<MaterialConstants>> MaterialCB = nullptr;
// 围栏值,标识此资源何时可以被更新
UINT64 Fence = 0;
FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount, UINT materialCount) {
device->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(&CmdListAlloc)
);
ObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(
device, objectCount, true
);
PassCB = std::make_unique<UploadBuffer<PassConstants>>(
device, passCount, true
);
MaterialCB = std::make_unique<UploadBuffer<MaterialConstants>>(
device, materialCount, true
);
}
FrameResource(const FrameResource& rhs) = delete;
FrameResource& operator=(const FrameResource& rhs) = delete;
~FrameResource() {}
};
// 帧资源管理
class FrameResourceManager {
public:
FrameResourceManager(ID3D12Device* device, UINT frameCount,
UINT passCount, UINT objectCount, UINT materialCount)
: mDevice(device), mFrameCount(frameCount),
mPassCount(passCount), mObjectCount(objectCount), mMaterialCount(materialCount) {
// 创建帧资源
for (UINT i = 0; i < mFrameCount; ++i) {
mFrameResources.push_back(
std::make_unique<FrameResource>(
mDevice, mPassCount, mObjectCount, mMaterialCount
)
);
}
}
// 获取当前帧资源
FrameResource* CurrentFrameResource() {
return mFrameResources[mCurrFrameResourceIndex].get();
}
// 移动到下一帧
void Advance() {
mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % mFrameCount;
}
private:
ID3D12Device* mDevice;
UINT mFrameCount;
UINT mPassCount;
UINT mObjectCount;
UINT mMaterialCount;
UINT mCurrFrameResourceIndex = 0;
std::vector<std::unique_ptr<FrameResource>> mFrameResources;
};
7.3.3 更新常量缓冲区
每帧需要更新不同类型的常量缓冲区:
cpp
// 更新对象常量缓冲区
void UpdateObjectCBs(const std::vector<std::shared_ptr<RenderItem>>& renderItems) {
auto currObjectCB = mCurrFrameResource->ObjectCB.get();
// 更新每个渲染项的常量缓冲区
for (auto& ri : renderItems) {
// 仅在对象数据改变时才更新
if (ri->NumFramesDirty > 0) {
ObjectConstants objConstants;
// 转换到射积矩阵(GPU采用列优先)
XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(XMLoadFloat4x4(&ri->World)));
XMStoreFloat4x4(&objConstants.TexTransform, XMMatrixTranspose(XMLoadFloat4x4(&ri->TexTransform)));
objConstants.MaterialIndex = ri->MaterialIndex;
// 复制数据到常量缓冲区
currObjectCB->CopyData(ri->ObjCBIndex, objConstants);
// 减少脏帧计数
ri->NumFramesDirty--;
}
}
}
// 更新通过常量缓冲区
void UpdatePassCB() {
auto currPassCB = mCurrFrameResource->PassCB.get();
// 构建通过常量
PassConstants passConstants;
// 转换矩阵(列优先)
XMStoreFloat4x4(&passConstants.View, XMMatrixTranspose(mCamera.GetView()));
XMStoreFloat4x4(&passConstants.InvView, XMMatrixTranspose(mCamera.GetInvView()));
XMStoreFloat4x4(&passConstants.Proj, XMMatrixTranspose(mCamera.GetProj()));
XMStoreFloat4x4(&passConstants.InvProj, XMMatrixTranspose(mCamera.GetInvProj()));
XMStoreFloat4x4(&passConstants.ViewProj, XMMatrixTranspose(mCamera.GetViewProj()));
XMStoreFloat4x4(&passConstants.InvViewProj, XMMatrixTranspose(mCamera.GetInvViewProj()));
// 相机和时间信息
passConstants.EyePosW = mCamera.GetPosition3f();
passConstants.RenderTargetSize = XMFLOAT2((float)mWidth, (float)mHeight);
passConstants.InvRenderTargetSize = XMFLOAT2(1.0f / mWidth, 1.0f / mHeight);
passConstants.NearZ = mCamera.GetNearZ();
passConstants.FarZ = mCamera.GetFarZ();
passConstants.TotalTime = mTimer.TotalTime();
passConstants.DeltaTime = mTimer.DeltaTime();
// 光源信息
passConstants.AmbientLight = {0.25f, 0.25f, 0.35f, 1.0f};
passConstants.LightDirection = {0.57735f, -0.57735f, 0.57735f};
passConstants.LightStrength = {0.8f, 0.8f, 0.8f};
// 复制到常量缓冲区
currPassCB->CopyData(0, passConstants);
}
// 更新材质常量缓冲区
void UpdateMaterialCBs(const std::unordered_map<std::string, Material>& materials) {
auto currMaterialCB = mCurrFrameResource->MaterialCB.get();
// 遍历所有材质
for (auto& [name, mat] : materials) {
// 只在材质数据改变时更新
if (mat.NumFramesDirty > 0) {
MaterialConstants matConstants;
matConstants.DiffuseAlbedo = mat.DiffuseAlbedo;
matConstants.FresnelR0 = mat.FresnelR0;
matConstants.Roughness = mat.Roughness;
XMStoreFloat4x4(&matConstants.MatTransform, XMMatrixTranspose(XMLoadFloat4x4(&mat.MatTransform)));
matConstants.DiffuseMapIndex = mat.DiffuseSrvHeapIndex;
matConstants.NormalMapIndex = mat.NormalSrvHeapIndex;
// 复制到常量缓冲区
currMaterialCB->CopyData(mat.MatCBIndex, matConstants);
// 减少脏帧计数
mat.NumFramesDirty--;
}
}
}
7.4 不同形状的几何体
DirectX 不提供内置的几何体生成函数,开发者需要自行创建常见的几何形状。
7.4.1 生成柱体网格
柱体是常见的基础几何体,适用于多种场景:
cpp
// 生成柱体网格
MeshData CreateCylinder(float bottomRadius, float topRadius, float height, uint32_t sliceCount, uint32_t stackCount) {
MeshData meshData;
// 生成顶点
float stackHeight = height / stackCount;
float radiusStep = (topRadius - bottomRadius) / stackCount;
uint32_t ringCount = stackCount + 1;
// 计算环顶点
for (uint32_t i = 0; i < ringCount; ++i) {
float y = -0.5f * height + i * stackHeight;
float r = bottomRadius + i * radiusStep;
// 生成环上的顶点
float dTheta = 2.0f * XM_PI / sliceCount;
for (uint32_t j = 0; j <= sliceCount; ++j) {
Vertex vertex;
float c = cosf(j * dTheta);
float s = sinf(j * dTheta);
vertex.Position = XMFLOAT3(r * c, y, r * s);
// 纹理坐标
vertex.TexC.x = (float)j / sliceCount;
vertex.TexC.y = 1.0f - (float)i / stackCount;
// 切线向量
vertex.TangentU = XMFLOAT3(-s, 0.0f, c);
// 法线向量 (需要处理不同半径的情况)
float dr = bottomRadius - topRadius;
XMFLOAT3 bitangent(dr * c, -height, dr * s);
XMVECTOR T = XMLoadFloat3(&vertex.TangentU);
XMVECTOR B = XMLoadFloat3(&bitangent);
XMVECTOR N = XMVector3Normalize(XMVector3Cross(T, B));
XMStoreFloat3(&vertex.Normal, N);
meshData.Vertices.push_back(vertex);
}
}
// 计算索引
uint32_t ringVertexCount = sliceCount + 1;
// 侧面三角形索引
for (uint32_t i = 0; i < stackCount; ++i) {
for (uint32_t j = 0; j < sliceCount; ++j) {
// 当前环起始索引
uint32_t currentRingStart = i * ringVertexCount;
uint32_t nextRingStart = (i + 1) * ringVertexCount;
// 两个三角形组成一个面片
meshData.Indices32.push_back(currentRingStart + j);
meshData.Indices32.push_back(nextRingStart + j);
meshData.Indices32.push_back(nextRingStart + j + 1);
meshData.Indices32.push_back(currentRingStart + j);
meshData.Indices32.push_back(nextRingStart + j + 1);
meshData.Indices32.push_back(currentRingStart + j + 1);
}
}
// 构建底部圆盖
uint32_t baseIndex = (uint32_t)meshData.Vertices.size();
float y = -0.5f * height;
// 添加底部中心顶点
Vertex centerVertex;
centerVertex.Position = XMFLOAT3(0.0f, y, 0.0f);
centerVertex.Normal = XMFLOAT3(0.0f, -1.0f, 0.0f);
centerVertex.TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
centerVertex.TexC = XMFLOAT2(0.5f, 0.5f);
meshData.Vertices.push_back(centerVertex);
// 添加底部边缘顶点
for (uint32_t i = 0; i <= sliceCount; ++i) {
float theta = 2.0f * XM_PI * i / sliceCount;
Vertex vertex;
vertex.Position = XMFLOAT3(bottomRadius * cosf(theta), y, bottomRadius * sinf(theta));
vertex.Normal = XMFLOAT3(0.0f, -1.0f, 0.0f);
vertex.TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
// 纹理坐标
vertex.TexC.x = 0.5f + 0.5f * cosf(theta);
vertex.TexC.y = 0.5f + 0.5f * sinf(theta);
meshData.Vertices.push_back(vertex);
}
// 底部三角形索引
uint32_t centerIndex = baseIndex;
for (uint32_t i = 0; i < sliceCount; ++i) {
meshData.Indices32.push_back(centerIndex);
meshData.Indices32.push_back(baseIndex + i + 2);
meshData.Indices32.push_back(baseIndex + i + 1);
}
// 构建顶部圆盖(类似底部)
baseIndex = (uint32_t)meshData.Vertices.size();
y = 0.5f * height;
// 添加顶部中心顶点
centerVertex.Position = XMFLOAT3(0.0f, y, 0.0f);
centerVertex.Normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
centerVertex.TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
centerVertex.TexC = XMFLOAT2(0.5f, 0.5f);
meshData.Vertices.push_back(centerVertex);
// 添加顶部边缘顶点
for (uint32_t i = 0; i <= sliceCount; ++i) {
float theta = 2.0f * XM_PI * i / sliceCount;
Vertex vertex;
vertex.Position = XMFLOAT3(topRadius * cosf(theta), y, topRadius * sinf(theta));
vertex.Normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
vertex.TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
// 纹理坐标
vertex.TexC.x = 0.5f + 0.5f * cosf(theta);
vertex.TexC.y = 0.5f + 0.5f * sinf(theta);
meshData.Vertices.push_back(vertex);
}
// 顶部三角形索引
centerIndex = baseIndex;
for (uint32_t i = 0; i < sliceCount; ++i) {
meshData.Indices32.push_back(centerIndex);
meshData.Indices32.push_back(baseIndex + i + 1);
meshData.Indices32.push_back(baseIndex + i + 2);
}
return meshData;
}
7.4.2 生成球体网格
球体是另一种常见的基础几何体:
cpp
// 生成球体网格
MeshData CreateSphere(float radius, uint32_t sliceCount, uint32_t stackCount) {
MeshData meshData;
// 计算顶点
Vertex topVertex;
topVertex.Position = XMFLOAT3(0.0f, radius, 0.0f);
topVertex.Normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
topVertex.TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
topVertex.TexC = XMFLOAT2(0.0f, 0.0f);
Vertex bottomVertex;
bottomVertex.Position = XMFLOAT3(0.0f, -radius, 0.0f);
bottomVertex.Normal = XMFLOAT3(0.0f, -1.0f, 0.0f);
bottomVertex.TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
bottomVertex.TexC = XMFLOAT2(0.0f, 1.0f);
meshData.Vertices.push_back(topVertex);
float phiStep = XM_PI / stackCount;
float thetaStep = 2.0f * XM_PI / sliceCount;
// 计算环顶点
for (uint32_t i = 1; i < stackCount; ++i) {
float phi = i * phiStep;
// 对于给定的phi,在每个theta角度上采样点
for (uint32_t j = 0; j <= sliceCount; ++j) {
float theta = j * thetaStep;
Vertex v;
// 球面坐标转笛卡尔坐标
v.Position.x = radius * sinf(phi) * cosf(theta);
v.Position.y = radius * cosf(phi);
v.Position.z = radius * sinf(phi) * sinf(theta);
// 在球上,位置矢量即为法线方向
XMVECTOR p = XMLoadFloat3(&v.Position);
XMVECTOR n = XMVector3Normalize(p);
XMStoreFloat3(&v.Normal, n);
// 计算切线
v.TangentU.x = -radius * sinf(phi) * sinf(theta);
v.TangentU.y = 0.0f;
v.TangentU.z = radius * sinf(phi) * cosf(theta);
XMVECTOR T = XMLoadFloat3(&v.TangentU);
T = XMVector3Normalize(T);
XMStoreFloat3(&v.TangentU, T);
// 纹理坐标
v.TexC.x = theta / (2.0f * XM_PI);
v.TexC.y = phi / XM_PI;
meshData.Vertices.push_back(v);
}
}
meshData.Vertices.push_back(bottomVertex);
// 计算索引
// 北极三角扇
for (uint32_t i = 1; i <= sliceCount; ++i) {
meshData.Indices32.push_back(0); // 北极顶点
meshData.Indices32.push_back(i);
meshData.Indices32.push_back(i + 1);
}
// 中间区域四边形
uint32_t baseIndex = 1;
uint32_t ringVertexCount = sliceCount + 1;
for (uint32_t i = 0; i < stackCount - 2; ++i) {
for (uint32_t j = 0; j < sliceCount; ++j) {
// 当前环起始索引
uint32_t currentRingStart = baseIndex + i * ringVertexCount;
uint32_t nextRingStart = baseIndex + (i + 1) * ringVertexCount;
// 两个三角形组成一个面片
meshData.Indices32.push_back(currentRingStart + j);
meshData.Indices32.push_back(nextRingStart + j);
meshData.Indices32.push_back(nextRingStart + j + 1);
meshData.Indices32.push_back(currentRingStart + j);
meshData.Indices32.push_back(nextRingStart + j + 1);
meshData.Indices32.push_back(currentRingStart + j + 1);
}
}
// 南极三角扇
uint32_t southPoleIndex = (uint32_t)meshData.Vertices.size() - 1;
baseIndex = southPoleIndex - ringVertexCount;
for (uint32_t i = 0; i < sliceCount; ++i) {
meshData.Indices32.push_back(southPoleIndex);
meshData.Indices32.push_back(baseIndex + i + 1);
meshData.Indices32.push_back(baseIndex + i);
}
return meshData;
}
7.4.3 生成几何球体网格
几何球体(通常称为Geosphere或Geodesic Sphere)是从二十面体细分生成的近似球体:
cpp
// 生成几何球体网格
MeshData CreateGeosphere(float radius, uint32_t numSubdivisions) {
MeshData meshData;
// 限制细分级别
numSubdivisions = std::min<uint32_t>(numSubdivisions, 6);
// 近似黄金比例,用于定义正二十面体
const float X = 0.525731f;
const float Z = 0.850651f;
// 正二十面体的12个顶点
XMFLOAT3 pos[12] = {
XMFLOAT3(-X, 0.0f, Z), XMFLOAT3(X, 0.0f, Z),
XMFLOAT3(-X, 0.0f, -Z), XMFLOAT3(X, 0.0f, -Z),
XMFLOAT3(0.0f, Z, X), XMFLOAT3(0.0f, Z, -X),
XMFLOAT3(0.0f, -Z, X), XMFLOAT3(0.0f, -Z, -X),
XMFLOAT3(Z, X, 0.0f), XMFLOAT3(-Z, X, 0.0f),
XMFLOAT3(Z, -X, 0.0f), XMFLOAT3(-Z, -X, 0.0f)
};
// 正二十面体的20个面的索引
uint32_t k[60] = {
1,4,0, 4,9,0, 4,5,9, 8,5,4, 1,8,4,
1,10,8, 10,3,8, 8,3,5, 3,2,5, 3,7,2,
3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0,
10,1,6, 11,0,9, 2,11,9, 5,2,9, 11,2,7
};
// 将顶点集合和索引集合复制到临时数组
std::vector<XMFLOAT3> vertices(12);
std::vector<uint32_t> indices(60);
for (int i = 0; i < 12; ++i)
vertices[i] = pos[i];
for (int i = 0; i < 60; ++i)
indices[i] = k[i];
// 细分
for (uint32_t i = 0; i < numSubdivisions; ++i) {
std::vector<uint32_t> newIndices;
// 为每个三角形创建4个新三角形
for (size_t j = 0; j < indices.size(); j += 3) {
uint32_t v0 = indices[j];
uint32_t v1 = indices[j+1];
uint32_t v2 = indices[j+2];
// 计算三角形各边的中点
XMFLOAT3 a = vertices[v0];
XMFLOAT3 b = vertices[v1];
XMFLOAT3 c = vertices[v2];
// 计算中点
XMFLOAT3 m0 = XMFLOAT3(
0.5f * (a.x + b.x),
0.5f * (a.y + b.y),
0.5f * (a.z + b.z)
);
XMFLOAT3 m1 = XMFLOAT3(
0.5f * (b.x + c.x),
0.5f * (b.y + c.y),
0.5f * (b.z + c.z)
);
XMFLOAT3 m2 = XMFLOAT3(
0.5f * (c.x + a.x),
0.5f * (c.y + a.y),
0.5f * (c.z + a.z)
);
// 将中点添加到顶点集合
uint32_t i0 = (uint32_t)vertices.size();
vertices.push_back(m0);
uint32_t i1 = (uint32_t)vertices.size();
vertices.push_back(m1);
uint32_t i2 = (uint32_t)vertices.size();
vertices.push_back(m2);
// 创建四个新三角形
newIndices.push_back(v0); newIndices.push_back(i0); newIndices.push_back(i2);
newIndices.push_back(i0); newIndices.push_back(v1); newIndices.push_back(i1);
newIndices.push_back(i0); newIndices.push_back(i1); newIndices.push_back(i2);
newIndices.push_back(i2); newIndices.push_back(i1); newIndices.push_back(v2);
}
// 用新生成的索引替换旧索引
indices = newIndices;
}
// 将所有点投影到球面上
for (auto& v : vertices) {
// 将点归一化然后缩放到所需半径
XMVECTOR n = XMVector3Normalize(XMLoadFloat3(&v));
XMVECTOR p = XMVectorScale(n, radius);
XMStoreFloat3(&v, p);
}
// 创建最终的顶点数据,包括法线和纹理坐标
for (size_t i = 0; i < vertices.size(); ++i) {
Vertex vertex;
// 位置
vertex.Position = vertices[i];
// 法线 - 对于球体,法线即为从原点到顶点的方向
XMVECTOR p = XMLoadFloat3(&vertex.Position);
XMVECTOR n = XMVector3Normalize(p);
XMStoreFloat3(&vertex.Normal, n);
// 计算球面纹理坐标(基于球面坐标)
float theta = atan2f(vertex.Position.z, vertex.Position.x);
if (theta < 0.0f)
theta += XM_2PI;
float phi = acosf(vertex.Position.y / radius);
vertex.TexC.x = theta / XM_2PI;
vertex.TexC.y = phi / XM_PI;
// 计算切线(近似处理)
// 切线必须与法线垂直,且与纹理坐标对齐
XMVECTOR tangent = XMVector3Normalize(
XMVectorSet(-vertex.Position.z, 0.0f, vertex.Position.x, 0.0f));
XMStoreFloat3(&vertex.TangentU, tangent);
meshData.Vertices.push_back(vertex);
}
// 复制索引
meshData.Indices32.assign(indices.begin(), indices.end());
return meshData;
}
7.5 绘制多种几何体演示程序
为展示不同几何体的创建和渲染,下面实现一个几何体展示程序。
7.5.1 顶点缓冲区和索引缓冲区
创建统一的几何体管理类,负责生成和维护各种几何体:
cpp
// 几何体管理类
class GeometryGenerator {
public:
// 顶点结构
struct Vertex {
XMFLOAT3 Position;
XMFLOAT3 Normal;
XMFLOAT2 TexC;
XMFLOAT3 TangentU;
};
// 网格数据结构
struct MeshData {
std::vector<Vertex> Vertices;
std::vector<uint32_t> Indices32;
// 获取16位索引
std::vector<uint16_t>& GetIndices16() {
if (mIndices16.empty() && !Indices32.empty()) {
mIndices16.resize(Indices32.size());
for (size_t i = 0; i < Indices32.size(); ++i) {
mIndices16[i] = static_cast<uint16_t>(Indices32[i]);
}
}
return mIndices16;
}
private:
std::vector<uint16_t> mIndices16;
};
// 几何体生成函数
MeshData CreateBox(float width, float height, float depth, uint32_t numSubdivisions);
MeshData CreateSphere(float radius, uint32_t sliceCount, uint32_t stackCount);
MeshData CreateGeosphere(float radius, uint32_t numSubdivisions);
MeshData CreateCylinder(float bottomRadius, float topRadius, float height,
uint32_t sliceCount, uint32_t stackCount);
MeshData CreateGrid(float width, float depth, uint32_t m, uint32_t n);
MeshData CreateQuad(float x, float y, float w, float h, float depth);
// ... 函数实现与前面的相同 ...
};
// 实现绘制多个几何体的管理
class ShapesApp {
public:
ShapesApp(HWND hwnd);
~ShapesApp();
bool Initialize();
void Update(float dt);
void Draw();
private:
// 初始化各种组件
void BuildDescriptorHeaps();
void BuildConstantBuffers();
void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildShapeGeometry();
void BuildPSOs();
void BuildFrameResources();
void BuildMaterials();
void BuildRenderItems();
// 更新函数
void UpdateCamera(float dt);
void UpdateObjectCBs(float dt);
void UpdateMaterialCBs(float dt);
void UpdateMainPassCB(float dt);
// 绘制函数
void DrawRenderItems(ID3D12GraphicsCommandList* cmdList,
const std::vector<RenderItem*>& ritems);
// 几何体数据
std::unique_ptr<MeshGeometry> mShapesGeo = nullptr;
// 渲染项
std::vector<std::unique_ptr<RenderItem>> mAllRitems;
std::vector<RenderItem*> mOpaqueRitems;
// 材质
std::unordered_map<std::string, std::unique_ptr<Material>> mMaterials;
// 帧资源
std::vector<std::unique_ptr<FrameResource>> mFrameResources;
FrameResource* mCurrFrameResource = nullptr;
int mCurrFrameResourceIndex = 0;
// DirectX 相关资源
ComPtr<ID3D12RootSignature> mRootSignature = nullptr;
ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;
ComPtr<ID3D12DescriptorHeap> mSrvDescriptorHeap = nullptr;
// 纹理资源
std::unordered_map<std::string, ComPtr<ID3D12Resource>> mTextures;
// PSO
std::unordered_map<std::string, ComPtr<ID3D12PipelineState>> mPSOs;
// 渲染状态
XMFLOAT4X4 mView = MathHelper::Identity4x4();
XMFLOAT4X4 mProj = MathHelper::Identity4x4();
// 其他成员
UINT mPassCbvOffset = 0;
bool mIsWireframe = false;
// 相机信息
XMFLOAT3 mEyePos = {0.0f, 0.0f, 0.0f};
float mTheta = 1.5f * XM_PI;
float mPhi = 0.2f * XM_PI;
float mRadius = 15.0f;
};
7.5.2 渲染项
创建并配置不同几何体的渲染项:
cpp
// 创建渲染项
void ShapesApp::BuildRenderItems() {
// 立方体
auto boxRitem = std::make_unique<RenderItem>();
boxRitem->World = MathHelper::Identity4x4();
boxRitem->ObjCBIndex = 0;
boxRitem->Geo = mShapesGeo.get();
boxRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount;
boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation;
boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation;
boxRitem->Mat = mMaterials["brick0"].get();
mAllRitems.push_back(std::move(boxRitem));
// 球体
auto sphereRitem = std::make_unique<RenderItem>();
XMStoreFloat4x4(&sphereRitem->World, XMMatrixTranslation(3.0f, 2.0f, 0.0f));
sphereRitem->ObjCBIndex = 1;
sphereRitem->Geo = mShapesGeo.get();
sphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
sphereRitem->IndexCount = sphereRitem->Geo->DrawArgs["sphere"].IndexCount;
sphereRitem->StartIndexLocation = sphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
sphereRitem->BaseVertexLocation = sphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;
sphereRitem->Mat = mMaterials["stone0"].get();
mAllRitems.push_back(std::move(sphereRitem));
// 几何球体
auto geoSphereRitem = std::make_unique<RenderItem>();
XMStoreFloat4x4(&geoSphereRitem->World, XMMatrixTranslation(-3.0f, 2.0f, 0.0f));
geoSphereRitem->ObjCBIndex = 2;
geoSphereRitem->Geo = mShapesGeo.get();
geoSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
geoSphereRitem->IndexCount = geoSphereRitem->Geo->DrawArgs["geosphere"].IndexCount;
geoSphereRitem->StartIndexLocation = geoSphereRitem->Geo->DrawArgs["geosphere"].StartIndexLocation;
geoSphereRitem->BaseVertexLocation = geoSphereRitem->Geo->DrawArgs["geosphere"].BaseVertexLocation;
geoSphereRitem->Mat = mMaterials["mirror0"].get();
mAllRitems.push_back(std::move(geoSphereRitem));
// 圆柱体
auto cylinderRitem = std::make_unique<RenderItem>();
XMStoreFloat4x4(&cylinderRitem->World, XMMatrixTranslation(0.0f, 1.0f, 3.0f));
cylinderRitem->ObjCBIndex = 3;
cylinderRitem->Geo = mShapesGeo.get();
cylinderRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
cylinderRitem->IndexCount = cylinderRitem->Geo->DrawArgs["cylinder"].IndexCount;
cylinderRitem->StartIndexLocation = cylinderRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;
cylinderRitem->BaseVertexLocation = cylinderRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;
cylinderRitem->Mat = mMaterials["tile0"].get();
mAllRitems.push_back(std::move(cylinderRitem));
// 网格地面
auto gridRitem = std::make_unique<RenderItem>();
XMStoreFloat4x4(&gridRitem->World, XMMatrixTranslation(0.0f, 0.0f, 0.0f));
gridRitem->ObjCBIndex = 4;
gridRitem->Geo = mShapesGeo.get();
gridRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount;
gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
gridRitem->Mat = mMaterials["grass0"].get();
mAllRitems.push_back(std::move(gridRitem));
// 所有渲染项都是不透明的
for (auto& e : mAllRitems)
mOpaqueRitems.push_back(e.get());
}
7.5.3 帧内资源和常量缓冲区视图
配置帧资源和常量缓冲区:
cpp
// 创建帧资源
void ShapesApp::BuildFrameResources() {
for (int i = 0; i < gNumFrameResources; ++i) {
mFrameResources.push_back(std::make_unique<FrameResource>(
md3dDevice.Get(),
1, // 一个通过
(UINT)mAllRitems.size(), // 对象数量
(UINT)mMaterials.size() // 材质数量
));
}
}
// 创建常量缓冲区视图
void ShapesApp::BuildDescriptorHeaps() {
// 创建CBV堆
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = gNumFrameResources * (1 + (UINT)mAllRitems.size() + (UINT)mMaterials.size());
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;
md3dDevice->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&mCbvHeap));
// 创建SRV堆(纹理)
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc;
srvHeapDesc.NumDescriptors = mTextures.size();
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
srvHeapDesc.NodeMask = 0;
md3dDevice->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&mSrvDescriptorHeap));
// 构建CBV
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvHeapHandle(mCbvHeap->GetCPUDescriptorHandleForHeapStart());
// 通过CBV的偏移
mPassCbvOffset = (UINT)mAllRitems.size() + (UINT)mMaterials.size();
for (int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex) {
auto passCB = mFrameResources[frameIndex]->PassCB->Resource();
// 通过CBV
D3D12_GPU_VIRTUAL_ADDRESS passCBAddress = passCB->GetGPUVirtualAddress();
D3D12_CONSTANT_BUFFER_VIEW_DESC passCBDesc;
passCBDesc.BufferLocation = passCBAddress;
passCBDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants));
md3dDevice->CreateConstantBufferView(
&passCBDesc,
cbvHeapHandle
);
cbvHeapHandle.Offset(1, mCbvDescriptorSize);
// 对象CBV
auto objectCB = mFrameResources[frameIndex]->ObjectCB->Resource();
for (UINT i = 0; i < mAllRitems.size(); ++i) {
D3D12_GPU_VIRTUAL_ADDRESS objectCBAddress = objectCB->GetGPUVirtualAddress();
objectCBAddress += i * d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
D3D12_CONSTANT_BUFFER_VIEW_DESC objectCBDesc;
objectCBDesc.BufferLocation = objectCBAddress;
objectCBDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
md3dDevice->CreateConstantBufferView(
&objectCBDesc,
cbvHeapHandle
);
cbvHeapHandle.Offset(1, mCbvDescriptorSize);
}
// 材质CBV
auto materialCB = mFrameResources[frameIndex]->MaterialCB->Resource();
for (UINT i = 0; i < mMaterials.size(); ++i) {
D3D12_GPU_VIRTUAL_ADDRESS materialCBAddress = materialCB->GetGPUVirtualAddress();
materialCBAddress += i * d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));
D3D12_CONSTANT_BUFFER_VIEW_DESC materialCBDesc;
materialCBDesc.BufferLocation = materialCBAddress;
materialCBDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));
md3dDevice->CreateConstantBufferView(
&materialCBDesc,
cbvHeapHandle
);
cbvHeapHandle.Offset(1, mCbvDescriptorSize);
}
}
// 构建SRV
CD3DX12_CPU_DESCRIPTOR_HANDLE srvHeapHandle(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
// 创建纹理SRV
for (auto& [name, texture] : mTextures) {
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = texture->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = texture->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
md3dDevice->CreateShaderResourceView(
texture.Get(),
&srvDesc,
srvHeapHandle
);
srvHeapHandle.Offset(1, mCbvSrvUavDescriptorSize);
// 记录此纹理在堆中的索引
mTextures[name]->SrvHeapIndex = i;
++i;
}
}
7.5.4 绘制场景
实现绘制函数,渲染所有几何体:
cpp
// 绘制场景
void ShapesApp::Draw() {
auto cmdListAlloc = mCurrFrameResource->CmdListAlloc;
// 重用命令列表相关内存
cmdListAlloc->Reset();
// 重置命令列表
if (mIsWireframe) {
mCommandList->Reset(cmdListAlloc.Get(), mPSOs["wireframe"].Get());
} else {
mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque"].Get());
}
// 设置视口和裁剪矩形
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);
// 资源屏障 - 从呈现到渲染目标状态
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
CurrentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET
));
// 清除后台缓冲区和深度缓冲区
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
// 指定要渲染的缓冲区
mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());
// 设置描述符堆
ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get(), mSrvDescriptorHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
// 设置根签名
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
// 设置纹理表
CD3DX12_GPU_DESCRIPTOR_HANDLE srvHandle(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
mCommandList->SetGraphicsRootDescriptorTable(1, srvHandle);
// 设置通过常量
UINT passCbvIndex = mPassCbvOffset + mCurrFrameResourceIndex;
CD3DX12_GPU_DESCRIPTOR_HANDLE passCbvHandle(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
passCbvHandle.Offset(passCbvIndex, mCbvDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(3, passCbvHandle);
// 绘制不透明渲染项
DrawRenderItems(mCommandList.Get(), mOpaqueRitems);
// 资源屏障 - 渲染目标到呈现状态
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT
));
// 完成记录命令
mCommandList->Close();
// 添加命令列表到队列并执行
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// 交换缓冲区
mSwapChain->Present(0, 0);
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;
// 推进围栏值
mCurrFrameResource->Fence = ++mCurrentFence;
// 向命令队列添加信号指令
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}
// 绘制渲染项
void ShapesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems) {
// 遍历所有渲染项
for (size_t i = 0; i < ritems.size(); ++i) {
auto ri = ritems[i];
// 设置顶点缓冲区
cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
// 设置索引缓冲区
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
// 设置图元类型
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);
// 设置对象常量
UINT objCbvIndex = mCurrFrameResourceIndex * (UINT)mAllRitems.size() + ri->ObjCBIndex;
CD3DX12_GPU_DESCRIPTOR_HANDLE objCbvHandle(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
objCbvHandle.Offset(objCbvIndex, mCbvDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(0, objCbvHandle);
// 设置材质常量
UINT matCbvIndex = mCurrFrameResourceIndex * (UINT)mMaterials.size() + ri->Mat->MatCBIndex;
CD3DX12_GPU_DESCRIPTOR_HANDLE matCbvHandle(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
matCbvHandle.Offset(matCbvIndex, mCbvDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(2, matCbvHandle);
// 绘制
cmdList->DrawIndexedInstanced(
ri->IndexCount,
1,
ri->StartIndexLocation,
ri->BaseVertexLocation,
0
);
}
}
7.6 细探根签名
根签名是 Direct3D 12 中的核心概念,定义了着色器程序可以访问的资源。
7.6.1 根参数
根签名由一组根参数组成,每个根参数可以是以下三种类型之一:
根常量:直接在根签名中嵌入小块常量数据
根描述符:直接指向资源的 GPU 虚拟地址
描述符表:指向描述符堆中的一个范围
cpp
// 创建根签名示例
void CreateRootSignature(ID3D12Device* device, ID3D12RootSignature** rootSignature) {
// 创建根参数数组
CD3DX12_ROOT_PARAMETER slotRootParameter[4];
// 参数0:对象常量描述符表
CD3DX12_DESCRIPTOR_RANGE cbvTable0;
cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0);
// 参数1:纹理描述符表
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 5, 0); // 5个纹理
slotRootParameter[1].InitAsDescriptorTable(1, &texTable);
// 参数2:材质常量描述符表
CD3DX12_DESCRIPTOR_RANGE cbvTable1;
cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);
slotRootParameter[2].InitAsDescriptorTable(1, &cbvTable1);
// 参数3:通过常量描述符表
CD3DX12_DESCRIPTOR_RANGE cbvTable2;
cbvTable2.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 2);
slotRootParameter[3].InitAsDescriptorTable(1, &cbvTable2);
// 创建静态采样器数组
const CD3DX12_STATIC_SAMPLER_DESC samplers[] = {
// 点过滤采样器
CD3DX12_STATIC_SAMPLER_DESC(
0, // 着色器寄存器
D3D12_FILTER_MIN_MAG_MIP_POINT,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP
),
// 线性过滤采样器
CD3DX12_STATIC_SAMPLER_DESC(
1, // 着色器寄存器
D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP
),
// 各向异性过滤采样器
CD3DX12_STATIC_SAMPLER_DESC(
2, // 着色器寄存器
D3D12_FILTER_ANISOTROPIC,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
0.0f,
8
),
// 阴影采样器
CD3DX12_STATIC_SAMPLER_DESC(
3, // 着色器寄存器
D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT,
D3D12_TEXTURE_ADDRESS_MODE_BORDER,
D3D12_TEXTURE_ADDRESS_MODE_BORDER,
D3D12_TEXTURE_ADDRESS_MODE_BORDER,
0.0f,
16,
D3D12_COMPARISON_FUNC_LESS_EQUAL,
D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE
)
};
// 创建根签名描述
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(
4, slotRootParameter,
_countof(samplers), samplers,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
);
// 序列化根签名
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(
&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
&serializedRootSig, &errorBlob
);
if (errorBlob != nullptr) {
OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
// 创建根签名
device->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(rootSignature)
);
}
7.6.2 描述符表
描述符表是一种指向描述符堆中连续描述符范围的根参数:
cpp
// 设置根描述符表
void SetRootDescriptorTables(ID3D12GraphicsCommandList* cmdList,
ID3D12DescriptorHeap* descriptorHeap,
UINT cbvSrvUavDescriptorSize) {
// 设置描述符堆
ID3D12DescriptorHeap* heaps[] = { descriptorHeap };
cmdList->SetDescriptorHeaps(_countof(heaps), heaps);
// 获取堆的GPU句柄
CD3DX12_GPU_DESCRIPTOR_HANDLE handle(descriptorHeap->GetGPUDescriptorHandleForHeapStart());
// 设置第一个表(对象常量)
cmdList->SetGraphicsRootDescriptorTable(0, handle);
// 移动到下一个描述符范围(纹理)
handle.Offset(1, cbvSrvUavDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(1, handle);
// 移动到下一个描述符范围(材质常量)
handle.Offset(5, cbvSrvUavDescriptorSize); // 跳过5个纹理描述符
cmdList->SetGraphicsRootDescriptorTable(2, handle);
// 移动到下一个描述符范围(通过常量)
handle.Offset(1, cbvSrvUavDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(3, handle);
}
7.6.3 根描述符
根描述符直接指向资源的 GPU 虚拟地址,无需使用描述符堆:
cpp
// 使用根描述符的根签名
void CreateRootSignatureWithRootDescriptors(ID3D12Device* device, ID3D12RootSignature** rootSignature) {
// 创建根参数数组
CD3DX12_ROOT_PARAMETER slotRootParameter[3];
// 参数0:对象常量根描述符
slotRootParameter[0].InitAsConstantBufferView(0);
// 参数1:纹理描述符表
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 5, 0); // 5个纹理
slotRootParameter[1].InitAsDescriptorTable(1, &texTable);
// 参数2:通过常量根描述符
slotRootParameter[2].InitAsConstantBufferView(1);
// 创建静态采样器
CD3DX12_STATIC_SAMPLER_DESC sampler(
0, // 着色器寄存器
D3D12_FILTER_MIN_MAG_MIP_LINEAR
);
// 创建根签名描述
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(
3, slotRootParameter,
1, &sampler,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
);
// 序列化和创建根签名
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
D3D12SerializeRootSignature(
&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
&serializedRootSig, &errorBlob
);
device->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(rootSignature)
);
}
使用根描述符设置常量缓冲区:
cpp
// 使用根描述符设置常量缓冲区
void SetRootDescriptors(ID3D12GraphicsCommandList* cmdList,
ID3D12Resource* objectCB,
ID3D12Resource* passCB) {
// 设置对象常量缓冲区视图
cmdList->SetGraphicsRootConstantBufferView(0, objectCB->GetGPUVirtualAddress());
// 设置通过常量缓冲区视图
cmdList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());
}
7.6.4 根常量
根常量直接在根签名中嵌入少量常量数据:
cpp
// 使用根常量的根签名
void CreateRootSignatureWithRootConstants(ID3D12Device* device, ID3D12RootSignature** rootSignature) {
// 创建根参数数组
CD3DX12_ROOT_PARAMETER slotRootParameter[3];
// 参数0:根常量(4个32位值)
slotRootParameter[0].InitAsConstants(4, 0);
// 参数1:纹理描述符表
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
slotRootParameter[1].InitAsDescriptorTable(1, &texTable);
// 参数2:通过常量根描述符
slotRootParameter[2].InitAsConstantBufferView(1);
// 创建静态采样器
CD3DX12_STATIC_SAMPLER_DESC sampler(0);
// 创建根签名描述
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(
3, slotRootParameter,
1, &sampler,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
);
// 序列化和创建根签名
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
D3D12SerializeRootSignature(
&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
&serializedRootSig, &errorBlob
);
device->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(rootSignature)
);
}
设置根常量:
cpp
// 使用根常量设置转换数据
void SetRootConstants(ID3D12GraphicsCommandList* cmdList) {
// 设置转换数据(例如,世界矩阵的前16个浮点数)
float transformData[4] = {
1.0f, 0.0f, 0.0f, 0.0f // 世界矩阵的第一行
};
cmdList->SetGraphicsRoot32BitConstants(0, 4, transformData, 0);
}
7.6.5 更复杂的根签名示例
以下是一个更复杂的根签名示例,结合了多种参数类型:
cpp
// 复杂根签名示例
void CreateAdvancedRootSignature(ID3D12Device* device, ID3D12RootSignature** rootSignature) {
// 创建根参数数组
CD3DX12_ROOT_PARAMETER rootParams[6];
// 参数0:全局变量(4个32位根常量)
rootParams[0].InitAsConstants(4, 0, 0, D3D12_SHADER_VISIBILITY_ALL);
// 参数1:相机常量(根CBV)
rootParams[1].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_ALL);
// 参数2:材质常量(根CBV)
rootParams[2].InitAsConstantBufferView(1, 0, D3D12_SHADER_VISIBILITY_PIXEL);
// 参数3:纹理描述符表(5个纹理)
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 5, 0);
rootParams[3].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
// 参数4:体积纹理描述符表
CD3DX12_DESCRIPTOR_RANGE volumeTexTable;
volumeTexTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 5);
rootParams[4].InitAsDescriptorTable(1, &volumeTexTable, D3D12_SHADER_VISIBILITY_PIXEL);
// 参数5:UAV描述符表(计算着色器使用)
CD3DX12_DESCRIPTOR_RANGE uavTable;
uavTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0);
rootParams[5].InitAsDescriptorTable(1, &uavTable, D3D12_SHADER_VISIBILITY_ALL);
// 创建静态采样器数组
CD3DX12_STATIC_SAMPLER_DESC samplers[] = {
// 线性过滤采样器
CD3DX12_STATIC_SAMPLER_DESC(
0, // 着色器寄存器
D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
0.0f,
16,
D3D12_COMPARISON_FUNC_LESS_EQUAL,
D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE,
0.0f,
D3D12_FLOAT32_MAX,
D3D12_SHADER_VISIBILITY_PIXEL
),
// 点过滤采样器
CD3DX12_STATIC_SAMPLER_DESC(
1, // 着色器寄存器
D3D12_FILTER_MIN_MAG_MIP_POINT,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
0.0f,
16,
D3D12_COMPARISON_FUNC_LESS_EQUAL,
D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE,
0.0f,
D3D12_FLOAT32_MAX,
D3D12_SHADER_VISIBILITY_PIXEL
)
};
// 创建根签名描述
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(
_countof(rootParams), rootParams,
_countof(samplers), samplers,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
);
// 序列化和创建根签名
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(
&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
&serializedRootSig, &errorBlob
);
if (FAILED(hr)) {
if (errorBlob) {
OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
throw std::runtime_error("Failed to serialize root signature");
}
hr = device->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(rootSignature)
);
if (FAILED(hr)) {
throw std::runtime_error("Failed to create root signature");
}
}
7.6.6 根参数的版本控制
根签名支持不同的版本,用于控制可用的功能和特性:
cpp
// 根签名版本1.1
void CreateRootSignatureV1_1(ID3D12Device* device, ID3D12RootSignature** rootSignature) {
// 检查设备是否支持根签名版本1.1
D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
if (FAILED(device->CheckFeatureSupport(
D3D12_FEATURE_ROOT_SIGNATURE,
&featureData,
sizeof(featureData)))) {
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}
// 创建根参数
CD3DX12_ROOT_PARAMETER1 rootParams[3];
// 参数0:根CBV(使用1.1版本的新功能 - 缓冲区位置绑定)
rootParams[0].InitAsConstantBufferView(
0, // 寄存器
0, // 空间
D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC, // 标志 - 静态数据
D3D12_SHADER_VISIBILITY_ALL
);
// 参数1:纹理描述符表
CD3DX12_DESCRIPTOR_RANGE1 texTable;
texTable.Init(
D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
5, // 5个SRV
0, // 基础着色器寄存器
0, // 寄存器空间
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE // 标志
);
rootParams[1].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
// 参数2:根UAV(使用1.1版本的新功能)
rootParams[2].InitAsUnorderedAccessView(
0, // 寄存器
0, // 空间
D3D12_ROOT_DESCRIPTOR_FLAG_DATA_VOLATILE, // 标志 - 易变数据
D3D12_SHADER_VISIBILITY_ALL
);
// 创建静态采样器
CD3DX12_STATIC_SAMPLER_DESC sampler(0);
// 创建根签名描述
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSigDesc;
rootSigDesc.Init_1_1(
_countof(rootParams), rootParams,
1, &sampler,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
);
// 序列化和创建根签名
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3DX12SerializeVersionedRootSignature(
&rootSigDesc,
featureData.HighestVersion,
&serializedRootSig,
&errorBlob
);
if (FAILED(hr)) {
throw std::runtime_error("Failed to serialize root signature");
}
hr = device->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(rootSignature)
);
if (FAILED(hr)) {
throw std::runtime_error("Failed to create root signature");
}
}
7.7 陆地与波浪演示程序
为展示更复杂的几何体和动态效果,下面实现一个海洋与陆地演示程序。
7.7.1 生成栅格顶点
首先,创建一个平面网格作为海洋的基础:
cpp
// 创建网格顶点
MeshData CreateGrid(float width, float depth, uint32_t m, uint32_t n) {
MeshData meshData;
// 创建顶点
float halfWidth = 0.5f * width;
float halfDepth = 0.5f * depth;
float dx = width / (n - 1);
float dz = depth / (m - 1);
float du = 1.0f / (n - 1);
float dv = 1.0f / (m - 1);
// 生成顶点网格
meshData.Vertices.resize(m * n);
for (uint32_t i = 0; i < m; ++i) {
float z = halfDepth - i * dz;
for (uint32_t j = 0; j < n; ++j) {
float x = -halfWidth + j * dx;
// 平面顶点
Vertex& vertex = meshData.Vertices[i * n + j];
vertex.Position = XMFLOAT3(x, 0.0f, z);
vertex.Normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
vertex.TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
// 纹理坐标
vertex.TexC.x = j * du;
vertex.TexC.y = i * dv;
}
}
// 创建索引
meshData.Indices32.resize((m - 1) * (n - 1) * 6);
uint32_t k = 0;
for (uint32_t i = 0; i < m - 1; ++i) {
for (uint32_t j = 0; j < n - 1; ++j) {
// 计算四个角的索引
uint32_t i0 = i * n + j;
uint32_t i1 = i * n + j + 1;
uint32_t i2 = (i + 1) * n + j;
uint32_t i3 = (i + 1) * n + j + 1;
// 添加两个三角形
meshData.Indices32[k++] = i0;
meshData.Indices32[k++] = i2;
meshData.Indices32[k++] = i1;
meshData.Indices32[k++] = i1;
meshData.Indices32[k++] = i2;
meshData.Indices32[k++] = i3;
}
}
return meshData;
}
7.7.2 生成栅格索引
索引布局确保正确的三角形形成:
cpp
// 创建网格索引(如果需要特殊索引布局)
std::vector<uint16_t> CreateGridIndices(uint32_t m, uint32_t n) {
std::vector<uint16_t> indices;
indices.reserve((m - 1) * (n - 1) * 6);
for (uint32_t i = 0; i < m - 1; ++i) {
for (uint32_t j = 0; j < n - 1; ++j) {
// 计算四个角的索引
uint16_t i0 = static_cast<uint16_t>(i * n + j);
uint16_t i1 = static_cast<uint16_t>(i * n + j + 1);
uint16_t i2 = static_cast<uint16_t>((i + 1) * n + j);
uint16_t i3 = static_cast<uint16_t>((i + 1) * n + j + 1);
// 添加两个三角形
indices.push_back(i0);
indices.push_back(i2);
indices.push_back(i1);
indices.push_back(i1);
indices.push_back(i2);
indices.push_back(i3);
}
}
return indices;
}
7.7.3 应用计算高度的函数
为地形和波浪添加高度变化:
cpp
// 计算高度函数
float GetHillHeight(float x, float z) {
return 0.3f * (z * sinf(0.1f * x) + x * cosf(0.1f * z));
}
// 应用高度函数到网格
void ApplyHeightFunction(MeshData& meshData, float (*heightFunc)(float, float)) {
// 应用高度函数到每个顶点
for (Vertex& v : meshData.Vertices) {
v.Position.y = heightFunc(v.Position.x, v.Position.z);
}
// 重新计算每个顶点的法线
uint32_t triCount = static_cast<uint32_t>(meshData.Indices32.size()) / 3;
for (uint32_t i = 0; i < triCount; ++i) {
// 获取三角形顶点索引
uint32_t i0 = meshData.Indices32[i * 3 + 0];
uint32_t i1 = meshData.Indices32[i * 3 + 1];
uint32_t i2 = meshData.Indices32[i * 3 + 2];
// 获取三角形顶点
Vertex& v0 = meshData.Vertices[i0];
Vertex& v1 = meshData.Vertices[i1];
Vertex& v2 = meshData.Vertices[i2];
// 计算两条边向量
XMVECTOR e0 = XMVectorSet(
v1.Position.x - v0.Position.x,
v1.Position.y - v0.Position.y,
v1.Position.z - v0.Position.z,
0.0f
);
XMVECTOR e1 = XMVectorSet(
v2.Position.x - v0.Position.x,
v2.Position.y - v0.Position.y,
v2.Position.z - v0.Position.z,
0.0f
);
// 计算叉积(法线方向)
XMVECTOR n = XMVector3Cross(e0, e1);
n = XMVector3Normalize(n);
// 将法线加到顶点,后面会取平均
XMVECTOR v0Normal = XMLoadFloat3(&v0.Normal) + n;
XMVECTOR v1Normal = XMLoadFloat3(&v1.Normal) + n;
XMVECTOR v2Normal = XMLoadFloat3(&v2.Normal) + n;
XMStoreFloat3(&v0.Normal, v0Normal);
XMStoreFloat3(&v1.Normal, v1Normal);
XMStoreFloat3(&v2.Normal, v2Normal);
}
// 归一化所有法线
for (Vertex& v : meshData.Vertices) {
XMVECTOR n = XMLoadFloat3(&v.Normal);
n = XMVector3Normalize(n);
XMStoreFloat3(&v.Normal, n);
// 重新计算切线(假设垂直于法线且在XZ平面上)
XMVECTOR normal = XMLoadFloat3(&v.Normal);
XMVECTOR tangent = XMVector3Cross(normal, XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f));
// 防止法线与上向量平行的情况
if (XMVector3Equal(XMVector3Length(tangent), XMVectorZero())) {
tangent = XMVector3Cross(normal, XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f));
}
tangent = XMVector3Normalize(tangent);
XMStoreFloat3(&v.TangentU, tangent);
}
}
7.7.4 根常量缓冲区视图
为波浪动画创建时间相关的常量:
cpp
// 波浪常量结构
struct WaveConstants {
float Time;
float WaveHeight;
float WaveFrequency;
float WaveSpeed;
};
// 设置根常量视图
void SetWaveRootConstants(ID3D12GraphicsCommandList* cmdList,
float time, float height, float frequency, float speed) {
// 打包波浪参数
WaveConstants waveConstants;
waveConstants.Time = time;
waveConstants.WaveHeight = height;
waveConstants.WaveFrequency = frequency;
waveConstants.WaveSpeed = speed;
// 设置为根常量
cmdList->SetGraphicsRoot32BitConstants(
0, // 根参数索引
4, // 32位值的数量
&waveConstants, // 数据指针
0 // 32位值的偏移
);
}
7.7.5 动态顶点缓冲区
实现动态更新波浪顶点的技术:
cpp
// 动态更新波浪网格
void UpdateWaveMesh(ID3D12GraphicsCommandList* cmdList,
ID3D12Resource* vertexBuffer,
const MeshData& baseMesh,
float time) {
// 波浪参数
const float waveHeight = 0.3f;
const float waveFrequency = 2.0f;
const float waveSpeed = 1.5f;
// 映射顶点缓冲区
Vertex* mappedVertices = nullptr;
CD3DX12_RANGE readRange(0, 0);
vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&mappedVertices));
// 修改顶点位置以模拟波浪
for (size_t i = 0; i < baseMesh.Vertices.size(); ++i) {
const Vertex& baseVertex = baseMesh.Vertices[i];
Vertex& vertex = mappedVertices[i];
// 复制基本属性
vertex = baseVertex;
// 应用波浪效果
float x = baseVertex.Position.x;
float z = baseVertex.Position.z;
// 计算多个波叠加
float height = 0.0f;
// 第一个波
float phase1 = waveFrequency * x + waveSpeed * time;
height += waveHeight * sinf(phase1);
// 第二个波(不同方向)
float phase2 = waveFrequency * z + waveSpeed * time * 0.8f;
height += waveHeight * 0.5f * sinf(phase2);
// 第三个波(对角线方向)
float phase3 = waveFrequency * (x + z) * 0.7f + waveSpeed * time * 1.2f;
height += waveHeight * 0.25f * sinf(phase3);
// 设置新高度
vertex.Position.y = height;
// 简化的法线计算(实际应用中应该计算精确法线)
float dx1 = waveHeight * waveFrequency * cosf(phase1);
float dz2 = waveHeight * 0.5f * waveFrequency * cosf(phase2);
float dxz3 = waveHeight * 0.25f * waveFrequency * 0.7f * cosf(phase3);
// 根据高度梯度计算新法线
XMFLOAT3 normal(
-(dx1 + dxz3),
1.0f,
-(dz2 + dxz3)
);
XMVECTOR n = XMVector3Normalize(XMLoadFloat3(&normal));
XMStoreFloat3(&vertex.Normal, n);
}
// 解除映射
vertexBuffer->Unmap(0, nullptr);
}
7.8 小结
本章深入探讨了利用 Direct3D 绘制几何体的高级技术。从简单的几何体生成到复杂的渲染场景,每个步骤都是构建 3D 图形应用的重要部分。
关键要点总结:
纹理资源是为几何体添加表面细节和真实感的核心元素。DirectX 12 支持多种纹理类型和格式,每种适用于不同的渲染需求。
渲染项抽象使管理和组织场景中的可绘制对象变得简单,便于实现批处理和优化。
常量数据通过常量缓冲区传递给着色器,包括对象变换、材质属性和光照信息。
几何体生成算法可用于创建各种基础形状,如立方体、球体、柱体等,这些是构建复杂场景的基础。
根签名是 Direct3D 12 的核心概念,定义了着色器程序可以访问的资源。理解根签名对于高效使用 Direct3D 12 至关重要。
陆地与波浪演示展示了如何结合这些技术创建动态、互动的 3D 场景,包括动态顶点修改和shader实时计算。
通过掌握这些概念和技术,开发者可以创建丰富、高效的 3D 图形应用程序,无论是游戏、可视化工具还是模拟系统。
7.9 练习
修改海洋波浪程序,实现基于用户输入调整波浪参数(高度、频率、速度)的功能。
创建一个程序,在单个场景中组合显示本章介绍的所有基本几何体(球体、柱体、立方体等),并让用户通过键盘切换不同的材质和渲染模式。
实现一个地形编辑器,允许用户通过鼠标交互修改地形高度,并实时更新法线和光照效果。
扩展几何体生成器,添加新的几何体类型,如环面(甜甜圈)、锥体或复杂的数学曲面(如Klein瓶)。
创建一个粒子系统,使用实例化渲染显示大量动态粒子,可以模拟雨水、雪花或火焰效果。
实现一个程序,展示不同顶点结构和输入布局的效果,包括位置、法线、颜色、纹理坐标等不同组合。
扩展根签名示例,创建一个支持多种渲染技术(如前向渲染、延迟渲染)的统一渲染框架。
为波浪程序添加物理交互,例如投入物体后产生的波纹,或模拟船只在水面上航行时的波纹。
这些练习将帮助你巩固和扩展本章学到的知识,提高使用 Direct3D 绘制和处理 3D 几何体的技能。














暂无评论内容