











本章系统讲解混合(Blending)技术—— 通过将当前光栅化的源像素与渲染目标中已有的目标像素进行数学融合,实现半透明物体、特效叠加、像素裁剪、大气雾效等关键渲染效果,是上一章纹理映射的重要延伸,也是游戏中玻璃、水、粒子、UI、天气特效等效果的核心实现基础。
混合的核心目标:将像素着色器当前输出的源像素(Source Pixel),与渲染目标(后台缓冲 / 离屏渲染目标)中已有的目标像素(Destination Pixel)进行融合通过混合方程计算最终结果,而非直接覆盖,从而实现半透明、颜色叠加等效果(如透过水面看到水底的地形与木箱)。
源像素\(C_{src}\)/\(A_{src}\):当前像素着色器输出的 RGBA 值,即将写入渲染目标的像素;
目标像素\(C_{dst}\)/\(A_{dst}\):渲染目标中已存在的 RGBA 值,即当前像素位置上已经绘制的画面内容;
RGB 通道混合核心方程\(C=C_{src} \otimes F_{src} \boxplus C_{dst} \otimes F_{dst}\)
\(\otimes\):颜色向量的分量级乘法;
\(\boxplus\):二元混合操作符(加、减、最大 / 最小等);
\(F_{src}\):源混合因子,\(F_{dst}\):目标混合因子,用于自定义源 / 目标像素的权重。
Alpha 通道独立混合方程\(A=A_{src}F_{src} \boxplus A_{dst}F_{dst}\)
Alpha 通道与 RGB 通道使用独立的混合因子、混合操作符,可单独配置;
绝大多数场景无需修改 Alpha 通道混合,仅当算法需要目标 Alpha 值时才会用到。
混合不仅适用于后台缓冲,对所有离屏渲染目标同样生效,目标像素为渲染目标中已光栅化的像素值。混合最终会用融合后的新颜色C/A覆盖目标像素。
本节讲解了混合方程中二元操作符\(\boxplus\)的所有可选类型,包括算术混合操作与逻辑操作,明确了不同操作的数学规则、效果与使用限制。
算术混合
使用D3D12_BLEND_OP枚举,对源像素和目标像素进行算术计算
| D3D12_BLEND_OP枚举值 | 数学规则 | 核心效果 |
|---|---|---|
| D3D12_BLEND_OP_ADD | \(C=C_{src} \otimes F_{src} + C_{dst} \otimes F_{dst}\) | 最常用的加法混合,用于半透明、加性特效 |
| D3D12_BLEND_OP_SUBTRACT | \(C=C_{dst} \otimes F_{dst} - C_{src} \otimes F_{src}\) | 目标像素减去源像素,反向减色 |
| D3D12_BLEND_OP_REV_SUBTRACT | \(C=C_{src} \otimes F_{src} - C_{dst} \otimes F_{dst}\) | 源像素减去目标像素,实现减色变暗效果 |
| D3D12_BLEND_OP_MIN | \(C=min(C_{src}, C_{dst})\) | 取源 / 目标像素的最小值,忽略混合因子 |
| D3D12_BLEND_OP_MAX | \(C=max(C_{src}, C_{dst})\) | 取源 / 目标像素的最大值,忽略混合因子 |
逻辑操作混合
使用D3D12_LOGIC_OP枚举,对源像素和目标像素进行逻辑运算。
支持与、或、非、异或、拷贝、清零等 16 种位逻辑操作,直接对像素的二进制位进行运算;
核心限制:
逻辑操作与传统算术混合不能同时启用;
逻辑操作仅支持 UINT 类型的渲染目标格式,否则会触发渲染管线创建错误:像素着色器输出项‘SV_Target0’需匹配无符号整型类型。
为源 / 目标混合因子(\(F_{src}\)/\(F_{dst}\))设置不同的混合运算符,可以实现各种混合效果。
D3D12通过混合因子枚举,D3D12_BLEND,提供了一些预设的混合因子。这些因子同时适用于 RGB 与 Alpha 混合方程(_COLOR结尾的因子仅适用于 RGB 通道),定义如下:
| 枚举值 | RGB | Alpha | 核心用途 |
|---|---|---|---|
| D3D12_BLEND_ZERO | (0,0,0) | 0 | 屏蔽对应通道的贡献 |
| D3D12_BLEND_ONE | (1,1,1) | 1 | 保留对应通道的完整贡献 |
| D3D12_BLEND_SRC_COLOR | ($r_s$, $g_s$, $b_s$) | 不支持 | 用源颜色调制目标颜色 |
| D3D12_BLEND_INV_SRC_COLOR | (1-$r_s$, 1-$g_s$, 1-$b_s$) | 不支持 | 反向源颜色调制 |
| D3D12_BLEND_SRC_ALPHA | ($a_s$, $a_s$, $a_s$) | $a_s$ | 用源 Alpha 控制源像素权重,半透明核心 |
| D3D12_BLEND_INV_SRC_ALPHA | (1-$a_s$, 1-$a_s$, 1-$a_s$) | 1-$a_s$ | 用源 Alpha 控制目标像素权重,半透明核心 |
| D3D12_BLEND_DEST_COLOR | ($r_d$, $g_d$, $b_d$) | 不支持 | 用目标颜色调制源颜色 |
| D3D12_BLEND_INV_DEST_COLOR | (1-$r_d$, 1-$g_d$, 1-$b_d$) | 不支持 | 反向目标颜色调制 |
| D3D12_BLEND_DEST_ALPHA | ($a_d$, $a_d$, $a_d$) | $a_d$ | 用目标 Alpha 控制权重 |
| D3D12_BLEND_INV_DEST_ALPHA | (1-a_d, 1-a_d, 1-a_d) | 1-a_d | 反向目标 Alpha 控制权重 |
| D3D12_BLEND_SRC_ALPHA_SAT | clamp($a_s$,0,1) | clamp($a_s$,0,1) | 限制源 Alpha 权重在 0-1 范围,避免过曝 |
自定义混合因子
可以自行指定RGBA四个值的混合因子
D3D12_BLEND_BLEND_FACTOR/D3D12_BLEND_INV_BLEND_FACTOR:使用应用程序手动指定的固定颜色作为混合因子;
设置 API:ID3D12GraphicsCommandList::OMSetBlendFactor(const FLOAT BlendFactor[4]),传入 nullptr 可恢复默认值 (1,1,1,1);
特性:混合因子为全局固定值,直到再次调用 API 修改。
混合状态是图形管线状态对象(PSO)的一部分,DirectX 12提供默认的混合状态。可以通过混合描述结构体(D3D12_BLEND_DESC),设置管线的混合方式。

