在游戏开发中,Bloom效果是提升画面表现力的重要手段之一。它能让场景中的高亮区域产生柔和的辉光效果,营造出梦幻或科幻的氛围。对于使用Unity通用渲染管线(URP)的开发者来说,理解如何在不依赖Post-Processing Stack v2等第三方插件的情况下实现Bloom效果,是一项极具价值的技能。
URP的后处理系统与内置渲染管线有着显著差异。在URP中,后处理效果通过ScriptableRendererFeature和ScriptableRenderPass来实现,这为开发者提供了更灵活的扩展方式。
URP的后处理流程主要包含以下几个核心组件:
与内置管线使用OnRenderImage不同,URP的后处理需要在渲染管线中明确插入自定义的Render Pass。这种设计使得渲染流程更加清晰,也便于性能优化。
要在URP中添加Bloom效果,首先需要创建一个继承自ScriptableRendererFeature的类:
csharp复制using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class BloomFeature : ScriptableRendererFeature
{
[System.Serializable]
public class BloomSettings
{
public Material bloomMaterial;
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
[Range(0, 4)] public float intensity = 1.0f;
[Range(0, 3)] public float threshold = 1.0f;
[Range(1, 8)] public int iterations = 4;
}
public BloomSettings settings = new BloomSettings();
private BloomPass bloomPass;
public override void Create()
{
bloomPass = new BloomPass(settings);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.bloomMaterial == null) return;
renderer.EnqueuePass(bloomPass);
}
}
Bloom效果的实现主要分为三个步骤:亮度提取、模糊处理和最终合成。
亮度提取是Bloom效果的第一步,它决定了哪些像素会参与辉光计算。以下是URP兼容的亮度提取Shader:
hlsl复制Shader "Hidden/BrightnessExtract"
{
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _Threshold;
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
Varyings Vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 Frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
half brightness = max(color.r, max(color.g, color.b));
half contribution = max(0, brightness - _Threshold);
color.rgb *= contribution;
return color;
}
ENDHLSL
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
}
}
模糊处理是Bloom效果中最耗性能的部分。在URP中,我们可以使用双Pass的Kawase模糊算法:
csharp复制public class BloomPass : ScriptableRenderPass
{
private Material bloomMaterial;
private RenderTargetIdentifier source;
private RenderTargetHandle tempTexture1;
private RenderTargetHandle tempTexture2;
private BloomFeature.BloomSettings settings;
public BloomPass(BloomFeature.BloomSettings settings)
{
this.settings = settings;
tempTexture1.Init("_TempBloomTexture1");
tempTexture2.Init("_TempBloomTexture2");
renderPassEvent = settings.renderPassEvent;
}
public void Setup(RenderTargetIdentifier source)
{
this.source = source;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get("Bloom Effect");
RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
opaqueDesc.depthBufferBits = 0;
// 亮度提取
cmd.GetTemporaryRT(tempTexture1.id, opaqueDesc, FilterMode.Bilinear);
cmd.SetGlobalFloat("_Threshold", settings.threshold);
cmd.Blit(source, tempTexture1.Identifier(), settings.bloomMaterial, 0);
// 模糊处理
for (int i = 0; i < settings.iterations; i++)
{
cmd.GetTemporaryRT(tempTexture2.id, opaqueDesc, FilterMode.Bilinear);
cmd.SetGlobalFloat("_BlurOffset", i * 0.5f + 0.5f);
cmd.Blit(tempTexture1.Identifier(), tempTexture2.Identifier(), settings.bloomMaterial, 1);
cmd.ReleaseTemporaryRT(tempTexture1.id);
cmd.GetTemporaryRT(tempTexture1.id, opaqueDesc, FilterMode.Bilinear);
cmd.SetGlobalFloat("_BlurOffset", i * 0.5f + 1.0f);
cmd.Blit(tempTexture2.Identifier(), tempTexture1.Identifier(), settings.bloomMaterial, 1);
cmd.ReleaseTemporaryRT(tempTexture2.id);
}
// 最终合成
cmd.SetGlobalTexture("_BloomTexture", tempTexture1.Identifier());
cmd.SetGlobalFloat("_Intensity", settings.intensity);
cmd.Blit(source, source, settings.bloomMaterial, 2);
cmd.ReleaseTemporaryRT(tempTexture1.id);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
模糊处理后的图像需要与原始图像进行合成:
hlsl复制Shader "Hidden/BloomComposite"
{
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_BloomTexture);
SAMPLER(sampler_BloomTexture);
float _Intensity;
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
Varyings Vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 Frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
half4 bloom = SAMPLE_TEXTURE2D(_BloomTexture, sampler_BloomTexture, input.uv);
color.rgb += bloom.rgb * _Intensity;
return color;
}
ENDHLSL
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
}
}
Bloom效果可能会对性能产生显著影响,特别是在移动设备上。以下是几种有效的优化方法:
降低Bloom处理的分辨率可以大幅减少计算量:
csharp复制// 在BloomPass.Execute方法中修改
opaqueDesc.width /= 2;
opaqueDesc.height /= 2;
模糊迭代次数与性能消耗成正比,建议根据目标平台调整:
| 平台类型 | 推荐迭代次数 | 性能影响 |
|---|---|---|
| 高端PC/主机 | 4-6 | 中等 |
| 中端PC | 3-4 | 低到中等 |
| 移动设备 | 2-3 | 低 |
可以跳过对暗部区域的模糊处理:
hlsl复制// 在亮度提取Shader中修改
half contribution = smoothstep(_Threshold, _Threshold + 0.1, brightness);
对于支持Compute Shader的平台,可以使用更高效的实现:
hlsl复制#pragma kernel BrightnessExtract
#pragma kernel HorizontalBlur
#pragma kernel VerticalBlur
#pragma kernel Composite
RWTexture2D<float4> Result;
Texture2D<float4> SourceTexture;
float _Threshold;
float _Intensity;
float _BlurOffset;
[numthreads(8,8,1)]
void BrightnessExtract (uint3 id : SV_DispatchThreadID)
{
float4 color = SourceTexture[id.xy];
float brightness = max(color.r, max(color.g, color.b));
float contribution = max(0, brightness - _Threshold);
Result[id.xy] = color * contribution;
}
通过叠加一张脏迹纹理,可以模拟真实镜头效果:
hlsl复制TEXTURE2D(_DirtTexture);
SAMPLER(sampler_DirtTexture);
float _DirtIntensity;
// 在合成Shader中添加
half4 dirt = SAMPLE_TEXTURE2D(_DirtTexture, sampler_DirtTexture, input.uv);
color.rgb += bloom.rgb * _Intensity * dirt.rgb * _DirtIntensity;
为Bloom边缘添加微妙的色差,增强视觉效果:
hlsl复制float2 uvR = input.uv + float2(0.002, 0.002);
float2 uvB = input.uv - float2(0.002, 0.002);
half4 bloomR = SAMPLE_TEXTURE2D(_BloomTexture, sampler_BloomTexture, uvR);
half4 bloomB = SAMPLE_TEXTURE2D(_BloomTexture, sampler_BloomTexture, uvB);
color.r += bloomR.r * _Intensity;
color.g += bloom.g * _Intensity;
color.b += bloomB.b * _Intensity;
根据场景亮度自动调整Bloom参数:
csharp复制float sceneLuminance = CalculateSceneLuminance();
settings.threshold = Mathf.Lerp(0.5f, 1.5f, sceneLuminance);
settings.intensity = Mathf.Lerp(0.5f, 2.0f, 1 - sceneLuminance);