【渲染流水线】[逐片元阶段]-[混合Blend]以UnityURP为例

Unity URP(Universal Render Pipeline)中的Blend和BlendOp是ShaderLab中控制颜色混合的核心指令,其发展历史与渲染技术演进密切相关。早期固定功能管线仅支持简单的Alpha混合,随着可编程着色器的普及,混合操作逐渐扩展为可定制化的数学运算。URP通过优化这些指令的底层实现,使其在移动端和高性能平台均能高效运行。

【从UnityURP开始探索游戏渲染】专栏-直达

OpenGL中的混合实现

API调用机制

OpenGL通过glBlendFuncglBlendEquation函数实现混合操作,对应URP中BlendBlendOp指令。其中:

  • glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)等价于URP的Blend SrcAlpha OneMinusSrcAlpha
  • glBlendEquation(GL_FUNC_ADD)对应BlendOp Add操作

管线阶段差异

OpenGL将混合作为固定管线阶段,在片段着色器之后执行,而URP通过可编程着色器在片元处理阶段动态控制混合参数

双源混合限制

OpenGL 4.x支持GL_SRC1_COLOR等双源混合因子,但移动端GLES 3.0需通过扩展实现,URP对此做了平台兼容性封装

内置管线混合实现

早期实现方式

内置管线通过Queue="Transparent"标签自动启用混合,但需手动配置Blend命令且不支持BlendOp高级操作

深度处理差异

内置管线要求显式关闭深度写入(ZWrite Off),而URP在透明渲染队列中自动管理深度测试与混合的冲突

性能优化对比

内置管线混合计算固定于GPU固定功能单元,URP则通过可编程着色器实现动态混合策略,如根据设备性能切换Min/Max操作

关键技术对比

特性 OpenGL原生实现 Unity内置管线 URP优化方案
混合因子配置 glBlendFunc Blend命令 封装为跨平台Shader指令
操作符扩展 glBlendEquation 仅支持基础加减 支持Min/Max/RevSub
移动端兼容性 需检查扩展支持 全平台统一行为 自动降级混合方案
多渲染目标支持 glDrawBuffers 有限支持 完整MRT混合控制
  • OpenGL中实现URP的BlendOp RevSub需调用glBlendEquation(GL_FUNC_REVERSE_SUBTRACT)
  • 内置管线模拟URP发光效果需组合Blend One One与多个Pass渲染

核心问题解决

  • 透明效果实现 ‌:通过Blend SrcAlpha OneMinusSrcAlpha等组合,解决了传统透明度混合中排序依赖和边缘锯齿问题
  • 复杂光效合成 ‌:利用BlendOp MaxAdd操作,实现发光体叠加时的亮度累积效果
  • 非破坏性图像处理 ‌:BlendOp RevSub等操作允许反向颜色计算,用于特殊遮罩或腐蚀效果

命令使用介绍

  • 将当前片元颜色与帧缓冲颜色按公式混合
  • 支持加法/乘法/透明度混合等模式‌
  • 混合当前片元与颜色缓冲(Blend 指令,如 Blend SrcAlpha OneMinusSrcAlphaBlendOp 指令控制操作符,如BlendOp Sub(默认不指定是Add))

Blend命令

混合命令是当前输出的颜色值Src,和缓冲区里的颜色值Dst做数值运算来进行混合。命令配置主要影响这个运算公式,下面先看下这个运算公式是什么样的:

Src \* SrcFactor + Dst \* DstFactor

  • 这里的Src是当前输出的颜色值。
  • Dst是缓冲区里已有的颜色值。
  • SrcFactor是接下来命令配置时要改变的当前颜色值的影响变量因子。
  • DstFactor同样的是命令配置要改变缓冲区内的颜色值的影响因子。

这两个因子与颜色值相乘后相加就是新的要输出的颜色值。这里的相加是默认操作,由接下来的命令BlendOp来配置可修改。

上述公式是对颜色RGBA一起操作的配置。还有一种是分离颜色RGB和A单独控制的配置方式,与这个类似的公式:

Src \* SrcFactorA + Dst \* DstFactorA

混合的两种模板公式:

Blend SrcFactor DstFactor

Blend SrcFactor DstFactor, SrcFactorA DstFactorA

