Unity3D分层的Bloom(泛光)效果(二)
发布于 3 年前 作者 houyan 2457 次浏览 来自 分享

上一节实现了分层的泛光效果后还是有坑没有填完,如果吊灯前面有物体挡着的话,我们会看到这个物体上会出现吊灯的泛光效果。如图,我们在吊灯的前方加一个箱子,可以看到箱子上有吊灯的泛光效果

目前我能想到的解决办法有两种:1.使用深度,比较主摄像机与新建的摄像机在某个片元上的深度是否一致或者差异很小;2.比较主摄像机与新建的摄像机在某个片元上的颜色或者亮度是否差异很小。如果它们得到的结果是深度或者颜色差异很小,我们就可以在该片元上将从主纹理上采样得到的颜色与从光亮区域采样得到的颜色混合,否则只显示从主纹理上采样得到的颜色。

方法1和2都可以达到解决上面办法的目的,但是2会引入其它的问题:如果吊灯前面的遮挡物与灯的颜色一样,那么在遮挡物前还是会出现泛光效果。因此我使用第一种方法来解决遮挡物上出现泛光的问题。本节的代码与上节的内容相差不大。在Bloom.shader中我们在fragBloom片元着色器中添加了对主深度纹理与最后渲染的摄像机(该例子中为currBloomCamera)的深度纹理的采样,将采样得到的颜色转换为深度值(使用SAMPLE_DEPTH_TEXTURE(texture, uv)来实现的上面两步),此时该深度值为非线性的,我们再将该值转换为线性的值(使用Linear01Depth(value)实现),比较这两个值是否大于某个常数,如果大于这个常数则认为灯光前面有其它物体遮挡。下面是shader代码(因为排版与代码复制过来会有问题,所以后面会给出代码截图):

