到目前为止,我们编写了顶点输入布局,传输了顶点数据存入GPU显存,还有RTV,DSV以及CBV等等,本篇之后就大概知道了绘制一个立方体的全过程;
一、编译着色器
对于shader的编译,D3D12给我们提供了一个函数:D3DCompileFromFile
使用的时候源码进行了一次封装:
ComPtr<ID3DBlob> d3dUtil::CompileShader(
const std::wstring& filename,//shader文件名
const D3D_SHADER_MACRO* defines,//编译所需的宏,在shader中可使用#ifdef等来识别
const std::string& entrypoint,//着色器函数名
const std::string& target)//编译平台
{
//若处于调试模式,则使用调式模式
UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
HRESULT hr = S_OK;
ComPtr<ID3DBlob> byteCode = nullptr;//编译shader产生的字节码,也是我们编译的目标
ComPtr<ID3DBlob> errors;//使用一个errors来传递编译出错的信息
hr = D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, &errors);//使用D3D提供的函数
if(errors != nullptr)
OutputDebugStringA((char*)errors->GetBufferPointer());
ThrowIfFailed(hr);
return byteCode;
}
ID3DBlob是一个没有类型的类型,提供两种方法:
GetBufferPointer()
GetBufferSize()
编译完之后要把字节码存储下来:
std::unordered_map<std::string, ComPtr<ID3DBlob>> mShaders;
mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_1");
mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_1");
二、光栅化状态
定义渲染绘制到像素化的配置
typedef struct D3D12_RASTERIZER_DESC
{
D3D12_FILL_MODE FillMode;//D3D12_FILL_MODE_WIREFRAME线框模式/默认_SOLID
D3D12_CULL_MODE CullMode;//默认背面剔除,D3D12_CULL_MODE_NONE/_FRONT/BACK
BOOL FrontCounterClockwise;//从使用时针转动方向为正方向(true,false)——顺时针
INT DepthBias;
FLOAT DepthBiasClamp;
FLOAT SlopeScaledDepthBias;
BOOL DepthClipEnable;
BOOL MultisampleEnable;
BOOL AntialiasedLineEnable;
UINT ForcedSampleCount;
D3D12_CONSERVATIVE_RASTERIZATION_MODE ConservativeRaster;
} D3D12_RASTERIZER_DESC;
使用的时候往往先按照默认值,再提供特殊的定义:
opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
opaquePsoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;
opaquePsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
三、流水线对象PSO
前边很多工作都是在为流水线做准备,比如创建的RTV,DSV实际就是流水线的输出,而顶点,索引,以及CBV等为流水线提供输入,还有shader等等就是在为流水线准备工具。
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;//空的PSO结构体
// 为结构体填空
ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
opaquePsoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
opaquePsoDesc.pRootSignature = mRootSignature.Get();
opaquePsoDesc.VS =
{
reinterpret_cast<BYTE*>(mShaders["standardVS"]->GetBufferPointer()),
mShaders["standardVS"]->GetBufferSize()
};
opaquePsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders["opaquePS"]->GetBufferPointer()),
mShaders["opaquePS"]->GetBufferSize()
};
opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
opaquePsoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;
opaquePsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);//透明混合
opaquePsoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);//深度模板
opaquePsoDesc.SampleMask = UINT_MAX;//采样掩码(特殊采样需求,个别点避免采样)
opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;//虽然我们在传输顶点数据使用IA函数设置过,但是这里还是有图元拓扑的概念,个人感觉检测是否匹配的可能性比较大
opaquePsoDesc.NumRenderTargets = 1;//这两个是匹配的,就是下方格式数组的size
opaquePsoDesc.RTVFormats[0] = mBackBufferFormat;
opaquePsoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
opaquePsoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
opaquePsoDesc.DSVFormat = mDepthStencilFormat;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&opaquePsoDesc, IID_PPV_ARGS(&mPSOs["opaque"])));
之前通过填写结构体+创建这种标准模式建立的,都是渲染流水线的子单元,子结构,现在统一配置给DX12的PSO,相当于设置了一条流水线的所有开关的开启方式,那么开启这台机器的最后两步:
1.ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque"].Get()));
重设命令分配器,并关联PSO,除非后边改变,不然CommandListAlloc一直使用这个PSO
2.mCommandList->SetPipelineState(mPSOs["opaque"].Get());
直接改变PSO,因为可能有多条流水线,比如先渲染不透明,后渲染透明等,两者在shader层面是不同的,所以切换流水线。
所谓的流水线就是一整套生产流程,它具有各个独立的功能组件,不同的设置就可能产生不同的生产输出结果。所以D3D12将这个称为状态机,设置不同的状态,在不同设置状态下就能运转起来,不妨看一下驱动他运转的全部代码:
//以下是整理的驱动绘图的一些关键代码,并非全部,要看全部去看看龙书的源码即可:
void Draw(const GameTimer& gt)
{
//重设命令分配器以添加新一帧的绘制驱动命令
auto cmdListAlloc = mCurrFrameResource->CmdListAlloc;
ThrowIfFailed(cmdListAlloc->Reset());
//设置命令分配器&渲染管线
ThrowIfFailed(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));
// 清理RTV&DSV.
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
// 使RTV和DSV资源成为管线的渲染目标和深度/模板缓冲区.
//实际上这里三个参数:第一个是渲染目标的数量,第二个RTV首地址,第三个DSV首地址,支持向多个目标渲染
mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());
//常量缓冲区与根签名的关联设置
ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
int passCbvIndex = mPassCbvOffset + mCurrFrameResourceIndex;
auto passCbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
passCbvHandle.Offset(passCbvIndex, mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(1, passCbvHandle);
//DrawCall,push流水线绘图,内部使用DrawIndexedInstanced
DrawRenderItems(mCommandList.Get(), mOpaqueRitems);
{
.......
cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);
cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);
......
cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
}
//执行完第一次绘制之后,我们可以转换PSO进行第二轮的绘制,甚至次数我们可以使用不同的RTV,DSV,CBV来实现一些分屏的效果
// 结束写入,转化为显示,与上边呼应.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
// 结束命令添加.
ThrowIfFailed(mCommandList->Close());
// 将命令推送到GPU命令队列执行.
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// 交换双缓冲链,让渲染好的结果呈现在屏幕
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;
// CPU端更新围栏值.
mCurrFrameResource->Fence = ++mCurrentFence;
// 直接在GPU命令队列的最后设置更新后的围栏值,执行同步.
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}
另外:贴一段来解释输出合并阶段:
ID3D11DeviceContext::OMSetRenderTargets()
* 绑定一个或更多渲染目标和深度模板缓冲区到管线的输出合并器阶段
* @param NumViews 将要绑定的渲染目标的数量
* @param ppRenderTargetViews 将要绑定的渲染目标视图数组中的第一个元素的指针
* @param pDepthStencilView 将要绑定到管线的深度/模板视图
————————————————
不错这是我转载的解释,这里是原文,从这里的理解几乎可以确定我们是支持多个RTV和DSV渲染的
原文链接:https://blog.csdn.net/killian0213/article/details/106458489