第一种配置方式直接配置RGBA,第二种将RGB和A分开配置。

  • 那么这些Factor因子可以配置成哪些内容:

    ‌基础混合因子‌

    因子名称 数学表达式 解释
    One 1 完全保留源或目标颜色值(常用于加法混合)‌
    Zero 0 忽略源或目标颜色值(常用于屏蔽颜色)‌
    SrcColor 源颜色的RGB值 使用片元着色器输出的RGB值作为混合因子‌
    SrcAlpha 源颜色的Alpha值 使用片元着色器的透明度值(如标准透明混合)‌
    DstColor 目标颜色的RGB值 使用帧缓冲区中已存在的RGB值(如乘法混合)‌
    DstAlpha 目标颜色的Alpha值 使用帧缓冲区中已存在的透明度值‌

    ‌反相混合因子‌

    因子名称 数学表达式 解释
    OneMinusSrcColor 1 - 源颜色RGB值 反相源颜色值(用于特殊效果叠加)‌
    OneMinusSrcAlpha 1 - 源Alpha值 反相源透明度(与SrcAlpha配合实现标准透明混合)‌
    OneMinusDstColor 1 - 目标颜色RGB值 反相目标颜色值(如屏幕空间发光效果)‌
    OneMinusDstAlpha 1 - 目标Alpha值 反相目标透明度值‌

    混合因子应用场景‌

    • 透明度混合 ‌:Blend SrcAlpha OneMinusSrcAlpha(源透明×源颜色 + (1-源透明)×目标颜色)‌
    • 加法发光 ‌:Blend One One(源颜色 + 目标颜色)‌
    • 乘法叠加 ‌:Blend DstColor Zero(源颜色×目标颜色)‌
    • 反相混合 ‌:Blend OneMinusDstColor OneMinusSrcColor(用于特殊遮罩效果)‌
  • BlendOp命令

    通过BlendOp修改混合计算方式,支持以下操作:

    • Add(默认):相加
    • Sub:源减去目标
    • RevSub:目标减去源
    • Min/Max:取最小/最大值‌
    glsl 复制代码
    BlendOp RevSub  
    Blend One One // 实现颜色相减效果‌

应用示例

能量护盾特效

  • 使用BlendOp RevSub结合Blend One One,实现护盾边缘对背景颜色的"扣除"效果,模拟能量场扭曲

    能量护盾特效通过反向减法混合(RevSub)实现背景颜色扣除,配合蜂窝纹理可产生能量场扭曲效果

    • EnergyShield.shader

      c 复制代码
      Shader "URP/EnergyShield" {
          Properties {
              _MainTex ("Shield Pattern", 2D) = "white" {}
              _EdgeColor ("Edge Color", Color) = (0.2,0.8,1,1)
          }
          SubShader {
              Tags { "RenderType"="Transparent" "Queue"="Transparent" }
              Blend One One
              BlendOp RevSub
              ZWrite Off
      
              HLSLINCLUDE
              #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
              ENDHLSL
      
              Pass {
                  HLSLPROGRAM
                  #pragma vertex vert
                  #pragma fragment frag
      
                  struct Attributes {
                      float4 positionOS : POSITION;
                      float2 uv : TEXCOORD0;
                  };
      
                  struct Varyings {
                      float4 positionCS : SV_POSITION;
                      float2 uv : TEXCOORD0;
                  };
      
                  TEXTURE2D(_MainTex);
                  SAMPLER(sampler_MainTex);
                  half4 _EdgeColor;
      
                  Varyings vert(Attributes IN) {
                      Varyings OUT;
                      OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
                      OUT.uv = IN.uv;
                      return OUT;
                  }
      
                  half4 frag(Varyings IN) : SV_Target {
                      half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                      return tex * _EdgeColor;
                  }
                  ENDHLSL
              }
          }
      }

