【TA-霜狼_may-《百人计划》】图形4.1 Bloom算法 游戏中的辉光效果实现

4.1.1 Bloom算法介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
模拟摄像机的一种图像效果;

实现思路

在这里插入图片描述

前置知识

HDR与LDR
HDR使得亮度提取步骤更加方便准确。

高斯模糊
利用高斯核对图像进行卷积。

高斯核计算方法:
在这里插入图片描述
计算量:可以假想图像中的任意一个像素点,它将会出现在高斯核中上的每个位置,也就是NN,而这样的点总共有WH(图像的长宽)个,所以总共需要的计算是NNWH;
如果将二维高斯核替换为两个一维,则上面步骤中的N
N变为2*N,当高斯核较大时可以起到很好的降低运算量的效果。
在这里插入图片描述
并且一维高斯核存在对称性,即在存储的时候可以只存储一半的权值。

4.1.2 Bloom效果实现

用于相机的csharp脚本,

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

public class MyBloom : PostEffectsBase
{
    // 首先定义使用的shader和材质
    public Shader bloomShader;
    public Material bloomMaterial = null;
    
    public Material material{
        get{
            // 调用PostEffectsBase基类中的函数,检查shader并且创建材质
            bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
            return bloomMaterial;
        }
    }

    // 定义shader中的参数
    // 高斯模糊迭代次数
    [Range(0,4)] public int iterations = 3;
    // 高斯模糊范围
    [Range(0.2f, 3.0f)] public float blurSpread = 0.6f;
    // 下采样,缩放系数
    [Range(1, 8)] int downSample = 2;
    // 高亮提取阈值
    [Range(0.0f, 4.0f)] public float luminanceThreshold = 0.6f;

    // 调用OnRenderImage函数来实现Bloom
    private void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if(material != null){
            // 传入阈值
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);

            // src.width 和 hight 代表屏幕图像的宽度和高度
            int rtW = src.width / downSample;
            int rtH = src.height / downSample;

            // 创建一块分辨率小于原屏幕的缓冲区:buffer0
            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW,rtH,0);
            buffer0.filterMode = FilterMode.Bilinear;

            // 用Blit方法调用shader中的第一个pass,提取图像中较亮的区域
            // 结果存在buffer0中
            Graphics.Blit(src, buffer0, material, 0);

            // 迭代进行高斯模糊
            for (int i = 0; i < iterations; i++){
                // 传入模糊半径
                material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

                // 定义第二个缓冲区:buffer1
                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                // 用Blit方法调用shader中的第二个pass,进行竖直方向上的高斯模糊
                // 结果存在buffer1中
                Graphics.Blit(buffer0, buffer1, material, 1);

                // 释放缓冲区buffer0,将buffer1中的数值赋值给buffer0,并重新分配buffer1
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                // 用Blit方法调用shader中的第三个pass,进行水平方向上的高斯模糊
                // 结果存在buffer1中
                Graphics.Blit(buffer0, buffer1, material, 2);

                // 再次调换顺序
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;

                // 最终高斯模糊的结果存储在buffer0中
            }

            // 将完成高斯模糊的结果buffer0传递给材质中的_Bloom微粒属性
            material.SetTexture("_Bloom", buffer0);

            // 用Blit方法调用shader中的第四个pass,完成混合
            // dest为最终输出
            Graphics.Blit(src, dest, material, 3);

            // 最后别忘记释放临时缓冲区
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else{
            Graphics.Blit(src, dest);
        }
    }
}

对调用Pass那一部分的代码进行了修改,没有必要将数据在buffer0,和buffer1中倒来倒去,徒增功耗,修改如下,测试后效果不变(经过进一步测试发现,重新为buffer0那一步赋值也是没有必要的,直接两句话就可以搞定了)(再更新,buffer1要手动释放,不然每次都重新申请就会爆内存!!):

	// 结果存在buffer1中
	Graphics.Blit(buffer0, buffer1, material, 1);
	
	// 将buffer0作为接收用的缓存
	// buffer0 = RenderTexture.GetTemporary(rtW,rtH,0);
	
	// 利用buffer1中的数据进行水平方向上的高斯模糊,结果保存在buffer0中
	Graphics.Blit(buffer1, buffer0, material, 2);
	
	// 替换以下代码
	// // 释放缓冲区buffer0,将buffer1中的数值赋值给buffer0,并重新分配buffer1
	// RenderTexture.ReleaseTemporary(buffer0);
	// buffer0 = buffer1;
	// buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
	
	// // 用Blit方法调用shader中的第三个pass,进行水平方向上的高斯模糊
	// // 结果存在buffer1中
	// Graphics.Blit(buffer0, buffer1, material, 2);
	
	// // 再次调换顺序
	// RenderTexture.ReleaseTemporary(buffer0);
	// buffer0 = buffer1;
	
	// // 最终高斯模糊的结果存储在buffer0中

bloom shader:

