Unity Command Buffer初探——对选中物体做bloom效果

自从Unity发布了Command Buffer这个功能,我就一直想用一下这个功能,正好最近遇上个需求,需要让想要的物体高亮而其余物体正常,这正是用Command Buffer的好机会。
先来介绍一下bloom效果,中文一般译作“全屏泛光”,是一种常见的后处理效果。简而言之,bloom就是在高亮物体周围产生羽毛状或条纹状的光芒,造成一种光很强的错觉,如下图1所示。


图1

最常见的bloom做法是我们先拿到摄像机渲染完成的图像,将这个图像模糊化,然后把模糊过的图像在和原图像做叠加,就得到了bloom效果。这样的做法虽然得到了bloom效果,却是对整个屏幕上的物体做的,一般来说我们会用某个亮度作为阈值,超过这个阈值的所有在屏幕上的物体都会做bloom,剩下的不做。不过我遇到的需求是只需要将屏幕中的某个物体做bloom,用亮度做阈值筛选会“伤及无辜”,所以干脆就把常见的bloom做法变通一下,改成要某个物体有bloom效果就有。

步骤不麻烦:

  1. 获取屏幕图像t0并将它涂黑,记为t1
  2. 将需要做bloom的物体画到第一步得到的图像t1上,记为t2
  3. 将t2模糊,得到一张糊掉的图,记为t3
  4. t0 + t3,再乘以想要的bloom颜色,搞定!

然后,我们照着上面的思想来开始写代码。
preTex = RenderTexture.GetTemporary(Screen.width >> blurDownSample, Screen.height >> blurDownSample,24,RenderTextureFormat.Default);
这是第一步获取一张全屏大小的图像,其中第三个参数是要求传入一个depth,根据官方文档

Depth buffer bits (0, 16 or 24). Note that only 24 bit depth has stencil buffer.

我们只能传入0、16或者24,简单来说,传入0代表获取到的全屏图像RT中的物体是不带排序的,只适用于全部物体都指定了渲染顺序的情况;16则代表是RT中的物体是排序好的;24代表RT中的物体不但排序好,RT中还有stencil buffer参与的痕迹。
还有选24的时候Z buffer的精度是“32 bit floating”,对于Z buffer精度有要求的时候就选24好了。

然后我们创造一个command buffer的实例,command buffer的作用是预定义一些渲染指令,然后在我们想要执行的时候去执行这些指令,所以我们在这个实例中塞入第一个指令涂黑图像,代码是这样的

cmd.SetRenderTarget(preTex);
cmd.ClearRenderTarget(true,true,Color.black);

再来塞入步骤2,也就是第二个指令,画个图像到全黑的RT上,我们通过command buffer中的方法DrawRenderer实现,代码是这样的cmd.DrawRenderer(r,preMat);其中preMat就是需要做bloom物体的material。现在我们得到了这样一张图

接着我们要模糊这张RT

        blurTex = RenderTexture.GetTemporary(Screen.width >> blurDownSample, Screen.height >>blurDownSample,0);
        temp = RenderTexture.GetTemporary(blurTex.width,blurTex.height);
        cmd.Blit(preTex,blurTex);
        for (int i=0;i<blurNums;i++){
            cmd.SetGlobalVector("offsets",new Vector4(Mathf.Pow(2.0f, i+1) / (Screen.width >> blurDownSample),0,0,0));
            cmd.Blit(blurTex,temp,blurMat);
            cmd.SetGlobalVector("offsets",new Vector4(0,Mathf.Pow(2.0f, i+1) / (Screen.width >> blurDownSample),0,0));
            cmd.Blit(temp,blurTex,blurMat);
        }
        compositeMat.SetTexture("_blurTex", blurTex);
        compositeMat.SetFloat("_Intensity",intensity);
        compositeMat.SetColor("_GlowColor",glowColor);

我们创造了一张屏幕大小的RT叫blurTex和一张一样大小的RT叫temp,先把preText复制到blurTex(Blit的大概意思就是把一个图像复制给另一个图像),然后blurTex在x方向模糊一下(模糊的材质指定为blurMat)给到temp,这时temp就是模糊过的图像,所以我们再让temp在y方向模糊一下给回blurTex,重复这一过程直到到达设定的模糊上限。最后blurTex就是模糊过的图像了。

其中blurMat是我从官方的command buffer示例中找到的高斯模糊的shader,具体请看官方示例
PS. 这里我勉强用语言说了一下这段代码在干什么,说的不好见谅啊>_<

最后让这个模糊过的图像和屏幕图像相加。这一步我们要依靠Unity生命周期里的一个方法OnRenderImage,具体代码是这样的。

private void OnRenderImage(RenderTexture src, RenderTexture dest) {
        Graphics.ExecuteCommandBuffer(cmd);
        Graphics.Blit(src,dest,compositeMat);

    }

我们先执行command buffer里面一系列的渲染指令,再通过一个材质compositeMat把原图像src变成目标图像dest。这个材质compositeMat的shader是这样的。

Shader "Unlit/GlowComposite"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {

        Pass
        {
            Cull Off
            ZWrite Off
            ZTest Always

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
        
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _blurTex;
            fixed4 _GlowColor;
            float _Intensity;
         

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed glow = tex2D(_blurTex,i.uv);

                return col + _GlowColor * _Intensity * glow;
            }
            ENDCG
        }
    }
}

其中Cull Off ZWrite Off ZTest Always官方文档要求添加

Turn off depth buffer writes and tests in your post-processing effect shaders. This ensures that Graphics.Blit does not write unintended values into destination Z buffer. Almost all post-processing shader passes should contain Cull Off ZWrite Off ZTest Always states.

这样可以保证Graphics.Blit在执行时不会把不想要的值写入Z buffer。

这里的_MainTex就是Graphics.Blit传入的全屏图像src,_blurTex就是上面代码里执行的compositeMat.SetTexture("_blurTex", blurTex);,这东西乘上强度和颜色,再和src图像叠加,最终得出的结果就是我们想要的bloom效果了。

不过现在有个问题,这个bloom效果有遮挡问题,如下图


我的解决方案是在渲染那个圆柱体的shader里面获取一下摄像头的depth buffer,把输出的颜色乘以摄像头中depth buffer的值来解决的。具体代码如下

Shader "Unlit/Test1"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque"}

        LOD 100

        Pass
        {
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 screenPos : TEXCOORD2;

            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _CameraDepthTexture;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.screenPos = ComputeScreenPos(o.vertex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                float2 texcoord = i.screenPos.xy / i.screenPos.w;
                float camDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, texcoord);
                camDepth = Linear01Depth (camDepth);
                
            
                col *= camDepth;
               

                return col;
                
            }
            ENDCG
        }
    }
}

这样就不会有遮挡问题了。
这里要注意一下要用自带的_CameraDepthTexture需要在Camera上挂脚本,脚本里写上GetComponent<Camera>().depthTextureMode = DepthTextureMode.Depth;才能用。还有float2 texcoord = i.screenPos.xy / i.screenPos.w;这里做除法是为了抵消透视所带来的影响。(原文:This division is to counteract the perspective correction the GPU automatically performs on interpolators. 可以看下这篇博客做更多了解)

项目地址

参考
Unity中镜头使用的若干研究
Using Command Buffers in Unity: Selective Bloom
Unity辉光效果/噪声生成笔记

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352