粒子系统发光

  • 采用Blend One OneBlendOp Add叠加多层粒子颜色,避免传统混合导致的亮度衰减

    粒子系统采用加法混合(Add)确保多层粒子叠加时亮度线性累积,避免传统混合的亮度衰减问题

    • ParticleGlow.shader

      c 复制代码
      Shader "URP/ParticleGlow" {
          Properties {
              _MainTex ("Particle Texture", 2D) = "white" {}
              _Intensity ("Glow Intensity", Range(1,10)) = 3
          }
          SubShader {
              Tags { "RenderType"="Transparent" "Queue"="Transparent+100" }
              Blend One One
              BlendOp Add
              ZWrite Off
              Cull Off
      
              HLSLINCLUDE
              #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
              ENDHLSL
      
              Pass {
                  HLSLPROGRAM
                  #pragma vertex vert
                  #pragma fragment frag
      
                  struct Attributes {
                      float4 positionOS : POSITION;
                      float2 uv : TEXCOORD0;
                      half4 color : COLOR;
                  };
      
                  struct Varyings {
                      float4 positionCS : SV_POSITION;
                      float2 uv : TEXCOORD0;
                      half4 color : COLOR;
                  };
      
                  TEXTURE2D(_MainTex);
                  SAMPLER(sampler_MainTex);
                  float _Intensity;
      
                  Varyings vert(Attributes IN) {
                      Varyings OUT;
                      OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
                      OUT.uv = IN.uv;
                      OUT.color = IN.color * _Intensity;
                      return OUT;
                  }
      
                  half4 frag(Varyings IN) : SV_Target {
                      half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                      return tex * IN.color;
                  }
                  ENDHLSL
              }
          }
      }

透明水体渲染

  • 通过Blend SrcAlpha OneMinusSrcAlpha控制折射/反射强度,配合BlendOp Min保留深度最小的泡沫高光

    水体渲染使用Min混合操作保留最小深度值,配合标准Alpha混合实现透明折射效果

    • WaterSurface.shader

      c 复制代码
      Shader "URP/WaterSurface" {
          Properties {
              _MainTex ("Water Texture", 2D) = "white" {}
              _NormalMap ("Normal Map", 2D) = "bump" {}
              _FoamTex ("Foam Texture", 2D) = "white" {}
          }
          SubShader {
              Tags { "RenderType"="Transparent" "Queue"="Transparent" }
              Blend SrcAlpha OneMinusSrcAlpha
              BlendOp Min
              ZWrite Off
      
              HLSLINCLUDE
              #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
              #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
              ENDHLSL
      
              Pass {
                  HLSLPROGRAM
                  #pragma vertex vert
                  #pragma fragment frag
      
                  struct Attributes {
                      float4 positionOS : POSITION;
                      float2 uv : TEXCOORD0;
                      float3 normalOS : NORMAL;
                      float4 tangentOS : TANGENT;
                  };
      
                  struct Varyings {
                      float4 positionCS : SV_POSITION;
                      float2 uv : TEXCOORD0;
                      float3 normalWS : TEXCOORD1;
                      float3 viewDirWS : TEXCOORD2;
                  };
      
                  TEXTURE2D(_MainTex);
                  TEXTURE2D(_NormalMap);
                  TEXTURE2D(_FoamTex);
                  SAMPLER(sampler_MainTex);
      
                  Varyings vert(Attributes IN) {
                      Varyings OUT;
                      float3 positionWS = TransformObjectToWorld(IN.positionOS.xyz);
                      OUT.positionCS = TransformWorldToHClip(positionWS);
                      OUT.uv = IN.uv;
                      OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS);
                      OUT.viewDirWS = GetWorldSpaceViewDir(positionWS);
                      return OUT;
                  }
      
                  half4 frag(Varyings IN) : SV_Target {
                      half4 water = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                      water.a = 0.7;
                      half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_MainTex, IN.uv));
                      half foam = SAMPLE_TEXTURE2D(_FoamTex, sampler_MainTex, IN.uv).r;
                      return min(water, foam.xxxx);
                  }
                  ENDHLSL
              }
          }
      }

【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

相关推荐
在路上看风景8 小时前
15. 纹理尺寸是4的倍数
unity
AT~10 小时前
unity 使用Socket和protobuf实现网络连接
unity·游戏引擎
怣疯knight16 小时前
Cocos creator判断节点是否能用的方法
unity·cocos2d
tealcwu17 小时前
Google Play的Keystore不可用时的解决方法
unity
呼呼突突17 小时前
Unity使用TouchSocket的RPC
unity·rpc·游戏引擎
qq 180809511 天前
从零构建一个多目标多传感器融合跟踪器
unity
平行云1 天前
实时云渲染支持在网页上运行UE5开发的3A大作Lyra项目
unity·云原生·ue5·webgl·虚拟现实·实时云渲染·像素流送
鹏飞于天1 天前
Shader compiler initialization error: Failed to read D3DCompiler DLL file
unity
wonder135791 天前
UGUI重建流程和优化
unity·游戏开发·ugui
那个村的李富贵2 天前
Unity打包Webgl后 本地运行测试
unity·webgl