Unity自定义后处理——Bloom效果

文章介绍了Bloom效果,即一种让游戏画面产生发光感的技术,它基于模糊效果。Bloom通过计算和叠加模糊后的图像来增强亮部,通过BrightRange控制发光范围。文章提供了C#代码和Shader实现细节,包括模糊处理、亮度阈值计算以及最终的Bloom添加步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大家好,我是阿赵。
  继续介绍屏幕后处理效果,这一期讲一下Bloom效果。

一、Bloom效果介绍

在这里插入图片描述

还是拿这个模型作为背景。
在这里插入图片描述

Bloom效果,就是一种全屏泛光的效果,让模型和特效有一种真的在发光的感觉。

在这里插入图片描述
在这里插入图片描述

根据参数不一样,可以做出不同的发光效果。

二、原理介绍

  之前在介绍模糊效果的时候说过,Bloom效果也是基于模糊效果制作的。
  Bloom的原理很简单,先用模糊处理,算出一张模糊后的图片,然后把这张图片的RGB和原始屏幕图片的RGB相加就可以了。由于是RGB颜色叠加,所以本身图片颜色越接近白色的地方,越容易爆掉,所以越接近白色的地方发光的感觉就越明显。如果想Bloom的效果更爆一点,就自己对模糊后的图片进一步做处理吧,最常见的手段就是先乘后加,或者Power后再相加也可以
在这里插入图片描述

实现很简单,在原来的模糊效果代码上面,我们应该再加多一个叠加的Shader就可以了。

half4 frag (v2f_img i) : SV_Target
{
    half4 col = tex2D(_MainTex, i.uv);
	half4 brightCol = tex2D(_brightTex, i.uv);
	col.rgb += brightCol.rgb;
    return col;
}

  我这里在处理模糊之前,先加多了一个BrightRange的计算。这是为了控制一下发光的范围。BrightRange的算法很简单,先取原始图片的rgb三个通道颜色值最大的通道的值,然后用这个最大值减去一个我们指定的值。最后把原图的rgb颜色乘以这个值就可以了。

fixed4 frag (v2f_img i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
	float br = max(max(col.r, col.g), col.b);
	br = max(0, (br - _BrightCut)) / max(br, 0.00001);
	col.rgb *= br;
    return col;
}

