DX12 实战 blend

chenglixue / 2023-05-07 / 原文

前言

本篇将展示如何使用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

输出

image-20230507004333249

reference

RTR3

Direct3D 12 programming guide