【DirectX12笔记】 第10章 混合
2019-08-20 本文已影响0人
crossous
前言
这一章讲的是混合,一般是用作半透明物体的渲染,不过一些特殊的写法也能做出其他效果。
原理
混合
混合方程如P360所述: 其中C代表颜色,F代表因子,圈乘符号、箱加符号以及因子都可以通过程序设置。
我们常用的是透明混合(P367),物体颜色乘自身α值,加上目标原颜色乘1-α。
alpha测试
除了半透明物体外,还存在全透明的物体,例如370页的金属栏箱,这种地方可以用上混合,毕竟alpha值为0,但效率存在问题。
渲染的各种测试的顺序为:剪裁、alpha测试、模版测试、深度测试,如果是全透明的像素片段,可以在alpha测试阶段直接丢弃,而如果用混合,还需要经过模版测试和深度测试。
因此如370页最上面代码所示,用clip检测alpha值,如果小于0.1就直接丢弃片段。
雾
P371页给出了雾的好处以及生成雾的原理。
程序分析
我们看看程序中有明显区别的地方。
首先是布局设置函数:
void BlendApp::BuildShadersAndInputLayout()
{
const D3D_SHADER_MACRO defines[] =
{
"FOG", "1",
NULL, NULL
};
const D3D_SHADER_MACRO alphaTestDefines[] =
{
"FOG", "1",
"ALPHA_TEST", "1",
NULL, NULL
};
mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", nullptr, "VS", "vs_5_0");
mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", defines, "PS", "ps_5_0");
mShaders["alphaTestedPS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", alphaTestDefines, "PS", "ps_5_0");
mInputLayout =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
}
这次我们有两个像素着色器了,区别在于传入的define有区别,去看看pixel shader:
#ifdef ALPHA_TEST
clip(diffuseAlbedo.a - 0.1f);
#endif
//...
#ifdef FOG
float fogAmount = saturate((distToEye - gFogStart) / gFogRange);
litColor = lerp(litColor, gFogColor, fogAmount);
#endif
操控了是否进行alpha测试。
然后看看PSO的创建(P364):
void BlendApp::BuildPSOs()
{
//不透明物体的PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;
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.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;
opaquePsoDesc.NumRenderTargets = 1;
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"])));
//半透明物体的PSO(用于渲染水体)
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc;
D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendDesc.BlendEnable = true;//使用老式混合
transparencyBlendDesc.LogicOpEnable = false;//和上面只能有一个为true
transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;//源混凝因子为α
transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;//目标混凝因子为1-α
transparencyBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;//混合操作为+
transparencyBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE;//α值的混合方法
transparencyBlendDesc.DestBlendAlpha = D3D12_BLEND_ZERO;
transparencyBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;//不用新式方法
transparencyBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;//向渲染目标渲染前先&mask
transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&transparentPsoDesc, IID_PPV_ARGS(&mPSOs["transparent"])));
//带有完全透明物体的PSO(渲染框箱)
D3D12_GRAPHICS_PIPELINE_STATE_DESC alphaTestedPsoDesc = opaquePsoDesc;
alphaTestedPsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders["alphaTestedPS"]->GetBufferPointer()),
mShaders["alphaTestedPS"]->GetBufferSize()
};
alphaTestedPsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&alphaTestedPsoDesc, IID_PPV_ARGS(&mPSOs["alphaTested"])));
}
接下来看看Draw函数:
void BlendApp::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));
// **雾的颜色只会对物体生效,因此要把底色(无限远)设为雾的颜色
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), (float*)&mMainPassCB.FogColor, 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[] = { mSrvDescriptorHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
auto passCB = mCurrFrameResource->PassCB->Resource();
mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());
// 绘制不透明物体
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);
// 绘制alpha测试物体,切换PSO
mCommandList->SetPipelineState(mPSOs["alphaTested"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::AlphaTested]);
// 绘制半透明物体,切换PSO
mCommandList->SetPipelineState(mPSOs["transparent"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Transparent]);
//****************************复读部分******************************
//.........
}
程序结果
感觉一部分水体颜色不对劲,是因为那部分在山里面,可以发现,为了让线框箱子背面能看到,我们在渲染箱子的PSO中禁用了背面剔除,而山没有,因此背对着我们的山体部分被剔除了。
同时不要误会,那部分水体颜色浅不是因为没受雾影响,只是因为作为山体的底色不在而已,换个水平的角度会更容易看清。