其中 C C C是最终得到的颜色, C s r c C_{src} Csrc是新来到的fragment的颜色, C d s t C_{dst} Cdst是back buffer里已有的像素的颜色, F d s t F_{dst} Fdst和 F s r c F_{src} Fsrc是两个系数,两个乘是component wise的乘法,中间的运算符号可以由我们规定。
Alpha通道也满足下式
Alpha和RGB的Operation和Factor都可以分别各自选取。
以下是支持的一些Operation
此外还有逻辑运算的Operation,但是用逻辑运算就不能用以上的这些运算符号了,在设置blend state的时候要设置一下LogicOpEnable,具体有哪些逻辑算符不再罗列。
然后以下是支持的一些Factor
D3D12_BLEND_ZERO: F = (0, 0, 0) and F = 0
D3D12_BLEND_ONE: F = (1, 1, 1) and F = 1
D3D12_BLEND_SRC_COLOR: F = (rs, gs, bs)
D3D12_BLEND_INV_SRC_COLOR: Fsrc = (1 − rs, 1 − gs, 1 − bs)
D3D12_BLEND_SRC_ALPHA: F = (as, as, as) and F = as
D3D12_BLEND_INV_SRC_ALPHA: F = (1 − as, 1 − as, 1 − as) and F = (1 − as)
D3D12_BLEND_DEST_ALPHA: F = (ad, ad, ad) and F = ad
D3D12_BLEND_INV_DEST_ALPHA: F = (1 − ad, 1 − ad, 1 − ad) and F = (1 − ad)
D3D12_BLEND_DEST_COLOR: F = (rd, gd, bd)
D3D12_BLEND_INV_DEST_COLOR: F = (1 − rd, 1 − gd, 1 − bd)
D3D12_BLEND_SRC_ALPHA_SAT: F = (a′s, a′s, a′s) and F = a′s where a′s = clamp(as, 0, 1)
D3D12_BLEND_BLEND_FACTOR: F = (r, g, b) and F = a, where the color (r, g, b, a) is supplied to the second parameter of the ID3D12GraphicsCommandList::OMSetBlendFactor method. This allows you to specify the blend factor color to use directly; however, it is constant until you change the blend state.
D3D12_BLEND_INV_BLEND_FACTOR: F = (1 − r, 1 − g, 1 − b) and F =1 − a, where the color (r, g, b, a) is supplied by the second parameter of the ID3D12GraphicsCommandList::OMSetBlendFactor method. This allows you to specify the blend factor color to use directly; however, it is constant until you change the blend state.
然后是Blend State,Blend State在创建PSO的时候设置,需要指定以下Desc
typedef struct D3D12_BLEND_DESC {
BOOL AlphaToCoverageEnable; // Default: False
BOOL IndependentBlendEnable; // Default: False
D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;
其中AlphaToCoverageEnable是制定一种对植被等渲染的时候有用的一种多重采样技术,默认是关的。
第二个参数IndependentBlendEnable是允许各个RenderTarget使用不同的混合方式(最多八个),如果关闭,则八个RenderTarget都用数组里的0号元素的混合方式。
第三个参数是混合方式数组,最多8个,注意这里虽然名字叫RenderTarget但是类型其实是D3D11_RENDER_TARGET_BLEND_DESC,接下来介绍这个DESC。
typedef struct D3D12_RENDER_TARGET_BLEND_DESC
{
BOOL BlendEnable; // Default: False
BOOL LogicOpEnable; // Default: False
D3D12_BLEND SrcBlend; // Default: D3D12_BLEND_ONE
D3D12_BLEND DestBlend; // Default: D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOp; // Default:
D3D12_BLEND_OP_ADD
D3D12_BLEND SrcBlendAlpha; // Default:
D3D12_BLEND_ONE
D3D12_BLEND DestBlendAlpha; // Default:
D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOpAlpha; // Default:
D3D12_BLEND_OP_ADD
D3D12_LOGIC_OP LogicOp; // Default:D3D12_LOGIC_OP_NOOP
UINT8 RenderTargetWriteMask; // Default:D3D12_COLOR_WRITE_ENABLE_ALL
} D3D12_RENDER_TARGET_BLEND_DESC;
前两个参数最多只能有一个为true,表示使用混合,两个都为false则关闭混合,第一个是普通的混合,第二个是前面提到的使用逻辑运算的混合,然后接下来几个参数是F和Op的设置,具体有哪些取值前面已经提到,然后接下来是Alpha的F和Op,然后最后一个参数可以注意下,这个是写入的Mask,取值如下
typedef enum D3D12_COLOR_WRITE_ENABLE {
D3D12_COLOR_WRITE_ENABLE_RED = 1,
D3D12_COLOR_WRITE_ENABLE_GREEN = 2,
D3D12_COLOR_WRITE_ENABLE_BLUE = 4,
D3D12_COLOR_WRITE_ENABLE_ALPHA = 8,
D3D12_COLOR_WRITE_ENABLE_ALL =
( D3D12_COLOR_WRITE_ENABLE_RED |D3D12_COLOR_WRITE_ENABLE_GREEN |
D3D12_COLOR_WRITE_ENABLE_BLUE |D3D12_COLOR_WRITE_ENABLE_ALPHA )
} D3D12_COLOR_WRITE_ENABLE;
比如有的时候只想写入alpha,不想写入rgb,那就可以通过设置这个mask参数来达到效果。或者有的时候只想写入深度缓冲,那么就把这个mask设置成0即可。
一个设置blend state的示例如下
// Start from non-blended PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc;
D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendDesc.BlendEnable = true;
transparencyBlendDesc.LogicOpEnable = false;
transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
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;
transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState( &transparentPsoDesc, IID_PPV_ARGS(&mPSOs[“transparent”])));
在shader里可以用clip(x)来裁剪像素,如果x<0,那么shader会直接舍弃掉这个像素然后返回,之后的代码也不会运行。这个函数只能在像素着色器里被调用,例如:
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
#ifdef ALPHA_TEST
// Discard pixel if texture alpha < 0.1. We do this test as soon
// as possible in the shader so that we can potentially exit the
// shader early, thereby skipping the rest of the shader code.
clip(diffuseAlbedo.a - 0.1f);
#endif
因为采样的时候会有filtering所以贴图会稍微模糊一点,alpha通道也会是一个插值的结果,所以想要滤掉alpha为0的像素,最好给一个小点的判断范围,比如这里写的是alpha小于0.1就裁掉。
此外,这里需要定义ALPHA_TEST才执行这个裁剪步骤,我们可以单独给一个PSO用来渲染有alpha测试的物体,这个PSO中我们需要定义一个定义了ALPHA_TEST的shader版本,并且将关掉back culling(因为有透明所以能够看到物体内部了,因此大多数情况下我们希望能看到背面),C++中可以这样写:
const D3D_SHADER_MACRO alphaTestDefines[] =
{
"FOG", "1",
"ALPHA_TEST", "1",
NULL, NULL
};
mShaders["alphaTestedPS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", alphaTestDefines, "PS", "ps_5_0");
...
//
// PSO for alpha tested objects
//
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"])));
一个简单的雾气效果可以这样写
其中s是雾的浓度,从start位置开始直到start+range位置s从0线性增长到1
写进shader里,代码如下:
// Constant data that varies per pass.
cbuffer cbPass : register(b1)
{
···
// Allow application to change fog parameters once per frame.
// For example, we may only use fog for certain times of day.
float4 gFogColor;
float gFogStart;
float gFogRange;
float2 cbPerObjectPad2;
···
};
float4 PS(VertexOut pin) : SV_Target
{
···
#ifdef FOG
float fogAmount = saturate((distToEye - gFogStart) / gFogRange);
litColor = lerp(litColor, gFogColor, fogAmount);
#endif
···
}
然后和刚才的alpha test一样在读取shader的时候把fog定义上即可
修改之前的demo并加入透明效果和雾气,要做这么几件事情:
读shader的时候,分成做ALPHA_TEST的和不做ALPHA_TEST的(虽然这个demo里全部都定义的话效果也一样,但是真正做大点的项目的时候还是要分的)
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 },
};
}
定义一个render layer和render item layer,然后build render item的时候没定义一个render item都把物体放到对应的layer里去
enum class RenderLayer : int
{
Opaque = 0,
Transparent,
AlphaTested,
Count
};
···
// Render items divided by PSO.
std::vector<RenderItem*> mRitemLayer[(int)RenderLayer::Count];
···
mRitemLayer[(int)RenderLayer::Transparent].push_back(wavesRitem.get());
然后对opaque、alpha tested和transparent分别定义三个不同的PSO
void BlendApp::BuildPSOs()
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;
//
// PSO for opaque objects.
//
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 for transparent objects
//
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc;
D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendDesc.BlendEnable = true;
transparencyBlendDesc.LogicOpEnable = false;
transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
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;
transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&transparentPsoDesc, IID_PPV_ARGS(&mPSOs["transparent"])));
//
// PSO for alpha tested objects
//
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"])));
}
最后在渲染的时候,按顺序分别渲染opaque、alpha tested和transparent这三个层
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);
mCommandList->SetPipelineState(mPSOs["alphaTested"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::AlphaTested]);
mCommandList->SetPipelineState(mPSOs["transparent"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Transparent]);
注意这里我们没有对透明物体进行排序,因为只有一个水面是透明的,如果有很多个透明物体的话,按理说要从深到浅排序并依次渲染。但是,还有更复杂的情况,那就是同一个物体自我纠缠或者多个物体相互纠缠的情况,这样的话按物体排序也没用,解决办法有这么几种:
1.最简单的情况,这个物体只需要对背后的物体透明,不需要对自己透明,那么开深度写入,分两个pass,第一个pass只写入深度,第二个pass正常渲染即可。
2.alpha to coverage,基于MSAA,其实相当于把他当成非透明物体来渲染。只是最后输出的颜色会乘以alpha值。
3.OIT(Order-Independent Transparency),可能需要很多RenderTarget。可以看这个https://github.com/candycat1992/OIT_Lab
4.离线排序,把所有三角面排好序再渲染
最后得到的效果如图