通过这个值,我们就可以更好的控制发光的范围
三、源码
1、C#部分

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BloomCtrl : MonoBehaviour
{
    private Material blurMat;
    private Material brightMat;
    private Material bloomMat;
    public bool isBlur = false;
    [Range(0, 4)]
    public float blurSize = 0;
    [Range(-3, 3)]
    public float blurOffset = 1;
    [Range(1, 3)]
    public int blurType = 3;
    [Range(0, 1)]
    public float brightCut = 0.5f;
    void Start()
    {

    }


    void Update()
    {

    }

    private Material GetBlurMat(int bType)
    {
        if (bType == 1)
        {
            return new Material(Shader.Find("Hidden/AzhaoBoxBlur"));
        }
        else if (bType == 2)
        {
            return new Material(Shader.Find("Hidden/AzhaoGaussianBlur"));
        }
        else if (bType == 3)
        {
            return new Material(Shader.Find("Hidden/AzhaoKawaseBlur"));
        }
        else
        {
            return null;
        }
    }

    private void ReleaseRT(RenderTexture rt)
    {
        if (rt != null)
        {
            RenderTexture.ReleaseTemporary(rt);
        }
    }

    private bool CheckNeedCreateBlurMat(Material mat, int bType)
    {
        if (mat == null)
        {
            return true;
        }
        if (mat.shader == null)
        {
            return true;
        }
        if (bType == 1)
        {
            if (mat.shader.name != "Hidden/AzhaoBoxBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else if (bType == 2)
        {
            if (mat.shader.name != "Hidden/AzhaoGaussianBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else if (bType == 3)
        {
            if (mat.shader.name != "Hidden/AzhaoKawaseBlur")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }
    private void BlurFun(RenderTexture source, RenderTexture destination, float blurTime, int bType, float offset)
    {
        if (CheckNeedCreateBlurMat(blurMat, bType) == true)
        {
            blurMat = GetBlurMat(bType);
        }
        if (blurMat == null || blurMat.shader == null || blurMat.shader.isSupported == false)
        {
            return;
        }
        blurMat.SetFloat("_BlurOffset", offset);
        float width = source.width;
        float height = source.height;
        int w = Mathf.FloorToInt(width);
        int h = Mathf.FloorToInt(height);
        RenderTexture rt1 = RenderTexture.GetTemporary(w, h);
        RenderTexture rt2 = RenderTexture.GetTemporary(w, h);
        Graphics.Blit(source, rt1);
        for (int i = 0; i < blurTime; i++)
        {
            ReleaseRT(rt2);
            width = width / 2;
            height = height / 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            rt2 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt1, rt2, blurMat, 0);
            width = width / 2;
            height = height / 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            ReleaseRT(rt1);
            rt1 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt2, rt1, blurMat, 1);
        }
        for (int i = 0; i < blurTime; i++)
        {
            ReleaseRT(rt2);
            width = width * 2;
            height = height * 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            rt2 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt1, rt2, blurMat, 0);
            width = width * 2;
            height = height * 2;
            w = Mathf.FloorToInt(width);
            h = Mathf.FloorToInt(height);
            ReleaseRT(rt1);
            rt1 = RenderTexture.GetTemporary(w, h);
            Graphics.Blit(rt2, rt1, blurMat, 1);
        }
        Graphics.Blit(rt1, destination);
        ReleaseRT(rt1);
        rt1 = null;
        ReleaseRT(rt2);
        rt2 = null;
        return;
    }
    private bool BrightRangeFun(RenderTexture source, RenderTexture destination)
    {
        if (brightMat == null)
        {
            brightMat = new Material(Shader.Find("Hidden/BrightRange"));
        }
        if (brightMat == null || brightMat.shader == null || brightMat.shader.isSupported == false)
        {
            return false;
        }
        brightMat.SetFloat("_BrightCut", brightCut);
        Graphics.Blit(source, destination, brightMat);
        return true;

    }

    private bool BloomAddFun(RenderTexture source, RenderTexture destination, RenderTexture brightTex)
    {
        if (bloomMat == null)
        {
            bloomMat = new Material(Shader.Find("Hidden/AzhaoBloom"));
        }
        if (bloomMat == null || bloomMat.shader == null || bloomMat.shader.isSupported == false)
        {
            return false;
        }
        bloomMat.SetTexture("_brightTex", brightTex);
        Graphics.Blit(source, destination, bloomMat);
        return true;
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (isBlur == true)
        {
            RenderTexture finalRt = source;
            RenderTexture rt2 = RenderTexture.GetTemporary(source.width, source.height);
            RenderTexture rt3 = RenderTexture.GetTemporary(source.width, source.height);
            BrightRangeFun(source, rt2);
            if (blurSize > 0)
            {
                BlurFun(rt2, rt3, blurSize, blurType, blurOffset);
                BloomAddFun(source, finalRt, rt3);
            }
            Graphics.Blit(finalRt, destination);
            ReleaseRT(finalRt);
            ReleaseRT(rt2);
            ReleaseRT(rt3);

        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

2、Shader
  模糊shader就不重复了,参考之前的文章
这里提供一下Bloom和BrightRange的shader
1.Bloom

Shader "Hidden/AzhaoBloom"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_brightTex("BrightTex",2D) = "black"
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"



            sampler2D _MainTex;
		sampler2D _brightTex;

			half4 frag (v2f_img i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);
				half4 brightCol = tex2D(_brightTex, i.uv);
				col.rgb += brightCol.rgb;
                return col;
            }
            ENDCG
        }
    }
}

2.BrightRange

Shader "Hidden/BrightRange"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_BrightCut("LightVal",Range(0,1)) = 0.5
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"



            sampler2D _MainTex;
			float _BrightCut;

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
				float br = max(max(col.r, col.g), col.b);
				br = max(0, (br - _BrightCut)) / max(br, 0.00001);
				col.rgb *= br;
                return col;
            }
            ENDCG
        }
    }
}
### Unity WebGL 后处理效果实现方法 在 Unity WebGL 平台中,后处理效果可以通过多种方式来实现。以下是关于如何在 Unity WebGL 上实现后处理效果的具体说明: #### 1. 使用 Post Processing Stack (V2 或 V3) Unity 提供了一个官方的后处理库——Post Processing Stack,它支持各种高级视觉效果,如景深、抗锯齿、色调映射等。此工具可以很好地兼容 WebGL 构建目标。 要启用后处理效果,请按照以下步骤操作: - 首先,在项目中导入 **Post Processing Package**[^5]。 - 创建一个 `Volume` 对象并将其附加到摄像机上。 - 在 Volume 中配置所需的后处理效果(例如 Bloom、Chromatic Aberration 等)。 需要注意的是,某些复杂的后处理效果可能会显著增加 WebGL 的性能开销,因此建议优化着色器代码以减少渲染负担。 ```csharp using UnityEngine; using UnityEngine.Rendering.PostProcessing; public class SetupPostProcess : MonoBehaviour { void Start() { var volume = GetComponent<Volume>(); if (!volume) Debug.LogError("No Volume component found on the camera."); // Add or modify post-processing effects here. } } ``` #### 2. 自定义 Shader 和 Material 实现特定效果 如果需要更灵活的效果控制,则可以选择编写自定义着色器和材质脚本。通过修改顶点/片段着色器逻辑,开发者能够完全掌控图像输出过程中的每一帧数据流。 下面展示了一种简单的屏幕模糊效果实现方案: ```hlsl Shader "Custom/SimpleBlur" { Properties { _MainTex ("Texture", 2D) = "white" {} _BlurSize ("Blur Size", Float ) = 0.01 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag uniform sampler2D _MainTex; float _BlurSize; fixed4 frag(v2f_img i):COLOR { half4 sum = tex2D(_MainTex, i.uv); for(int j=-2; j<=2; ++j){ for(int k=-2; k<=2; ++k){ sum += tex2D(_MainTex, i.uv + float2(j,k)*_BlurSize)/9.f; } } return sum / 25.; } ENDCG } } } ``` 将上述 HLSL 文件保存为 `.shader` 扩展名,并应用至新的材质球上即可完成设置。 #### 3. 解决潜在问题 当尝试运行带有后处理特效的游戏时,可能遇到一些常见障碍,比如跨域请求失败或者包体积过大等问题。这些问题可通过调整构建参数或引入额外技术手段加以规避。 对于跨域访问限制,推荐采用服务端 CORS 政策开放接口权限[^4];而对于过大的发布尺寸,则可考虑精简资源依赖项或将静态素材托管于 CDN 上[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值