前言
第五章讲述的是渲染管线的概念,没有代码,因此不做笔记,第六章则是应用流水线,绘制最基础的几何体。
和前文一样,先粗略在书中看一遍,然后直接分析源代码。
代码分析
除了上一章节说明的文件:
- 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;
}
运行结果
总结
根据上面的调用规则来看,用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
网友评论