Unity輝光效果/噪聲生成筆記

這次效果制作主要學(xué)習(xí)了Value Noise的活用以及使用腳本處理屏幕后期效果。

大致步驟是:首先用一個(gè)新的相機(jī)(注:已使用Command Buffer,現(xiàn)在只需要主相機(jī))使用SetReplacementShader把屏幕中材質(zhì)標(biāo)記有Glowable的物體替換為只渲染全白的顏色的shader,得到的RT記為貼圖1;然后在OnRenderImage中使用Graphics.Blit將生成的RT提供給模糊后處理材質(zhì)進(jìn)行全屏模糊,得到的RT記為貼圖2。將貼圖1貼圖2提供給一個(gè)混合材質(zhì),其shader的作用是先得到貼圖1貼圖2的差值的絕對值(純白的輝光),并計(jì)算Value Noise的采樣得到最終混合的彩色加性輝光效果。

模糊

這里采用了高斯模糊。一種比較有效的方法是在shader中寫兩個(gè)Pass,一個(gè)縱向模糊一個(gè)橫向模糊,然后在OnRenderImage中開一個(gè)臨時(shí)RT(記得最后要RenderTexture.ReleaseTemporary(temp)),我們需要做的就是將srcRT做一次橫向模糊再做一次縱向模糊,即達(dá)到二維高斯模糊的效果。如果再增加模糊的迭代次數(shù)可以得到較好的效果,但是相對的也消耗性能。

for (int i = 0; i < 5; i++)
{
       var temp = RenderTexture.GetTemporary(Blurred.width, Blurred.height);
       Graphics.Blit(Blurred, temp, _blurMat, 0);
       Graphics.Blit(temp, Blurred, _blurMat, 1);
       RenderTexture.ReleaseTemporary(temp);
}

另外,如果一直保持高斯模糊采樣的距離不變的話,會(huì)導(dǎo)致離得太遠(yuǎn)的物體輝光過于強(qiáng)烈。



因此我們可以在腳本中計(jì)算好物體和相機(jī)的距離,然后在模糊的過程中將高斯模糊的距離除以一個(gè)和距離正相關(guān)的數(shù)即可(類似透視除法)。

_BlurSize.x /= _Distance / 10;
fixed4 s = tex2D(_MainTex, i.uv) * 0.38774;
s += tex2D(_MainTex, i.uv + float2(_BlurSize.x * 2, 0)) * 0.06136;
s += tex2D(_MainTex, i.uv + float2(_BlurSize.x, 0)) * 0.24477;
s += tex2D(_MainTex, i.uv + float2(_BlurSize.x * -1, 0)) * 0.24477;
s += tex2D(_MainTex, i.uv + float2(_BlurSize.x * -2, 0)) * 0.06136;

如果在最后混合時(shí)將采用的貼圖1貼圖2的差絕對值改為差最小為0的話,也能做出無對內(nèi)發(fā)光的效果。


//fixed4 glow = abs(tex2D(_GlowBlurredTex, uvTemp) - tex2D(_GlowPrePassTex,uvTemp));
fixed4 glow = max(0, tex2D(_GlowBlurredTex, uvTemp) - tex2D(_GlowPrePassTex,uvTemp));

Value Noise

開始時(shí)的想法是制作類似寒氣向外消散的效果,于是在網(wǎng)上搜索了一番發(fā)現(xiàn)了Shadertoy的Ball of Fire這個(gè)效果。雖然只有短短三四十行代碼,但是效果確實(shí)驚艷。


分析效果和代碼后發(fā)現(xiàn),這個(gè)效果其實(shí)是由一個(gè)非常簡單的3D Value 噪聲得到的,這里就不具體講其定義了。

第一維是屏幕坐標(biāo)相對中心的角度,第二維是屏幕坐標(biāo)相對中心的距離加上時(shí)間(以達(dá)到向外擴(kuò)散的效果),第三個(gè)維度是時(shí)間(以達(dá)到得到圖像整體隨時(shí)間變化的效果)。不過此處我們還有一個(gè)問題要解決,因?yàn)榻嵌染S度是從0到360度,如何保證在0度附近圖像具有連續(xù)性?


不連續(xù)的情況

這里shadertoy的作者使用了一種非常精妙的處理方法,讓0度和360度的Value Noise網(wǎng)格頂點(diǎn)生成的是相同的數(shù)據(jù)。首先隨機(jī)數(shù)的產(chǎn)生是如下所示:

float4 v = float4(xyz0.x + xyz0.y + xyz0.z , xyz1.x  + xyz0.y + xyz0.z,
                  xyz0.x   + xyz1.y + xyz0.z, xyz1.x  + xyz1.y + xyz0.z);
rand = frac(sin(v/res*6.2832)*1000.0);

其中v是根據(jù)網(wǎng)格頂點(diǎn)的整數(shù)坐標(biāo)三維相加得到的,隨機(jī)數(shù)是采用sinv值取其小數(shù)點(diǎn)第4位之后的數(shù)得到的偽隨機(jī)數(shù)。那么如何保證0度和360度的Value Noise網(wǎng)格頂點(diǎn)生成相同隨機(jī)數(shù)呢?只需要讓網(wǎng)格x坐標(biāo)0(角度0度)和晶格坐標(biāo)x為最大值(res,角度360度)生成的v具有相同數(shù)值即可!因此我們使用v/res*6.2832就可以保證0度和360度處具有連續(xù)性。當(dāng)然,此處采用的三坐標(biāo)相加的隨機(jī)數(shù)產(chǎn)生,為了讓三坐標(biāo)之間互無相關(guān),我們要讓他們在數(shù)量級上各不相同。

在我們的腳本中,要記得把物體的屏幕坐標(biāo)傳送給shader,當(dāng)做計(jì)算中屏幕的中心。