Shader "Unlit/Bloom"
{
    Properties
    {
        _MainTex("输入的渲染纹理"2D) = "white" {}
        _Bloom("高斯模糊后的较亮区域"2D) = "black" {}
        _LuminanceThreshold("用于提取较亮区域的阈值", Float) = 0.5
        _BlurSize("用于控制不同迭代之间的高斯模糊的模糊区域范围", Float) = 1.0
    }

    SubShader
    {
        CGINCLUDE

        #include "UnityCG.cginc"

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

        sampler2D _CameraDepthTexture;          // 主深度纹理
        sampler2D _LastCameraDepthTexture;      // 最后渲染的摄像机(该例子中为bloomCamera)的深度纹理

        struct v2f
        {
            float4 pos : SV_POSITION;
            half2 uv : TEXCOORD0;
        };

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

            return o;
        }

        // 将传入的颜色转换为亮度
        fixed luminance(fixed4 color)
        {
            return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
        }

        // 片元着色器
        fixed4 fragExtractBright(v2f i) : SV_Target
        {
            fixed4 c = tex2D(_MainTex, i.uv);
            fixed val = step(_LuminanceThreshold, luminance(c));

            return c * val;
        }

        struct v2fBloom
        {
            float4 pos : SV_POSITION;
            half4 uv : TEXCOORD0;
            half2 uv_depth : TEXCOORD1;
        };

        // 混合高斯模糊后的光亮区域纹理与原纹理的顶点着色器
        v2fBloom vertBloom(appdata_img v)
        {
            v2fBloom o;

            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv.xy = v.texcoord;
            o.uv.zw = v.texcoord;
            o.uv_depth = v.texcoord;

            #if UNITY_UV_STARTS_AT_TOP
            if (_MainTex_TexelSize.y < 0.0)
            {
                o.uv.w = 1.0 - o.uv.w;
                o.uv_depth.y = 1.0 - o.uv_depth.y;
            }
            #endif

            return o;
        }

        // 混合高斯模糊后的光亮区域纹理颜色与主纹理颜色的片元着色器
        fixed4 fragBloom(v2fBloom i) : SV_Target
        {
            // 获取主深度纹理,采样并将深度转换为线性的值
            float linearDepth1 = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
            // 获取最后渲染的摄像机(该例子中为bloomCamera)的深度纹理,采样并将深度转换到线性的值
            float linearDepth2 = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_LastCameraDepthTexture, i.uv_depth));
            // 是否有近似的深度值 step(a, x) => return x >= a ? 1 : 0;
            fixed depthValueIsSimilar = step(abs(linearDepth1 - linearDepth2), 0.0001);
            return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.zw) * depthValueIsSimilar;
        }

        ENDCG

        ZTest Always Cull Off ZWrite Off

        Pass        // Pass0 提取光亮区域
        {
            CGPROGRAM 

            #pragma vertex vertExtractBright        // 指定该Pass的顶点着色器
            #pragma fragment fragExtractBright      // 指定该Pass的片元着色器

            ENDCG
        }

        UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"     // Pass1 对纹理进行纵向的高斯模糊的处理

        UsePass "Unlit/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"   // Pass2 对纹理进行横向的高斯模糊的处理 

        Pass        // Pass3 将进行过高斯模糊的光亮区域与原纹理混合
        {
            CGPROGRAM

            #pragma vertex vertBloom        // 指定该Pass的顶点着色器
            #pragma fragment fragBloom      // 指定该Pass的片元着色器

然后修改Bloom.cs代码,这里面的代码同样修改不大,我们只需要在摄像机上设置我们需要深度纹理就可以了(camera.depthTextureModule = DepthTextureModule.Depth)(因为排版与代码复制过来会有问题,所以后面会给出代码截图)

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

namespace Effect
{
    public class Bloom : PostEffectsBase
    {
        [Header("泛光效果的着色器")]
        [SerializeField]
        private Shader bloomShader;

        [Header("泛光摄像机要在哪些层级中取亮度区域")]
        [SerializeField]
        private int[] CullingLayer;

        /// 
        /// 泛光效果的材质
        /// 
        private Material bloomMaterial = null;

        /// 
        /// 泛光效果的材质
        /// 
        public Material material
        {
            get
            {
                bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
                return bloomMaterial;
            }
        }

        [Header("高斯模糊的迭代次数,次数越多越模糊,但性能越低")]
        [SerializeField]
        [Range(0, 10)]
        private int iterations = 3;

        [Header("模糊范围(采样的距离的倍数)")]
        [SerializeField]
        [Range(0.2f, 3.0f)]
        private float blurSpread = 1.0f;

        [Header("缩放系数(创建的RenderTexture的宽高为屏幕大小的多少分之一)")]
        [SerializeField]
        [Range(1, 8)]
        private int downSample = 2;

        [Header("亮度的范围,如果一个像素亮度大于等于该值时认为该像素点需要进行泛光处理,在不开启HDR的情况下亮度范围不会大于1")]
        [SerializeField]
        [Range(0.0f, 4.0f)]
        private float luminanceThreshold = 0.0f;

        /// 
        /// 特效摄像机上的渲染目标
        /// 

        private RenderTexture bloomCameraTargetRenderTexture;
        
        /// 
        /// 当前的泛光摄像机
        /// 

        private Camera currBloomCamera;

        /// 
        /// 当前的摄像机
        /// 

        private Camera currCamera;

        protected override void OnStart()
        {
            base.OnStart();

            if (bloomCameraTargetRenderTexture == null)
            {
                bloomCameraTargetRenderTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, 16);
            }

            currCamera = GetComponent();
            // 生成深度纹理
            currCamera.depthTextureMode = DepthTextureMode.Depth;

            // 创建特效摄像机
            currBloomCamera = CreateBloomCamera();
        }

        void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            if (material != null && bloomCameraTargetRenderTexture != null && currBloomCamera != null)
            {
                currBloomCamera.Render();   // 强行进行渲染
                
                material.SetFloat("_LuminanceThreshold", luminanceThreshold);
                int iWidth = source.width / downSample;
                int iHeight = source.height / downSample;

                RenderTexture buffer0 = RenderTexture.GetTemporary(iWidth, iHeight, 0);
                buffer0.filterMode = FilterMode.Bilinear;

                // 提取较亮区域部分(调用Shader中的Pass0)
                Graphics.Blit(bloomCameraTargetRenderTexture, buffer0, material, 0);

                // 进行高斯模糊(调用Shader中的Pass1和Pass2)
                for (int i = 0; i < iterations; i++)
                {
                    material.SetFloat("_BlurSize", 1.0f + blurSpread);
                    RenderTexture buffer1 = RenderTexture.GetTemporary(iWidth, iHeight, 0);

                    Graphics.Blit(buffer0, buffer1, material, 1);

                    RenderTexture.ReleaseTemporary(buffer0);
                    buffer0 = buffer1;

                    buffer1 = RenderTexture.GetTemporary(iWidth, iHeight, 0);
                    Graphics.Blit(buffer0, buffer1, material, 2);

                    RenderTexture.ReleaseTemporary(buffer0);
                    buffer0 = buffer1;
                }

                // 将高斯模糊后的纹理与原纹理进行混合(调用Shader中的Pass3)
                material.SetTexture("_Bloom", buffer0);
                Graphics.Blit(source, destination, material, 3);

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

        /// 
        /// 创建泛光摄像机
        /// 

        /// 
        private Camera CreateBloomCamera()
        {
            if (currBloomCamera != null)
            {
                return currBloomCamera;
            }

            int iTargetLayer = 0;
            for (int i = 0; i < CullingLayer.Length; i++)
            {
                iTargetLayer |= 1 << CullingLayer[i];
            }

            // 创建新的专门用于生成亮度纹理的摄像机
            GameObject goBloomCamera = new GameObject();
            // 设置新摄像机的父节点与位置
            goBloomCamera.transform.parent = transform;
            goBloomCamera.transform.position = transform.position;
            // 不激活该摄像机
            goBloomCamera.SetActive(false);

            currBloomCamera = goBloomCamera.AddComponent();
            // 将新创建的摄像机设置为与
            currBloomCamera.CopyFrom(currCamera);
            // 设置摄像机要照射的层
            currBloomCamera.cullingMask = iTargetLayer;
            // 摄像机每次清空为固定的颜色
            currBloomCamera.clearFlags = CameraClearFlags.SolidColor;
            currBloomCamera.backgroundColor = Color.black;
            // 设置渲染目标
            currBloomCamera.targetTexture = bloomCameraTargetRenderTexture;
            // 生成深度纹理
            currBloomCamera.depthTextureMode = DepthTextureMode.Depth;

            return currBloomCamera;
        }

        private void OnDestroy()
        {
            if (bloomCameraTargetRenderTexture != null)
            {
                RenderTexture.ReleaseTemporary(bloomCameraTargetRenderTexture);
                bloomCameraTargetRenderTexture = null;
            }

            if (currBloomCamera != null)
            {
                DestroyImmediate(currBloomCamera.gameObject);
                currBloomCamera = null;
            }

            currCamera = null;
            bloomShader = null;
            CullingLayer = null;
            bloomMaterial = null;
        }
    }
}

其它的设置与上一节的一样,然后运行查看效果,问题解决

代码截图:

回到顶部