管线的混合描述D3D12_BLEND_DESC
AlphaToCoverageEnable:开启 Alpha-to-coverage,一种多重采样技术,用于渲染植被、栅栏、树叶等透明纹理,需开启多重采样;
IndependentBlendEnable:开启多渲染目标独立混合,最多支持 8 个同时渲染目标使用不同的混合配置;关闭时所有渲染目标使用RenderTarget[0]的配置
RenderTarget:8 个渲染目标的独立混合配置。
渲染目标的混合描述D3D12_RENDER_TARGET_BLEND_DESC
BlendEnable:开启 / 禁用传统算术混合,与LogicOpEnable不能同时为 TRUE;
LogicOpEnable:开启 / 禁用逻辑操作混合,与BlendEnable不能同时为 TRUE;
SrcBlend/DestBlend/BlendOp:RGB 通道的源 / 目标混合因子、混合操作符;
SrcBlendAlpha/DestBlendAlpha/BlendOpAlpha:Alpha 通道的源 / 目标混合因子、混合操作符;
LogicOp:逻辑操作枚举值;
RenderTargetWriteMask:颜色通道写掩码,控制着混合后的数据可被写入后台缓冲区中哪些颜色通道,可控制RGBA通道的写入权限,支持D3D12_COLOR_WRITE_ENABLE_ALL或单独通道开关。
工程实现规范
所有 PSO 需在使用前预创建,例如分别创建不透明、半透明、Alpha 测试的 PSO;
运行时通过SetPipelineState切换 PSO,实现混合状态的切换;
混合会带来额外的逐像素计算开销,仅在需要时开启,使用完毕后切换回禁用混合的 PSO。
讲解了不同混合因子、操作符的组合方式,以及对应的效果、数学简化、绘制规则与深度缓冲的配合技巧。
无颜色写入
*\(F_{src}\)=ZERO,\(F_{dst}\)=ONE,BlendOp=ADD,混合方程简化为C=\(C_{dst}\),完全保留目标像素;
等效方案:直接将RenderTargetWriteMask设为 0,禁用所有颜色通道写入;
适用场景:仅需写入深度/模板缓冲,无需修改颜色缓冲,例如阴影贴图渲染、深度预pass。
加减混合
加法混合:\(F_{src}\)=ONE,\(F_{dst}\)=ONE,BlendOp=ADD,方程简化为C=\(C_{src}\)+\(C_{dst}\);

