DX12 实战 blend
前言
本篇将展示如何使用DX12实现blend
源代码D3D12 at blend
要点
over & under算子
blend的实现是基于over算子和under算子
over
- 含义:应对半透明物体在不透明物体前或半透明物体和半透明物体间的情况,它的顺序是从后向前——从源像素(PS中输出的片元)到目标像素(缓冲区内的片元)逐层blend
- 公式:\(\large c_0 = \alpha_s c_s + (1 - \alpha_s)c_d\)。其中,\(\large \alpha_s\)为源物体的alpha值,\(\large c_s\)为源物体的颜色值,\(\large c_d\)为目标物体的颜色值,\(\large c_0\)为目标输出值
under
- 含义:under操作和over操作类似,但顺序相反,它是从前往后
- 公式:\(\large c_0 = \alpha_d c_d + (1 - \alpha_d)\alpha_s cs \\ a_0 = \alpha_s(1-\alpha_d) + \alpha_d\)
区别
- over算子无需计算blend后(目标物体)的透明度,因为over算子需要的是源物体的透明度;但under算子则需要源物体和目标物体的透明度
- over算子不能对两个半透明物体进行叠加;而under可以
混合方程
- 方程表达式:\(\large C = C_{src} \bigotimes F_{src} \boxplus C_{dst} \bigotimes F_{dst}\)。其中,\(\bigotimes\)意为分量式乘法,\(\boxplus\)是D3D12_BLEND_OP定义的二元运算符 ,F为混合因子
- alpha混合方程和颜色混合方程类似,只是将alpha分量替换颜色方程中的C。公式:\(\large A = A_{src} F_{src} \boxplus A_{dst} F_{dst}\)
二元运算符
该二元运算符D3D12_BLEND_OP (d3d12.h)基于上述混合方程
typedef enum D3D12_BLEND_OP
{
D3D12_BLEND_OP_ADD = 1, // 源物体 + 目标物体
D3D12_BLEND_OP_SUBTRACT = 2, // 目标物体 - 源物体
D3D12_BLEND_OP_REV_SUBTRACT = 3, // 源物体 - 目标物体
D3D12_BLEND_OP_MIN = 4, // min(源物体,目标物体)
D3D12_BLEND_OP_MAX = 5 // max(源物体,目标物体)
} ;
D3D也支持逻辑运算符D3D12_LOGIC_OP (d3d12.h)来代替二元运算符
混合因子
混合因子是混合方程的F
D3D12_BLEND (d3d12.h)
// s:data from PS output
// d:data from buffer data(pre)
typedef enum D3D12_BLEND
{
D3D12_BLEND_ZERO = 1, // F(0,0,0,0)
D3D12_BLEND_ONE = 2, // F(1,1,1,1)
D3D12_BLEND_SRC_COLOR = 3, // F(R_s, G_s, B_s, A_s)
D3D12_BLEND_INV_SRC_COLOR = 4, // F(1 - R_s, 1 - G_s, 1 - B_s, 1 - A_s)
D3D12_BLEND_SRC_ALPHA = 5, // F(A_s, A_s, A_s, A_s)
D3D12_BLEND_INV_SRC_ALPHA = 6, // F(1 - A_s, 1 - A_s, 1 - A_s, 1 - A_s)
D3D12_BLEND_DEST_ALPHA = 7, // F(A_d, A_d, A_d, A_d)
D3D12_BLEND_INV_DEST_ALPHA = 8, // F(1 - A_d, 1 - A_d, 1 - A_d, 1 - A_d)
D3D12_BLEND_DEST_COLOR = 9, // F(R_d, G_d, B_d, A_d)
D3D12_BLEND_INV_DEST_COLOR = 10, // F(1 - R_d, 1 - G_d, 1 - B_d, 1 - A_d)
D3D12_BLEND_SRC_ALPHA_SAT = 11, // F(f, f, f, 1),f = min(A_s, 1 - A_d) 且 f = clamp(0,1)
D3D12_BLEND_BLEND_FACTOR = 14, // F(r, g, b, a)。用于ID3D12GraphicsCommandList::OMSetBlendFactor(F)
D3D12_BLEND_INV_BLEND_FACTOR = 15, // F(1 - r, 1 - g, 1 - b, 1 - a)
D3D12_BLEND_SRC1_COLOR = 16,
D3D12_BLEND_INV_SRC1_COLOR = 17,
D3D12_BLEND_SRC1_ALPHA = 18,
D3D12_BLEND_INV_SRC1_ALPHA = 19,
D3D12_BLEND_ALPHA_FACTOR = 20,
D3D12_BLEND_INV_ALPHA_FACTOR = 21
} ;
rendertarget混合状态描述符
混合状态描述符D3D12_BLEND_DESC (d3d12.h)描述是否启用AlphaToCoverage、IndependentBlend,以及D3D12_RENDER_TARGET_BLEND_DESC
rendertarget混合描述符D3D12_RENDER_TARGET_BLEND_DESC (d3d12.h)描述具体的blend操作
禁用rendertarget的写入位遮蔽D3D12_COLOR_WRITE_ENABLE (d3d12.h)
typedef struct D3D12_BLEND_DESC
{
BOOL AlphaToCoverageEnable; // default:FALSE.主要用于渲染叶片/门的纹理的多重采样技术
BOOL IndependentBlendEnable; // default:FALSE.向每一个rendertarget执行不同的blend.D3D最多同时支持8个rendertarget.若为FALSE,则所有rendertarget都使用下一个参数中首个元素的blend操作
D3D12_RENDER_TARGET_BLEND_DESC RenderTarget[8]; // 第i歌元素描述如何对第i个rendertarget进行blend
} D3D12_BLEND_DESC;
typedef struct D3D12_RENDER_TARGET_BLEND_DESC
{
BOOL BlendEnable; // default:FALSE.启用/禁用blend.
BOOL LogicOpEnable; // default:FALSE.启用/禁用逻辑运算符
D3D12_BLEND SrcBlend; // RGB中源片元的混合因子
D3D12_BLEND DestBlend; // RGB中目标片元的混合因子
D3D12_BLEND_OP BlendOp; // RGB的二元运算符
D3D12_BLEND SrcBlendAlpha; // Alpha中源片元的混合因子
D3D12_BLEND DestBlendAlpha; // Alpha中目标片元的混合因子
D3D12_BLEND_OP BlendOpAlpha; // Alpha的二元运算符
D3D12_LOGIC_OP LogicOp; // 逻辑运算符
UINT8 RenderTargetWriteMask; // rendertarget的写入位遮蔽
} D3D12_RENDER_TARGET_BLEND_DESC;
// 禁用rendertarget的写入位遮蔽
// 如:指定D3D12_COLOR_WRITE_ENABLE_RED,则可以写入r数据,但不可以写入gba
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
} ;
透明blend
渲染不透明物体,透明物体/半透明物体时需要注意以下几点
- 先绘制无需混合处理的物体
- 关闭深度写入但保留深度测试,再根据需要混合的物体的z值将它们进行sort,最后按画家算法的思路进行blend然后绘制
原因:进行混合后的物体属于半透明或全透明的物体,若假设一个不透明物体的z值大于透明物体,且它们在像素点上有部分重叠,若此时进行深度测试进行替换,会导致不透明物体的颜色值直接被丢弃,从而造成错误的结果
实现半透明物体
实现
// 第一次渲染:渲染天空盒依旧照常设置,将其视为不透明物体
// 第二次渲染:渲染物体,但对物体进行blend(over算子)——对源片元和目标片元的RGBA进行加法blend
// 注意:此时需要启用深度测试,但关闭深度写入,这样才能保证源片元不会被丢弃
opaquePSODesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
D3D12_RENDER_TARGET_BLEND_DESC opaqueBlendDesc = {};
opaqueBlendDesc.BlendEnable = TRUE;
opaqueBlendDesc.LogicOpEnable = FALSE;
opaqueBlendDesc.SrcBlend = D3D12_BLEND_SRC_COLOR;
opaqueBlendDesc.DestBlend = D3D12_BLEND_DEST_COLOR;
opaqueBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;
opaqueBlendDesc.SrcBlendAlpha = D3D12_BLEND_SRC_ALPHA;
opaqueBlendDesc.DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
opaqueBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
opaqueBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
opaqueBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
opaquePSODesc.BlendState.RenderTarget[0] = opaqueBlendDesc;
D3D12_DEPTH_STENCIL_DESC outlineDesc;
outlineDesc.DepthEnable = TRUE; // enable depth test
outlineDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; // Turn on writes to the depth-stencil buffer
输出
reference
RTR3
Direct3D 12 programming guide