白泽图

  • 文章
    • Unity渲染
    • Unity项目开发
    • 工具
    • 数学
    • 算法
    • 网站搭建
    • 网络&操作系统
蒋程个人博客
互联网技术经验总结&分享
  1. 首页
  2. Unity渲染
  3. 正文

模糊效果原理与实现

2021-09-13 2046点热度 0人点赞 0条评论

模糊效果是游戏项目开发中最简单也是最常见的效果之一,什么高斯模糊,运动模糊,镜头景深之类的效果通通都属于模糊的范畴,只是根据最终的显示不同,算法上略有区别而已,当然除了后期效果,模糊还有很多广泛的应用,比如优化实时阴影

模糊的原理

个人理解就是一句话,让像素失去它原本的细节信息

通俗的解释是一个像素原本的颜色乘以一个权重值,(范围在0,1之间),默认为1,无变化,乘完之后从周围的像素补齐剩余的权重,举个例子:自身像素颜色*权重值0.5,然后从左右各取一个像素颜色,分别乘以权重值0.25,最后把所有的颜色相加代替原本的颜色,我们可以说原本的像素丢失了50%的细节

权重的分配

权重的分配可以做为了一个模糊效果的调整参数,简单一点的操作是取周围N个像素做平均值,那么你得到的模糊会是一块一块的,类似马赛克的效果,如果我将靠近原本像素的权重值给高,然后以距离来分配权重,即越接近中心的像素权重越高,越边缘的像素权重越低,你会得到类似毛玻璃的效果,这是因为像素丢失的细节并不大,我们还是可以模模糊糊看到它的样子,而且由于权重的正态分布,边缘处是平滑的,而这种正态分布的方式又叫高斯分布,至此你已经学会了高斯模糊。

现在让我们扩展一下思维,前面说到模糊是取周围的N个像素来补齐我们丢失的细节,那么N到底是多少呢,事实上这并没有一个准确的数字,毕竟渲染的原则为渲染结果正确即合理,但是想想也知道,取的周围的像素越多,越模糊,计算量也越大,(这里给出UNITY官方的高斯模糊算法供大家参考,取左上角方向两个像素,右下角方向两个像素,中间像素权重0.4,然后以和中心点的距离分别为0.15和0.05)。另外模糊像素距离中心点位置(越远模糊越严重)

现在我们知道取填充像素的数量可变,那么我们是否控制一下这些像素的坐标呢,难道必须是周围N圈吗?显然不是的,比如我们可以根据角色或者相机移动的方向来换算出我们要模糊的“周围”像素位置,于是我们得到了运动模糊的效果(运动模糊的实现我之后的文章会详细说,这里只说原理)。

继续思考,上面我们说的模糊是针对整个画面的所有像素的来处理的,有没有一种可能,我们只对“部分”像素进行模糊操作呢,答案是肯定的,不过我们需要一个参考值来告诉我们哪些像素需要模糊,哪些不需要,说到这里,你们不会想到溶解效果的实现,给张控制图不就完了,说道控制图,那么相机的深度图算不算?当然算!我们可以根据深度图来使距离相机近的像素不模糊,距离远的像素模糊,于是我们得到的镜头效果-景深

实践

通过上面的几种方式你可能得到的模糊效果并不够“模糊”,特别是你处理了一张高清图的时候,这个时候我们需要对它多模糊几次,简单来说就是先把原图blit到临时RT1上,然后RT1 blit到临时RT2上,RT2再到RT1,反复模糊处理,那么画面也会越模糊,当然性能也越低,毕竟你pass了N回

优化总结

这里我们总结一下导致性能下降的几个点

1.模糊像素数量

2.模糊次数

3.临时RT大小(临时RT越小,处理的像素越少,反正是模糊处理)