减法混合:将操作符改为D3D12_BLEND_OP_SUBTRACT,方程简化为C=\(C_{src}\)-\(C_{dst}\);

先绘制不透明物体,后绘制混合物体;加减混合满足交换律,无需对混合物体排序,绘制顺序不影响最终结果。
乘法混合
*\(F_{src}\)=ZERO,\(F_{dst}\)=SRC_COLOR,BlendOp=ADD,方程简化为C=\(C_{src}\otimes C_{dst}\);

透明混合
*\(F_{src}\)=SRC_ALPHA,\(F_{dst}\)=INV_SRC_ALPHA,BlendOp=ADD,方程简化为:C=\(a_sC_{src}+(1-a_s)C_{dst}\)
源 Alpha 值\(a_s\)代表源像素的不透明度(0 = 完全透明,1 = 完全不透明),最终颜色为源颜色与目标颜色的加权平均,完美模拟半透明效果;
绘制规则:
先绘制所有不使用混合的不透明物体;
对透明物体按与相机的距离从远到近排序;
按从后往前的顺序绘制透明物体,保证透明物体能与后方的场景正确混合;
适用场景:水、玻璃、半透明塑料、UI 面板等真实半透明物体。
混合与深度缓冲区
核心问题:通过加性/减性/乘法混合绘制的粒子、光效等物体,不应互相遮挡;若开启深度写入,先绘制的物体会在深度缓冲中写入深度,导致后方的物体被深度测试剔除,无法完成颜色叠加;
解决方案:绘制这类混合物体时,禁用深度写入,但保留深度读取与深度测试;
禁用深度写入:混合物体不会互相遮挡,所有物体的颜色都能正确叠加;
保留深度测试:保证不透明的墙体会遮挡后方的混合物体,符合视觉逻辑;
注意事项:仅在绘制该组混合物体时临时禁用深度写入,绘制完毕后应恢复深度写入。
如果使用透明混合,通过漫反射纹理贴图的Alpha通道,美术控制物体的透明度。
需要在像素着色器中输出纹理贴图的Alpha值,这样混合阶段使用该 Alpha 值控制半透明程度。
纹理需使用支持 Alpha 通道的纹理格式,例如BC3_UNORM、R8G8B8A8_UNORM等。
HLSL内置的clip函数,该函数可以丢弃像素,从而实现像素的裁剪。
clip(x)函数核心规则
仅能在像素着色器中调用;
若输入值x < 0,则完全丢弃当前像素,终止后续像素着色器执行,该像素不会写入渲染目标、深度 / 模板缓冲;
若x ≥ 0,像素正常执行后续流程。
利用clip函数,可以实现Alpha测试。即通过纹理的Alpha通道控制像素丢弃,实现“要么完全不透明,要么完全透明”的效果(如铁丝网、栅栏、树叶、镂空图案);
相比混合的核心优势
性能更高:无需执行混合计算,可提前终止像素着色器执行,跳过后续光照等计算;
绘制顺序无关:无需对物体进行深度排序,实现更简单;
无半透明过渡,仅实现 0/1 式的透明效果。
注意事项
纹理过滤会导致 Alpha 通道边缘模糊,需设置合理的阈值(如 0.1),而非严格的 0,避免出现毛边;
需关闭背面剔除(CullMode = D3D12_CULL_MODE_NONE):因为镂空物体可被看穿,背面也需要渲染,否则会出现背面缺失的错误。
线性雾效是在像素着色器,采用类似透明混合的公式,将雾色叠加到渲染中。通过这个混合,可以模拟出雾的效果,隐藏远处的渲染瑕疵,防止物体“弹出(Popping)”(远平面外的物体在视锥体突兀出现)。
线性雾效数学模型
最终雾效颜色公式:foggedColor = (1-s)·litColor + s·fogColor其中litColor是像素原本的光照颜色,fogColor是雾的颜色,s是雾的权重系数,范围 [0,1]。
雾权重系数计算:\(s = saturate(\frac{dist(p,E) - fogStart}{fogRange})\)
dist(p,E):像素点 p 与相机位置 E 的距离;
fogStart:雾开始生效的距离,小于该距离的物体不受雾影响;
fogRange:雾的衰减范围,fogEnd = fogStart + fogRange,超过该距离的物体完全被雾遮挡;
saturate函数将结果钳位在 [0,1] 范围内。
雾效的代码实现
在像素着色器中,完成光照计算后执行雾效计算;
通过宏定义FOG开关雾效,未开启时不会执行雾效计算,无性能开销;
在编译Shader时传入D3D_SHADER_MACRO,让HLSL的宏起效
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");
雾的颜色、起始距离、范围通过每帧的 Pass 常量缓冲区传入,可动态修改。
混合技术通过源像素与目标像素的数学融合,实现半透明物体、特效叠加等效果,核心是 RGB 与 Alpha 通道的独立混合方程;
混合效果由混合操作符、源 / 目标混合因子共同决定,可通过不同组合实现数十种渲染效果;
混合状态是 PSO 的一部分,需预创建后运行时切换,Alpha 通道来自漫反射纹理的 Alpha 分量;
clip函数可完全丢弃像素,高效实现 Alpha 测试,适用于全透明 / 全不透明的镂空纹理;
线性雾效通过像素与相机的距离,混合原始颜色与雾颜色,模拟大气效果,隐藏渲染瑕疵。
练习 1:尝试不同的混合操作符与混合因子组合,观察渲染效果变化;
练习 2:修改 Blend Demo,先绘制水面再绘制地形,分析错误的渲染结果,理解从后往前绘制顺序的重要性;
练习 3:给定雾效参数,计算不同距离下的雾效颜色,掌握雾效的数学计算;
练习 4:通过着色器汇编,验证开启 / 关闭 ALPHA_TEST 宏时,是否生成 discard 指令,理解 clip 函数的底层实现;
练习 5:修改 Blend Demo,创建混合状态,禁用红、绿颜色通道的写入,仅保留蓝、Alpha 通道。
混合核心方程:\(C=C_{src} \otimes F_{src} \boxplus C_{dst} \otimes F_{dst}\),RGB与Alpha通道可独立配置混合操作和混合因子;
半透明标准配置:源因子SRC_ALPHA,目标因子INV_SRC_ALPHA,加法操作,绘制顺序为「先不透明,后透明物体从后往前」;
加性特效标准配置:源/目标因子均为ONE,加法操作,绘制时禁用深度写入、保留深度测试;
混合状态属于PSO,必须初始化预创建,运行时仅能切换完整 PSO,无法单独修改混合参数;
Alpha测试:通过clip(diffuseAlbedo.a - threshold)实现,比混合性能更高,无需排序,需关闭背面剔除;
Alpha通道来源:漫反射纹理的Alpha通道,像素着色器输出Alpha值供混合阶段使用;
颜色写掩码:可单独控制RGBA四个通道的写入权限,实现特殊渲染效果;
线性雾效:基于像素到相机的距离做颜色插值,可模拟大气透视,隐藏远处物体的弹出与瑕疵;
混合性能:混合会带来额外的逐像素开销,非必要不开启,使用完毕后立即切换回禁用混合的PSO;
逻辑操作混合:与传统算术混合互斥,仅支持 UINT 格式的渲染目标,用于特殊的位运算渲染效果。
之前看计算机图形学都是只提到算术混合,看龙书时第一次看到这种按逻辑运算的混合操作。但书中内容不多,也没看到使用的场景。所以好奇它有什么特别,会用在哪里。所以进行了一下查询。
首先,逻辑操作混合不是传统的颜色算术混合,它不对源 / 目标像素做浮点加减乘运算,而是对像素的二进制位执行原生按位逻辑运算。它的核心价值是:在不破坏整型数据的前提下,对渲染目标的二进制位做精准、高效的硬件级修改,这是传统算术混合完全做不到的。
以下是它的主流应用场景,按使用频率从高到低排序:
延迟渲染管线中,会渲染多个 GBuffer 渲染目标,其中大量使用UINT 格式的缓冲存储非颜色数据:
物体 ID 缓冲(Object ID Buffer):R32_UINT 格式,每个像素存储当前绘制物体的唯一整型 ID,用于后期选中、轮廓、物体级后期特效;
材质 ID 缓冲(Material ID Buffer):R8_UINT/R16_UINT 格式,存储像素对应的材质类型 ID,用于延迟光照的材质分支;
标记缓冲(Tag Buffer):用单个二进制位标记像素的属性(如是否受阴影影响、是否高亮、是否是透明物体)。
传统算术混合会直接破坏整型数据:比如给物体 ID 0x00000005 叠加高亮标记,用加法混合 0x00000005 + 0x80000000 = 0x80000005 看似可行,但如果 ID 本身是高位占用的数值,加法会直接溢出、篡改 ID 值;
而逻辑操作是按位处理,不会破坏原有数据。
具体实现
需求:给选中的物体,在 ID 缓冲的最高位标记「高亮位」,不修改原有物体 ID;
渲染目标:R32_UINT 格式的 Object ID 缓冲;
逻辑操作配置:D3D12_LOGIC_OP_OR(按位或);
像素着色器输出:uint(0x80000000)(仅最高位置 1,其余位为 0);
硬件执行效果:最终像素 = 源像素 | 目标像素,原有 ID 的低 31 位完全保留,仅最高位被置 1,完美实现标记,无数据损坏风险。
其他常见用法:
用D3D12_LOGIC_OP_AND清除指定标记位;
用D3D12_LOGIC_OP_XOR翻转标记位;
用D3D12_LOGIC_OP_CLEAR/SET批量重置 / 设置缓冲。
逻辑操作混合是制作精准、无浮点精度误差的二进制遮罩的最优方案,常用于 UI 渲染、特效裁剪、自定义视口等场景。
需要生成一个不规则的二进制遮罩缓冲(R8_UINT 格式,1 = 可绘制,0 = 不可绘制),比如:游戏内 UI 的不规则窗口裁剪、镂空效果;特效的自定义可见区域(如仅在圆形 / 多边形区域内显示粒子);离线渲染的自定义裁剪区域,替代固定管线的视口 / 裁剪平面。
这样做二进制遮罩的优点有:
纯硬件位运算,无浮点计算,开销远低于算术混合;
无浮点精度误差,遮罩边缘绝对精准,不会出现半透明过渡;
多 Pass 合成遮罩极其简单:比如先画矩形遮罩用SET置 1,再画镂空圆形用CLEAR清 0,无需复杂的着色器计算。
体素渲染、3D 纹理的体积数据处理中,逻辑操作混合是核心工具。体素渲染使用 3D UAV / 渲染目标(R8_UINT/R32_UINT 格式)存储体素数据,每个体素的二进制位分别标记:
位 0:体素是否被占用(是否有几何体);
位 1:体素是否是碰撞体;
位 2:体素是否受光照影响;
其余位:存储体素的材质 ID。
具体用法
场景体素化 Pass:用D3D12_LOGIC_OP_OR,将几何体覆盖的体素的「占用位」置 1;
碰撞体烘焙 Pass:用D3D12_LOGIC_OP_AND,仅保留同时满足「占用 + 碰撞」的体素;
动态破坏 Pass:用D3D12_LOGIC_OP_CLEAR,清除被破坏区域的体素占用位。
在引擎 / 渲染管线的开发调试中,逻辑操作混合是高效的像素级调试工具:
用一个 R32_UINT 的调试缓冲,每个位对应一种调试标记(如位 0 = 深度测试失败、位 1 = 法线异常、位 2=UV 超出范围);
每个调试 Pass 用对应的逻辑操作,给异常像素标记对应的位,最终可以一次性查看所有像素的异常类型,无需多次渲染调试缓冲。
用逻辑操作混合可以快速生成复杂的二进制图案,无需复杂的着色器代码:
比如生成棋盘格、网格线、二维码、条形码等二进制图案,用多个 Pass 分别绘制横竖线,通过OR/AND/XOR操作合成最终图案;
硬件原生位运算,比在像素着色器里做数学计算生成图案快一个数量级。
早期 2D 游戏、街机游戏的固定管线中,没有现代混合功能,大量使用逻辑操作(XOR/OR)实现精灵绘制、透明镂空、画面闪烁特效。在老游戏复刻、复古风格游戏开发中,逻辑操作混合可以完美复现原版的渲染效果,无需模拟计算。
在多 Pass 的离线渲染、光照烘焙中,每个 Pass 会输出不同的标记数据,用逻辑操作混合可以将多个 Pass 的标记合并到一个 UINT 缓冲中,节省显存和带宽:
OR操作合并到一个 R8_UINT 缓冲中,一次性存储 4 个方向的阴影信息。格式强制要求:渲染目标必须是 UINT 格式,否则会触发 PSO 创建错误,常规的后台颜色缓冲(UNORM/SRGB)无法使用;
互斥性:BlendEnable和LogicOpEnable不能同时为 TRUE,传统算术混合和逻辑操作只能二选一;
着色器输出匹配:像素着色器的输出必须是无符号整型(uint/uint4),否则会出现类型不匹配警告,硬件会直接按二进制位 reinterpret 处理,无数据转换;
多渲染目标限制:开启独立渲染目标混合时,每个渲染目标可以单独开启 / 关闭逻辑操作,但必须都满足 UINT 格式要求。
龙书中提到自定义混合因子,想了解更多,所以查询了一下。
自定义混合因子是Direct3D 12提供的、由应用程序手动指定固定值作为混合因子,对应两个枚举值,D3D12_BLEND_BLEND_FACTOR和D3D12_BLEND_INV_BLEND_FACTOR。
通过该方法制定因子值
void ID3D12GraphicsCommandList::OMSetBlendFactor(const FLOAT BlendFactor[4]);
入参:长度为 4 的浮点数组,对应(R,G,B,A)四个分量,范围[0,1];
特殊规则:传入nullptr时,会自动恢复默认值(1.0f, 1.0f, 1.0f, 1.0f);
生效前提:必须在 PSO 的混合状态中,将SrcBlend/DestBlend设置为上述两个自定义枚举值,否则设置的数值不会生效。
生命周期:跟命令列表绑定,是命令列表级别的全局状态,设置后后续所有绘制调用生效,期间绘制的所有像素共用一个值,直到再次修改它,或重置/关闭命令列表。
自定义混合因子由PSO启用/禁用,需在在创建 PSO 时,在D3D12_BLEND_DESC里把SrcBlend/DestBlend设置为BLEND_FACTOR/INV_BLEND_FACTOR,后续调用OMSetBlendFactor设置的数值才不会被GPU忽略。实际 D3D12 规范中:
自定义混合因子的数值,仅能通过OMSetBlendFactor修改,切换 PSO 不会改变当前命令列表中的自定义混合因子数值;
即使你切换了 PSO,只要新的 PSO 启用了自定义混合因子,就会沿用当前命令列表中已经设置的数值,除非你主动调用OMSetBlendFactor修改。
自定义混合因子的核心价值是:对整个绘制批次做统一的、全局的混合权重控制,无需修改着色器代码、无需修改纹理、无需批量更新材质参数。它可以应用到如下场景:
游戏启动、场景切换、死亡黑屏时,需要让整个画面从完全透明过渡到完全黑屏,或从黑屏过渡到正常画面。
实现方案
先渲染完整个 3D 场景到后台缓冲;
绘制一个覆盖整个屏幕的全屏四边形,PSO 混合配置为:
源混合因子:D3D12_BLEND_BLEND_FACTOR
目标混合因子:D3D12_BLEND_INV_BLEND_FACTOR
混合操作:D3D12_BLEND_OP_ADD
全屏四边形的像素着色器仅输出固定黑色(0,0,0,1);
每帧通过OMSetBlendFactor更新自定义值:淡入时从(0,0,0,0)线性过渡到(1,1,1,1),淡出时反向过渡。
优势
如果用SRC_ALPHA实现,需要每帧修改着色器常量、或修改全屏四边形的顶点颜色,代码冗余;
自定义混合因子仅需调用一次 API,无需修改任何着色器、材质、几何体数据,实现极简,性能开销为 0。
场景中有大量同类型的半透明物体(比如一群粒子、一整片玻璃幕墙、一组 UI 控件),需要统一控制它们的整体透明度,比如玩家靠近时整体渐隐、菜单打开时整体淡入。
实现方案
所有物体共用同一个 PSO,混合配置为SRC_ALPHA * BLEND_FACTOR + INV_SRC_ALPHA;
物体本身的纹理 Alpha 控制逐像素的透明度,自定义混合因子控制整体的全局透明度;
需要调整整体透明度时,仅需调用一次OMSetBlendFactor,所有物体的透明度会同步变化,无需逐个修改材质参数。
优势
如果用常量缓冲区实现,需要逐个更新每个物体的材质常量,当物体数量极多时,CPU 开销大;
自定义混合因子一次调用全局生效,无需修改任何物体的数据,批量控制效率极高。
渲染两张渲染目标的融合效果,比如场景与景深模糊图的混合、原画与风格化渲染图的过渡、多 Pass 光照的叠加权重控制。
实现方案
先将 A 图渲染到渲染目标 A,B 图渲染到渲染目标 B;
绘制全屏四边形,采样渲染目标 B,PSO 混合配置为:
BLEND_FACTOR,目标因子:INV_BLEND_FACTOR,加法操作;通过自定义混合因子的值,直接控制 B 图与 A 图的融合比例:最终颜色 = factor * B + (1-factor) * A。
优势
如果在像素着色器里做混合,需要同时采样两张图,占用更多带宽,着色器开销更高;
自定义混合因子由 GPU 硬件原生执行,无需采样额外纹理,带宽和性能更优,且修改权重无需重新编译着色器。
渲染管线调试时,需要给整个画面叠加一个固定颜色,或全局衰减某个颜色通道,比如标记当前渲染的 Pass、检查某个通道的数值是否溢出。
实现方案
绘制全屏四边形,像素着色器输出固定调试颜色(比如红色(1,0,0,1));
PSO 混合配置为源因子BLEND_FACTOR,目标因子ONE,加法操作;
通过OMSetBlendFactor控制红色的叠加强度,无需修改任何场景渲染代码,即可给整个画面叠加调试色。
卡通渲染、非真实感渲染中,需要多 Pass 叠加描边、阴影、高光层,每个层的叠加权重需要动态调整。
实现方案
每个 Pass 用自定义混合因子控制当前层的叠加强度,比如描边 Pass 用BLEND_FACTOR控制描边的粗细 / 透明度,无需每个 Pass 都修改着色器常量,代码更简洁,调试更方便。
关于Alpha Test、Alpha Blend对Early Z的影响,是以前面试中经常遇到的老问题了,这里就总结一下,并记录下来。
首先,先明确一下上述名词的含义
Alpha Test是「0/1 式的像素丢弃」—— 像素要么完全保留,要么完全被丢弃,不存在半透明过渡。
通过HLSL内置的clip(x)函数实现,相应地:
混合状态:禁用混合(BlendEnable = FALSE),因为不需要颜色融合;
背面剔除:通常禁用背面剔除(CullMode = D3D12_CULL_MODE_NONE),因为镂空物体(如铁丝网)的背面也需要被看到;
深度写入/测试:保留深度写入与深度测试,未被丢弃的像素会正常写入深度缓冲。
Alpha Blend是「加权颜色融合」—— 通过源 Alpha 值控制源像素与目标像素的混合比例,实现真实的半透明效果。
通过设置PSO的混合状态配置实现,通常有严格的绘制顺序的要求:
先绘制所有不透明物体(正常写入深度缓冲);
将半透明物体按与相机的距离从远到近排序;
按从后往前的顺序绘制半透明物体,保证后方物体先写入渲染目标,前方物体能与其正确混合。
Early Z是GPU的硬件级深度优化:在像素着色器执行之前,先对像素做深度测试,若像素深度测试失败(被前方物体遮挡),则直接丢弃该像素,跳过后续的像素着色器计算,大幅减少GPU开销。
Early Z的核心前提:GPU能在像素着色器执行前,确定该像素是否会通过深度测试,且深度缓冲的状态是可预测的。
结论:Alpha Test会导致Early Z完全失效,GPU会回退到Late Z(像素着色器后续的“深度 /模板测试”)。
原因:Alpha Test的clip(x)函数是在像素着色器内部执行的,GPU在Early Z 阶段(像素着色器前),无法知道该像素的Alpha值是多少,也无法预测clip(x)是否会丢弃该像素。
性能影响:对于Alpha Test物体,即使被前方物体完全遮挡,也会执行完整的像素着色器,仅在最后被丢弃,GPU开销显著增加。
结论:Alpha Blend对Early Z没有影响,但通常需要搭配“禁用深度写入 + 保留深度测试”会大幅削弱Early Z的优化效果,甚至完全失效。
原因:Alpha Blend通常搭配“禁用深度写入 + 保留深度测试”,这样导致混合绘制出来的物体无法作为Early Z提前剔的遮挡的物。
为了保证半透明物体之间的正确混合,绘制半透明物体时通常会禁用深度写入。这会导致半透明物体不会更新深度缓冲,后续的半透明物体无法通过Early Z知道自己是否被之前的半透明物体遮挡,而且半透明物体需要与后方的场景混合。所以半透明物体总是被绘制,无法被Early Z优化;
半透明物体需要从后往前绘制,这与 Early Z依赖的「从前到后绘制,深度缓冲快速收敛」的优化方向完全相反,所以导致其失效。
举例:先绘制不透明的场景(正常写入深度缓冲),再绘制加性混合的粒子(禁用深度写入,保留深度测试),Early Z的效果:
对于被不透明场景遮挡的粒子,Early Z 能通过读取不透明场景的深度值,提前丢弃这些粒子,跳过粒子的像素着色器;
对于未被遮挡的粒子,Early Z 的深度测试通过,但不会写入深度缓冲,后续粒子无法通过 Early Z 知道当前粒子的存在;
即仍能通过 Early Z 丢弃被不透明场景遮挡的粒子,保留了部分优化效果,但粒子之间无法通过 Early Z 优化。
性能影响:只有不透明物体能够形成遮挡,让Early Z起效,能够终止被其遮挡的像素(无论该像素来自不透明物体还是半透明物体)执行像素着色器;半透明物体不能形成遮挡,Early Z完全失效,其遮挡的像素都会执行完整的像素着色器;
Alpha Blend如果开启深度写入,对Early Z没有影响,例如:顺序无关半透明(OIT)、树叶/植被的Alpha-to-Coverage渲染
而禁用深度写入+保留深度测试,但完全关闭Alpha Blend的,也有很多应用场景,例如:
调试用的Gizmo线框/辅助几何体:比如场景里的红色碰撞盒线框,我们希望:它会被前面的墙挡住(保留深度测试),但它不会挡住后面的模型(禁用深度写入);线框是完全不透明的红色,不需要Alpha Blend。
不透明的粒子特效:比如火焰的火星、烟雾的不透明核心,不需要半透明混合,但希望它被墙挡住,又不会挡住其他粒子,同样用这个深度配置,完全不用 Alpha Blend。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。