float3 xyz0 = floor(fmod(xyz,res)) * float3(1,200,1000);
float3 xyz1 = floor(fmod(xyz + float3(1,1,1),res))  * float3(1,200,1000);

最后,經(jīng)過一定的顏色處理我們就能得到這種十分酷炫的向外噴射的效果。

混合

得到上述的噪聲后,其實(shí)這里就很簡單了,自定義自己想要的Color Ramp即可。封面圖采用的是簡單的

n = smoothstep(0.1,0.9,n);
return col +  glow * _Intensity * float4(n,1-n,1,0);

其中n是疊加了三個(gè)不同分辨率的octave得到的采樣值,會(huì)生成類似云彩的效果(Photoshop的云彩效果就是這么做出來的)。

float n = noiseSampler(float3(x,y*0.75 - _Time.x * 2, _Time.x * 0.15),16) * 0.5;
n += noiseSampler(float3(x,y*0.75 - _Time.x * 2, _Time.x * 0.15),32) * 0.25;
n += noiseSampler(float3(x,y*0.75 - _Time.x * 2, _Time.x * 0.15),64) * 0.125;
n += noiseSampler(float3(x,y*0.75 - _Time.x * 2, _Time.x * 0.15),128) * 0.0625;

類似的改變Color Ramp、噪聲疊加方式以及模糊距離我們還能得到許多有趣的效果。


精神污染效果
return col +  glow * _Intensity * float4(frac(sin( 4 * n)),frac(sin(4 * n + 3)),frac(sin(4 * n + 5)),0);
簡單的帶動(dòng)態(tài)邊緣光效果
float n = noiseSampler(float3(x,y*0.75 - _Time.x * 2, _Time.x * 0.15),16) * 1;
return col +  glow * _Intensity * n;

優(yōu)化

以上噪聲效果都是基于實(shí)時(shí)計(jì)算的,如果我們暫且不用效果不是最明顯的噪聲第三個(gè)維度(整體隨時(shí)間慢慢變化),那么實(shí)際上我們的噪聲可以通過直接采樣一張tileable的噪聲圖得到。將角度以及離中心長度度映射到UV的0-1,采樣tileable的圖既能保證角度上采樣的連續(xù)性也能保證隨時(shí)間向外擴(kuò)散的采樣連續(xù)性。

生成tileable噪聲的具體方法是在四維空間生成兩個(gè)正交圓,然后將第一第二個(gè)維度代表的圓的角度映射為0-1,將第三第四個(gè)維度代表的圓的角度也映射為0-1,然后將這兩個(gè)0-1作為UV,用對應(yīng)的圓查找四維空間的采樣值即可生成tileable的噪聲。這個(gè)貼圖的生成我們可以在Unity中寫一個(gè)小插件做到。

使用Command Buffer優(yōu)化腳本結(jié)構(gòu)

最近看了相關(guān)command buffer的內(nèi)容,回想起這個(gè)效果能夠只用一個(gè)相機(jī)就完成,腳本的數(shù)量也減少了?,F(xiàn)在我們只需要為command buffer提供一串指令讓主相機(jī)在渲染過程中完成即可,因此我們不再需要一個(gè)專門的相機(jī)去渲染標(biāo)記發(fā)光的物體,而只需要使用CommandBuffer.DrawRenderer指令去單獨(dú)渲染某一個(gè)想要的物體即可。完整腳本內(nèi)容如下:

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

[RequireComponent(typeof(Camera))]
public class GlowComposite : MonoBehaviour

{
    [Range (0, 10)]
    public float Intensity = 2;
    public GameObject glowTargets = null;

    public static Material compositeMat;

    public static Material pureColorMaterial;
    public static Material blurMat;

    private CommandBuffer commandBuffer = null;


    private static RenderTexture prePass;
    private static RenderTexture blurred;
    private static RenderTexture temp;

    void OnEnable()
    {
        prePass = new RenderTexture(Screen.width, Screen.height, 24,RenderTextureFormat.Default);
        blurred = new RenderTexture(Screen.width >> 2, Screen.height >> 2, 0);

        pureColorMaterial = new Material(Shader.Find("Custom/WhiteReplace"));
        blurMat = new Material(Shader.Find("Hidden/Blur"));
        blurMat.SetVector("_BlurSize", new Vector2(blurred.texelSize.x * 1.5f, blurred.texelSize.y * 1.5f));

        Renderer[] renderers = glowTargets.GetComponentsInChildren<Renderer>();
        commandBuffer = new CommandBuffer();
        commandBuffer.SetRenderTarget(prePass);
        commandBuffer.ClearRenderTarget(true, true, Color.black);
        foreach (Renderer r in renderers)
        {
            commandBuffer.DrawRenderer(r, pureColorMaterial);
        }

        temp = RenderTexture.GetTemporary(blurred.width, blurred.height);
        commandBuffer.Blit(prePass,blurred);

        for (int i = 0; i < 5; i++)
        {
            commandBuffer.Blit(blurred, temp,blurMat, 0);
            commandBuffer.Blit(temp, blurred,blurMat, 1);
        }
            
        compositeMat = new Material(Shader.Find("Custom/GlowComposite"));
        compositeMat.SetTexture("_GlowPrePassTex", prePass);
        compositeMat.SetTexture("_GlowBlurredTex", blurred);

    }

    void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        Graphics.ExecuteCommandBuffer(commandBuffer);
        compositeMat.SetFloat("_Intensity", Intensity);
        Graphics.Blit(src, dst, compositeMat, 0);
    }
        
    void OnDisable()
    {
        RenderTexture.ReleaseTemporary(temp);
    }
}

Github: https://github.com/techizgit/UnityPlayground

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容