4.因地制宜(根据不同使用情况,采用不同的模糊算法,在性能充足的时候可以满足美术同学的细节需求,性能不充足的情况简单模糊处理,甚至可以模糊后做为一个不动的背景图使用,不用实时渲染,这也是本文的最终目的,懂原理,然后对项目进行定制化渲染,而不是一套效果从头用到尾


最后给出我在项目中里实现的一个模糊效果供大家参考(暂且叫高斯模糊吧,其实不满足正态分布,效果是最终调出来的)先贴一下效果图

代码(项目使用URP渲染)

using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using GameCore;

namespace PF.URP.PostProcessing
{
    [DisallowMultipleComponent]
    public class GaussianBlur : MonoBehaviour
    {
        private Material mMat;
        private bool mIsOpen;
        public void Open()
        {
            mIsOpen = true;
            CameraSlave.UI.Use();
            CameraSlave.UI.camera.SetCullingMask("Temp3");
            (CameraSlave.UI as CameraSlaveUI)?.SwitchEventSystem();

            SetLayer(LayerMask.NameToLayer("Temp3"));
            PFPostProcessingMgr.Instance.AddPostProcessing(this);
        }

        public void Close()
        {
            if (mIsOpen)
            {
                PFPostProcessingMgr.Instance.RemovePostProcessing(this);
                SetLayer(LayerMask.NameToLayer("UI"));
                CameraSlave.UI.UnUse();
                mIsOpen = false;
            }
        }

        private void SetLayer(int layer)
        {
            transform.gameObject.layer = layer;
            transform.SetChildLayer(layer);
        }

        [LuaInterface.NoToLua]
        public Material Material
        {
            get
            {
                if (mMat == null)
                {
                    mMat = Resources.Load<Material>("GaussianBlur");
                    mMat = new Material(mMat);
                }
                return mMat;
            }
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using GameCore;

namespace PF.URP.PostProcessing
{
    public class GaussianBlurFeature : ScriptableRendererFeature
    {
        public RenderPassEvent mEvent = RenderPassEvent.AfterRenderingPostProcessing;

        private GaussianBlurPass mPass;

        private GaussianBlur mBlur;

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            mBlur = PFPostProcessingMgr.Instance.GetPostProcessing<GaussianBlur>();
            if (mBlur == null)//控制开关
                return;

            if (renderingData.cameraData.camera != CameraEx._UICamera)
                return;

            var cameraColorTarget = renderer.cameraColorTarget;
            //设置当前需要后期的画面
            mPass.Setup(cameraColorTarget, mBlur);
            //添加到渲染列表
            renderer.EnqueuePass(mPass);
        }

        public override void Create()
        {
            mPass = new GaussianBlurPass(mEvent);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using GameCore;

namespace PF.URP.PostProcessing
{
    public class GaussianBlurPass : ScriptableRenderPass
    {
        private const string mCommandBufferName = "CommandBuffer_GaussianBlur";
        private const string mTempTexName1 = "GaussianBlurPass Temp Texture1";
        private const string mTempTexName2 = "GaussianBlurPass Temp Texture2";

        private RenderTargetHandle mTempTex_Handle1;
        private RenderTargetHandle mTempTex_Handle2;

        private GaussianBlur mBlur;
        private FilterMode mFilterMode = FilterMode.Bilinear;

        private RenderTargetIdentifier mSourceRT_Id;


        public GaussianBlurPass(RenderPassEvent @event)
        {
            this.renderPassEvent = @event;
            mTempTex_Handle1.Init(mTempTexName1);
            mTempTex_Handle2.Init(mTempTexName2);
        }


        public void Setup(RenderTargetIdentifier sourceRT, GaussianBlur blur)
        {
            mSourceRT_Id = sourceRT;
            mBlur = blur;
        }


        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            var cmd = CommandBufferPool.Get(mCommandBufferName);
            RenderImage(cmd, ref renderingData);
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }


        private void RenderImage(CommandBuffer cmd, ref RenderingData renderingData)
        {
            RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
            opaqueDesc.width = Mathf.FloorToInt(Screen.width * 0.5f);
            opaqueDesc.height = Mathf.FloorToInt(Screen.height * 0.5f);
            //获取临时RT
            cmd.GetTemporaryRT(mTempTex_Handle1.id, opaqueDesc, mFilterMode);
            cmd.GetTemporaryRT(mTempTex_Handle2.id, opaqueDesc, mFilterMode);
            //1
            mBlur.Material.SetVector("_OffsetSize", new Vector4(3, 4, 5, 6));
            Blit(cmd, mSourceRT_Id, mTempTex_Handle1.Identifier(), mBlur.Material);
            //2
            mBlur.Material.SetVector("_OffsetSize", new Vector4(6, 5, 4, 3));
            Blit(cmd, mTempTex_Handle1.Identifier(), mTempTex_Handle2.Identifier(), mBlur.Material);
            //3
            mBlur.Material.SetVector("_OffsetSize", new Vector4(3, 4, 5, 6));
            Blit(cmd, mTempTex_Handle2.Identifier(), mTempTex_Handle1.Identifier(), mBlur.Material);
            //4
            mBlur.Material.SetVector("_OffsetSize", new Vector4(6, 5, 4, 3));
            Blit(cmd, mTempTex_Handle1.Identifier(), mTempTex_Handle2.Identifier(), mBlur.Material);

            //将处理后的RT赋值给相机RT
            Blit(cmd, mTempTex_Handle2.Identifier(), mSourceRT_Id);
        }


        public override void FrameCleanup(CommandBuffer cmd)
        {
            //释放临时RT
            cmd.ReleaseTemporaryRT(mTempTex_Handle1.id);
            cmd.ReleaseTemporaryRT(mTempTex_Handle2.id);
        }

    }
}

标签: 暂无
最后更新:2021-09-22

蒋程

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

COPYRIGHT © 2023 白泽图. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

登录
注册|忘记密码?