【DirectX12笔记】第6章 绘制几何体
![](https://img.haomeiwen.com/i7875603/da7e7abb0ca59f8e.png)
前言
第五章讲述的是渲染管线的概念,没有代码,因此不做笔记,第六章则是应用流水线,绘制最基础的几何体。
和前文一样,先粗略在书中看一遍,然后直接分析源代码。
代码分析
除了上一章节说明的文件:
- d3dApp.h/cpp
- d3dUtil.h/cpp
- GameTimer.h/cpp
本章新增加的文件名有: - MathHelper.h
- UploadBuffer.h
- BoxApp.cpp
先看程序入口,在BoxApp.cpp文件中,WinMain方法基本没有变化,而D3DApp的新导出类BoxApp是我们的目标。
Initialize
初始化方法
if(!D3DApp::Initialize())
return false;
// 开启命令列表,指定分配器,无渲染管道。
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
BuildDescriptorHeaps();
BuildConstantBuffers();
BuildRootSignature();
BuildShadersAndInputLayout();
BuildBoxGeometry();
BuildPSO();
// 提交命令
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// 等待GPU执行完毕
FlushCommandQueue();
return true;
初始化过程中,分别构建了描述符堆、常量缓冲、根签名、着色器和输入层、几何体、流水线状态对象,下面分别看这六个方法体。
BuildDescriptorHeaps
ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;
void BoxApp::BuildDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = 1;//缓冲个数
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;//类型
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;//着色器可访问标志
cbvHeapDesc.NodeMask = 0;//单显卡设置为0
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
IID_PPV_ARGS(&mCbvHeap)));
}
上一章我们创建了rtvHeap(渲染目标视图堆)和dsvHeap(深度模版视图堆),这个方法要创建的是cbv(常量缓冲视图堆),具体说明在201页。
注:在看第二个方法之前,先说明一下关于上传堆的概念。
上传缓冲区
首次见是179页,用于将数据上传到顶点、索引缓冲区,而常量缓冲区则需要持久的上传缓冲区。
为什么常量缓冲区要持久的上传缓冲区?原因在196页:与顶点缓冲区和索引缓冲区不同,常量缓冲区需要CPU每帧更新一次。
创建上传缓冲区,以及更新常量缓冲区需要一系列操作,书中将其功能封装到UploadBuffer中,详见199页:
template<typename T>
class UploadBuffer
{
public:
UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) : //设备、元素数、是否用来管理常量缓冲区
mIsConstantBuffer(isConstantBuffer)
{
mElementByteSize = sizeof(T);//常量类型大小(单位字节)
//如果要管理常量缓冲区,需要大小256对齐,见196页
if(isConstantBuffer)
mElementByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(T));
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),//堆类型
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize*elementCount),//快速构建描述符,见178页
D3D12_RESOURCE_STATE_GENERIC_READ,//初始状态读
nullptr,//清空值
IID_PPV_ARGS(&mUploadBuffer)));
//子资源索引,设置为0,表示自身
//映射范围,nullptr表全部
//返回带映射资源内存块,双重指针,将分配的虚拟内存地址交给mMappedData,详情见198页注释
ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));
// 只要有修改资源的可能,就不需要取消映射
// 但资源被使用期间,千万不能对资源进行写操作,需要借助上一章的同步技术
}
UploadBuffer(const UploadBuffer& rhs) = delete;
UploadBuffer& operator=(const UploadBuffer& rhs) = delete;
~UploadBuffer()
{
if(mUploadBuffer != nullptr)
mUploadBuffer->Unmap(0, nullptr);//取消映射, 子资源索引、范围
mMappedData = nullptr;
}
//得到资源
ID3D12Resource* Resource()const
{
return mUploadBuffer.Get();
}
//将数据data复制到第elementIndex个位置上。
void CopyData(int elementIndex, const T& data)
{
memcpy(&mMappedData[elementIndex*mElementByteSize], &data, sizeof(T));
}
private:
Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
BYTE* mMappedData = nullptr;
UINT mElementByteSize = 0;
bool mIsConstantBuffer = false;
};
BuildConstantBuffers
首先拟定常量缓冲区的类型(Opengl中的Uniform就是常量缓冲区,以C++能搞出一堆眼花缭乱的写法,这样应该是更规范的写法)
struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};
看shader里面的定义,是直接将MVP矩阵传入。
//P202
void BoxApp::BuildConstantBuffers()
{
mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);//上传堆小助手,传入设备,只有一个元素,是常量缓冲区
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
//缓冲区起始地址
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
// 要偏移的地址
int boxCBufIndex = 0;
cbAddress += boxCBufIndex*objCBByteSize;
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
// 创建常量缓冲视图
md3dDevice->CreateConstantBufferView(
&cbvDesc,
mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}
BuildRootSignature
创建根签名,于202页讲述,225页实例
void BoxApp::BuildRootSignature()
{
// 着色器程序一般需要以资源做输入 (常量缓冲区、纹理、采样器)
// 根签名定义了着色器程序所需的具体资源
// 如光将着色器程序看做一个入口函数,将资源看做传递给函数的参数
// 那么可以将根签名看做函数签名(形参)
// 根参数可以是描述符表、根描述符或根常量,近期先只用根描述表
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
// 创建一个只含有一个CBV的描述符表
// 一个辅助结构,可以轻松初始化D3D12_DESCRIPTOR_RANGE1结构。
CD3DX12_DESCRIPTOR_RANGE cbvTable;
// 区域类型
// 描述符数量
// 将描述符区域绑定至此基准着色器寄存器
cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
// 描述符区域数量
// 指向描述符区域数组的指针
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable);
// 根签名由一组根参数组成
// 参数数量
// 根参数数组
// 静态采样器数量
// 静态采样器描述符指针
// 根签名标志
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr,
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.GetAddressOf(), errorBlob.GetAddressOf());
if(errorBlob != nullptr)//如果发生错误
{
::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
ThrowIfFailed(hr);
ThrowIfFailed(md3dDevice->CreateRootSignature(//创建根签名
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(&mRootSignature)));
}
CompileShader
DirectX的着色器程序是hlsl(高级着色器语言),写完就需要编译,编译函数需要很多参数,因此作者在d3dUtil中封装了编译着色器函数:
ComPtr<ID3DBlob> d3dUtil::CompileShader(//返回编译好的二进制
const std::wstring& filename,//文件名
const D3D_SHADER_MACRO* defines,//本书不使用的高级选项,总是填nullptr
const std::string& entrypoint,//入口函数
const std::string& target)//着色器版本字符串
{
UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
//DEBUG模式编译,跳过优化阶段
compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
HRESULT hr = S_OK;
ComPtr<ID3DBlob> byteCode = nullptr;
ComPtr<ID3DBlob> errors;
// 源文件名称
// 本书不使用的高级选项,总是填nullptr
// 本书不使用的高级选项,总是填nullptr(书上这么介绍的,但源代码没这么填)
// 入口函数
// 着色器类型和版本字符串
// 编译标志
// 标志2,不会用到的处理效果文件高级编译选项
// 返回储存二进制内存的智能指针
// 错误码
hr = D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, &errors);
if(errors != nullptr)
OutputDebugStringA((char*)errors->GetBufferPointer());
ThrowIfFailed(hr);
return byteCode;
}
BuildShadersAndInputLayout
构建着色器和输入布局,编译着色器在206页,输入布局在176页
ComPtr<ID3DBlob> mvsByteCode = nullptr;
ComPtr<ID3DBlob> mpsByteCode = nullptr;
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;
void BoxApp::BuildShadersAndInputLayout()
{
HRESULT hr = S_OK;
mvsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_0");
mpsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_0");
// 语义(变量名)
// 附加语义索引,未标明索引默认为0
// 格式
// 输入槽(0~15)
// 偏移量
// 用于实例化的高级技术
// 目前设置为0,若要采用实例化技术则填1
mInputLayout =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
}
MeshGeometry
几何图形辅助结构体,见217页。可以让CPU访问结构体数据,记录缓冲区大小,返回缓冲区视图。
//子几何体,说明详见185页,实例见218页
struct SubmeshGeometry
{
UINT IndexCount = 0;//索引数量
UINT StartIndexLocation = 0;//索引首位置
INT BaseVertexLocation = 0;//顶点首位置
// 包围盒,以后会用到
DirectX::BoundingBox Bounds;
};
struct MeshGeometry
{
// 指定几何体名称,可以根据名称找到几何体
std::string Name;
// 系统内存中的副本。由于顶点/索引可以是泛型格式,所以用Blob类型表示
// 使用时再转换回适当类型
Microsoft::WRL::ComPtr<ID3DBlob> VertexBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> IndexBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
// 缓冲区相关数据
UINT VertexByteStride = 0;//读取一个顶点的偏移量
UINT VertexBufferByteSize = 0;//顶点缓冲区大小
DXGI_FORMAT IndexFormat = DXGI_FORMAT_R16_UINT;//索引类型
UINT IndexBufferByteSize = 0;//索引缓冲区大小
// 一个MeshGeometry在顶点/索引缓冲区中可存多个几何体
// 用下列容器定义子网格几何体,就可以单独绘制出子网格
std::unordered_map<std::string, SubmeshGeometry> DrawArgs;
// 返回顶点缓冲区视图
D3D12_VERTEX_BUFFER_VIEW VertexBufferView()const
{
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = VertexByteStride;
vbv.SizeInBytes = VertexBufferByteSize;
return vbv;
}
// 返回索引缓冲区视图
D3D12_INDEX_BUFFER_VIEW IndexBufferView()const
{
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = IndexFormat;
ibv.SizeInBytes = IndexBufferByteSize;
return ibv;
}
// 数据上传到缓冲区后,就可以释放这部分内存了。
void DisposeUploaders()
{
VertexBufferUploader = nullptr;
IndexBufferUploader = nullptr;
}
};
CreateDefaultBuffer
创建默认缓冲区的封装方法,说明在179页。
Microsoft::WRL::ComPtr<ID3D12Resource> d3dUtil::CreateDefaultBuffer(//返回创建的默认缓冲区
ID3D12Device* device,
ID3D12GraphicsCommandList* cmdList,
const void* initData,
UINT64 byteSize,
Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{
ComPtr<ID3D12Resource> defaultBuffer;
// 创建默认堆资源
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),//p178
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultBuffer.GetAddressOf())));
// 上传堆资源
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(uploadBuffer.GetAddressOf())));
// 复制数据的描述
D3D12_SUBRESOURCE_DATA subResourceData = {};
subResourceData.pData = initData;//初始化数据
subResourceData.RowPitch = byteSize;//复制的字节数
subResourceData.SlicePitch = subResourceData.RowPitch;//对缓冲区而言,和上一行一样
// 将默认buffer从初始状态变为复制目标状态
cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));
// 将上传缓冲区资源交给默认缓冲区
// 模版参数:最大子资源数
// 函数参数:
// 命令列表
// 目标资源
// 中间资源
// 中间资源的偏移量
// 资源中第一个子资源的索引,范围是[0, 模版参数)
// 资源中子资源的数量,范围是[1, 模版参数-第一个子资源索引)
// 子资源数据指针
UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);
cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));
// 注意,调用函数后,必须保证uploadBuffer依然存在,不能立即销毁, 因为命令列表中的赋值操作可能尚未执行
// 待调用者得知复制完成消息后,方可释放uploadBuffer
return defaultBuffer;
}
BuildBoxGeometry
构建几何体信息
//顶点
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;
void BoxApp::BuildBoxGeometry()
{
//顶点数组
std::array<Vertex, 8> vertices =
{
Vertex({ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Red) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Yellow) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Cyan) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Magenta) })
};
//索引数组
std::array<std::uint16_t, 36> indices =
{
// 前
0, 1, 2,
0, 2, 3,
// 后
4, 6, 5,
4, 7, 6,
// 左
4, 5, 1,
4, 1, 0,
// 右
3, 2, 6,
3, 6, 7,
// 上
1, 5, 6,
1, 6, 2,
// 下
4, 0, 3,
4, 3, 7
};
const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);//顶点缓冲大小
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);//索引缓冲大小
mBoxGeo = std::make_unique<MeshGeometry>();
mBoxGeo->Name = "boxGeo";
//申请内存,将数据复制到内存中
ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));
CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
//创建默认缓冲区并初始化数据
mBoxGeo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);
mBoxGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);
mBoxGeo->VertexByteStride = sizeof(Vertex);//顶点跨度
mBoxGeo->VertexBufferByteSize = vbByteSize;//顶点缓冲大小
mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;//索引类型
mBoxGeo->IndexBufferByteSize = ibByteSize;//索引缓冲大小
SubmeshGeometry submesh;//定义子网格体
submesh.IndexCount = (UINT)indices.size();//索引数量
submesh.StartIndexLocation = 0;//首索引地址
submesh.BaseVertexLocation = 0;//首顶点地址
mBoxGeo->DrawArgs["box"] = submesh;//将子网格交给mBoxGeo几何网格对象
}
BuildPSO
创建流水线对象,见214页。
void BoxApp::BuildPSO()
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;//流水线状态对象描述符
ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
psoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };//输入布局描述,成员在BuildShadersAndInputLayout中初始化
psoDesc.pRootSignature = mRootSignature.Get();//指向一个与次PSO相容的根签名指针,成员在BuildRootSignature中创立
//顶点与像素着色器程序,成员在BuildShadersAndInputLayout初始化
psoDesc.VS =
{
reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
mvsByteCode->GetBufferSize()
};
psoDesc.PS =
{
reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
mpsByteCode->GetBufferSize()
};
//光栅器状态,见P213
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
//混合操作所用的混合状态,后续讲解
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
//深度模版缓冲状态,后续讲解
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
//采样情况掩码,一般都设为0xffffffff
psoDesc.SampleMask = UINT_MAX;
//指定图元拓扑类型,见P215
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
//同时用的渲染目标个数
psoDesc.NumRenderTargets = 1;
//渲染目标格式数组,个数和上面一样
psoDesc.RTVFormats[0] = mBackBufferFormat;
//采样数量和质量级别
psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
//深度/模版缓冲级别
psoDesc.DSVFormat = mDepthStencilFormat;
//创建图形流水线状态机
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}
这样一来,初始化差不多就都明白了
OnResize
上一章节没有覆写这个方法,但是这一章节有相机的概念,重整窗体大小会改变平截锥体形状(改变透视矩阵),因此做如下覆写:
XMFLOAT4X4 mProj = MathHelper::Identity4x4();
void BoxApp::OnResize()
{
D3DApp::OnResize();
// Fov角度45°,长宽比,近平面和远平面
XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);
XMStoreFloat4x4(&mProj, P);
}
Update
每帧都需要改变的函数,我们希望每帧都检测相机的位置,来改变渲染出来的画面,见200页。
void BoxApp::Update(const GameTimer& gt)
{
// 从球面坐标转换为笛卡尔坐标
float x = mRadius*sinf(mPhi)*cosf(mTheta);
float z = mRadius*sinf(mPhi)*sinf(mTheta);
float y = mRadius*cosf(mPhi);
// 构建view矩阵
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, view);
XMMATRIX world = XMLoadFloat4x4(&mWorld);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX worldViewProj = world*view*proj;
// 更新WVP矩阵到常量缓冲区
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
mObjectCB->CopyData(0, objConstants);
}
Draw
渲染函数包括了上一章的代码,并且因为加了流水线,还有所扩充。
void BoxApp::Draw(const GameTimer& gt)
{
//************************重复部分************************
//重用命令分配器
ThrowIfFailed(mDirectCmdListAlloc->Reset());
// 重用命令列表内存
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.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() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
//设置根签名
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
//设置顶点、索引缓冲,图元拓扑方法
mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
//设置根参数
mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());
//Draw Call, P185
//索引数量
//实例化技术,只绘制一个实例,设置为1
//开始索引地址
//开始顶点地址
//实例化技术选项,暂时设置为0
mCommandList->DrawIndexedInstanced(
mBoxGeo->DrawArgs["box"].IndexCount,
1, 0, 0, 0);
//************************重复部分************************
// 渲染完毕后,将资源从渲染状态转换为呈现状态
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
// 关闭命令列表并提交
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// 交换缓冲区
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;
// 同步
FlushCommandQueue();
}
鼠标事件
对于这个程序,我们需要按住左键拖动将进行注视旋转,右键拖动会改变与目标点距离。
先看简单的:
void BoxApp::OnMouseDown(WPARAM btnState, int x, int y)
{
//鼠标右键按下,将不断记录上一时刻坐标
mLastMousePos.x = x;
mLastMousePos.y = y;
SetCapture(mhMainWnd);
}
void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{
ReleaseCapture();
}
SetCapture和ReleaseCapture自己注释掉试一试就明白了,当鼠标划出窗口后,设置SetCapture会让事件继续生效。
然后是有关相机的参数改变:
void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
if((btnState & MK_LBUTTON) != 0)//如果左键被按下
{
// 球面坐标改变量
float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));
// 更新球面坐标
mTheta += dx;
mPhi += dy;
// 将phi角限制在0~π
mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
}
else if((btnState & MK_RBUTTON) != 0)//如果是右键
{
// 该变量
float dx = 0.005f*static_cast<float>(x - mLastMousePos.x);
float dy = 0.005f*static_cast<float>(y - mLastMousePos.y);
// 距离
mRadius += dx - dy;
// 远近限制
mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
}
mLastMousePos.x = x;
mLastMousePos.y = y;
}
运行结果
![](https://img.haomeiwen.com/i7875603/dbbe637870b15f16.png)
总结
根据上面的调用规则来看,用Shader绘制图形经历了如下的步骤:
1.构建常量缓冲区的描述符堆
2.创建常量缓冲
2.1构建上传缓冲
2.2填写常量缓冲区视图的描述符
2.3创建常量缓冲区视图
3.创建根签名
3.1创建根参数数组
3.2创建一个根参数(描述符表、描述符、常量)实例并初始化
3.3将根参数实例填入根参数
3.4用 根参数 填写 根签名描述符
3.5序列化 根签名描述符
3.6创建根签名
4.构建着色器和输入布局
5.构建几何体信息
5.1拿到顶点数据和索引数据
5.2CPU中备份数据
5.3创建顶点和索引缓冲并上传数据
6.创建流水线状态对象
6.1填写PSO
7.渲染
7.1设置常量缓冲区描述符堆
7.2设置根签名
7.3设置顶点、索引缓冲,图元拓扑方法
7.4设置根参数
7.5Draw Call