Shader "Custom/MyBloomShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Bloom ("Bloom (RGB)", 2D) = "black" {}
        _LuminanceThreshold("Luminance Threshold", Float) = 0.5
        _BlurSize("Blur Size", Float) = 1.0
    }
    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        sampler2D _Bloom;
        float _LuminanceThreshold;
        float _BlurSize;

        // 用于第一个pass
        // 输出结构体
        struct v2fExtractBright{
            float4 pos : SV_POSITION;
            half2 uv : TEXCOORD0;
        };

        // 顶点着色器
        v2fExtractBright vertExtractBright(appdata_img v){
            v2fExtractBright o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            return o;
        }

        // 明度计算公式
        fixed luminance(fixed4 color){
            return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
        }

        // 片元着色器提取高亮区域
        fixed4 fragExtractBright(v2fExtractBright i): SV_TARGET{
            // 采样贴图
            fixed4 c = tex2D(_MainTex, i.uv);
            // 利用clamp函数将亮度值截取在0,1范围内
            fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);

            // 将val与原图采样得到像素值相乘,得到提取后的亮部区域
            return c * val;
        }

        // 第二、三个pass
        struct v2fBlur{
            float4 pos: SV_POSITION;
            half2 uv[5]: TEXCOORD0;
            // 此处定义大小为5的数组来存储5个纹理坐标
            // 用于卷积核大小为5x5的二维高斯核可以拆分为两个大小为5的1维高斯核
            // uv[0]存储了当前的采样纹理
            // uv[1-4]为高斯模糊中对邻域采样时使用的纹理坐标
        };
        
        // 在顶点着色器中计算竖直方向进行高斯模糊的uv坐标
        v2fBlur vertBlurVertical(appdata_img v){
            v2fBlur o;
            o.pos = UnityObjectToClipPos(v.vertex);
            half2 uv = v.texcoord;

            o.uv[0] = uv;

            o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
            o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
            o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
            o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
            return o;
        }

        // 在顶点着色器中计算竖直方向进行高斯模糊的uv坐标
        v2fBlur vertBlurHorizontal(appdata_img v){
            v2fBlur o;
            o.pos = UnityObjectToClipPos(v.vertex);
            half2 uv = v.texcoord;

            o.uv[0] = uv;

            o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
            o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
            o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
            o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
            return o;
        }

        // 片元着色器中进行高斯模糊
        fixed4 fragBlur(v2fBlur i): SV_TARGET{
            // 利用二维高斯核的可分离性,分离得到具有对称性的一维高斯核
            // 因此只需要在数组中存放三个高斯权重即可
            float weight[3]= {0.4026, 0.2442, 0.0545};
            
            // 0,0位置的值直接乘上当前位置的权重保存下来
            fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];

            // 进行卷积运算,根据对称性完成两次循环
            for(int it = 1; it < 3; it++){
                sum += tex2D(_MainTex, i.uv[it * 2 - 1]).rgb * weight[it];
                sum += tex2D(_MainTex, i.uv[it * 2]).rgb * weight[it];
            }

            return fixed4(sum, 1.0);
        }

        // 第四个pass使用
        struct v2fBloom{
            float4 pos : SV_POSITION;
            half4 uv : TEXCOORD0;
        };

        // 实现效果顶点着色器
        v2fBloom vertBloom(appdata_img v){
            v2fBloom o;
            o.pos = UnityObjectToClipPos(v.vertex);

            o.uv.xy = v.texcoord;
            o.uv.zw = v.texcoord;

            // 平台差异化处理
            #if UNITY_UV_STARTS_AT_TOP
            if (_MainTex_TexelSize.y < 0.0)
                o.uv.w = 1.0 - o.uv.w;
            #endif
            return o;
        }

        fixed4 fragBloom(v2fBloom i): SV_TARGET{
            return tex2D(_MainTex,i.uv.xy)+ tex2D(_Bloom, i.uv.zw);
        }
        ENDCG

        ZTest Always
        Cull Off
        ZWrite Off

        Pass{
            CGPROGRAM
            #pragma vertex vertExtractBright
            #pragma fragment fragExtractBright
            ENDCG
        }

        Pass{
            CGPROGRAM
            #pragma vertex vertBlurVertical
            #pragma fragment fragBlur
            ENDCG
        }

        Pass{
            CGPROGRAM
            #pragma vertex vertBlurHorizontal
            #pragma fragment fragBlur
            ENDCG
        }

        Pass{
            CGPROGRAM
            #pragma vertex vertBloom
            #pragma fragment fragBloom
            ENDCG
        }
    }
    FallBack Off
}

4.1.3 Bloom效果应用

部分区域自发光,烟花,阳光,GodRay
在这里插入图片描述
在这里插入图片描述
GodRay仔细看可以看到光柱的感觉,(基于径向模糊的后处理)
在这里插入图片描述
结合Tonemapping实现更好的光线效果:
在这里插入图片描述

作业

原图未使用Bloom效果:
在这里插入图片描述
开启bloom效果:
在这里插入图片描述
这里注意要手动释放buffer1,教程里好像只释放了buffer0,当在测试时会发现buffer在疯狂增长,最后爆内存。

有关局部bloom:

想要利用Alpha进行调控局部bloom,但是一开始发现始终读不到屏幕的alpha值,debug了半天才想到,在物体shader的片元着色器中把A通道的值默认设置为1了,所以屏幕后处理的过程中alpha值始终为1,无法起到alpha蒙版的作用。
对原有的shader进行修改,达到了部分区域bloom的效果。
在这里插入图片描述
下图右侧有一条分割线,右半部分没有bloom,左半部分有bloom:
在这里插入图片描述
其中透明蒙版利用ps进行制作,如下:
在这里插入图片描述
代码部分对pass3的fragBloom进行修改,并添加判断函数:

		fixed4 bloomMask0(fixed4 src, fixed4 bloomColor){
            // 透明区域src.a为0
            if(1 > 1-src.a){
                return bloomColor;
            }else{
                return src;
            }
        }

        fixed4 fragBloom(v2fBloom i): SV_TARGET{
            fixed4 originalImg = tex2D(_MainTex, i.uv.xy);
            fixed4 bloomResult = tex2D(_MainTex,i.uv.xy)+ tex2D(_Bloom, i.uv.zw);
            return bloomMask0(originalImg, bloomResult);
        }

作业部分的内容主要照着这位大佬做的,通透。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zczplus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值