有序的無序:unity shader噪聲圖以及消融效果的實(shí)現(xiàn)

先上一張效果圖:


消融效果

這篇文章我準(zhǔn)備寫兩部分:

  • 使用噪聲圖在unity shader里實(shí)現(xiàn)物體消融的效果
  • 消融是隨機(jī)的,但每次過程又是固定的,這一種“有序的無序”模式的理解,這一點(diǎn)是我更想說的,消融效果是幫助解釋這個概念。

有規(guī)律的隨機(jī)

玩游戲的時候,有時會有火焰特效,有次我盯住一個火焰看,發(fā)現(xiàn)火焰是循環(huán)播放的,比如下面這張圖。類似火焰這種東西,是變化無常的,一般就是隨機(jī)來做,比如用unity自帶的粒子系統(tǒng)來實(shí)現(xiàn),那么火焰的形態(tài)是完全隨機(jī)無序的,是我們無法控制的。但又是怎么讓規(guī)定的樣式進(jìn)行晃動呢?這里就有一個沖突,就是火焰的形態(tài)的無序隨機(jī)的,否則就會不像;然后火焰這種無序又是按照一種既定的規(guī)律重復(fù)的進(jìn)行。

簡單說,隨機(jī)是13、313、54、114、521、14、41、4521、421這樣任意的,足夠時間,都是隨機(jī)的;而有規(guī)律的隨機(jī)是13、313、54、114、521、14、41、4521、421這樣隨機(jī)了一輪后,下一輪跟這個是相同的,這樣重復(fù)。可以理解為套了兩層,第一層重復(fù),是有規(guī)律的,第二層隨機(jī)。

這就是我的困惑,怎么構(gòu)造這種“有序的無序” ?

重復(fù)的火焰特效

消融效果

先上代碼,如果你不了解unity shader,也可以繼續(xù)看看,我會把里面的邏輯用常人理解的方式理一遍。

Shader "Unlit/Dissolve"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _BurnMap ("Fire Map", 2D) = "white" {}
        _BurnSpeed ("Burn Speed", float) = 1.0
        _Specular ("Specular", range(0, 1)) = 0.5
        _Gloss ("Gloss", range(8, 256)) = 20
        //_BurnAmount ("BurnAmount", range(0,1)) = 0

        _BurnFirstColor ("Burn First Color", Color) = (1,0,0,1)
        _BurnSecondColor ("Burn Second Color", Color) = (0,0,0,1)
        _BurnRange ("Burn Range", float) = 0.1
    }
    SubShader
    {
        Tags { "LightMode"="ForwardBase" }


        Pass
        {
            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"


            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BurnMap;
            fixed _BurnSpeed;
            fixed _Gloss;
            float _Specular;

            fixed _BurnAmount;
            fixed4 _BurnFirstColor;
            fixed4 _BurnSecondColor;
            float _BurnRange;

            struct a2v{
                float4 vertex : POSITION;
                float4 normal : NORMAL;
                float4 uv : TEXCOORD0;
            };

            struct v2f{
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 worldPos : TEXCOORD1;
                float3 worldNormal : TEXCOORD2;
            };

            v2f vert (a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                //burn map
                fixed3 burn = tex2D(_BurnMap, i.uv).rgb;
                _BurnAmount += _Time.y * _BurnSpeed;
                clip(burn.r - _BurnAmount);


                // sample the texture
                fixed3 albedo = tex2D(_MainTex, i.uv).rgb;

                //diffuse
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed3 diffuse = _LightColor0.rgb * saturate(dot(lightDir, i.worldNormal)) * albedo;
            
                //specular
                fixed3 reflectDir = normalize(reflect(-lightDir, i.worldNormal));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 specular = _LightColor0.rgb * _Specular * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;

                fixed3 finalColor = specular + diffuse + ambient;

                //在消融的邊緣位置,添加紅色和黑色,模擬燒焦的效果。當(dāng)前正在燒的邊緣就是那些r - _BurnAmount剛好為0的位置。
                //float burnRate = 1 - saturate((burn.r - _BurnAmount) / _BurnRange);
                float burnRate = 1 - smoothstep(0, _BurnRange, burn.r - _BurnAmount);
                fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, burnRate);
                //burnColor = pow(burnColor, 5);
                finalColor = lerp(finalColor, burnColor, burnRate);

                return fixed4(finalColor, 1);
            }
            ENDCG
        }
    }
}
  • shader里有個函數(shù)clip可以丟棄像素,就是不渲染模型的這個點(diǎn)了。什么是物體的消融?不就是你本來看到的是物體A,然后變成了看到物體A后面的背景了。使用丟棄像素的操作,本來渲染物體A的,變成顯示后面的東西了,消融效果就出來了。

  • 消融還有個特性,就是它像水滴在紙上面那樣逐漸擴(kuò)撒開來,而不是無規(guī)律的隨意消融。那么問題就轉(zhuǎn)移為:按照一個逐漸擴(kuò)散的模式丟棄模型的像素。

  • 如果要像現(xiàn)實(shí)里一樣,那么消融擴(kuò)散的方向、大小、快慢都該是無規(guī)律任意的,這是這個問題里的無序。但是如果要用程序隨機(jī)計(jì)算哪些地方消融,那么就是不斷出現(xiàn)一個個小洞那樣的效果,要模擬擴(kuò)散消融,個人覺得那就太難了。而現(xiàn)在的做法就是使用紋理圖

  • 比如我用相機(jī)給現(xiàn)實(shí)的火拍一張照,把這個作為模板。物體的表面有個uv坐標(biāo)系,簡單說就是3維物體表面跟2維的平面建立一個對應(yīng)關(guān)系,那么物體表面任意一點(diǎn),我都可以找到之前拍的火焰紋理圖上一個對應(yīng)點(diǎn),然后根據(jù)這個點(diǎn)的顏色里的紅色多少決定是否丟棄。

火焰紋理

主要代碼就是這一段,其他只是實(shí)現(xiàn)光照和邊緣燒焦效果。

fixed3 burn = tex2D(_BurnMap, i.uv).rgb;
_BurnAmount += _Time.y * _BurnSpeed;
clip(burn.r - _BurnAmount);

_BurnMap是火焰紋理圖,tex2D(_BurnMap, i.uv)這個函數(shù)就是根據(jù)點(diǎn)的uv左邊取到對應(yīng)紋理圖上那一點(diǎn)的顏色信息,clip(burn.r - _BurnAmount);如果這個一點(diǎn)的r(紅色)通道值小于_BurnAmount,就剔除這個像素。因?yàn)?code>_BurnAmount += _Time.y * _BurnSpeed;``_BurnAmount不斷增大,那么就會有越來越多的像素被丟棄,最后整個物體都被丟棄,徹底消融。

因?yàn)榧y理圖本身就是擴(kuò)散模式的,所以物體被剔除也是這個模式。

如果把物體表面的紋理也換成火焰紋理,就可以很明顯的看到,從最紅的位置開始消融,消融的位置跟紋理的顏色分布是完全一樣的。

火焰紋理消融

紋理的力量

回到上面

怎么構(gòu)造這種“有序的無序” ?

使用紋理。紋理上面的內(nèi)容本身是混亂無序的,但是紋理圖已經(jīng)是一張固定的圖,我們參照紋理來決定哪里消融,那紋理圖就是有規(guī)律的了,是有序的。

在unity里,shader里是使用紋理,那么在其他地方呢?更抽象的說,就是使用一個模板,把無序記錄下來,然后按照這個已經(jīng)記錄下來的模板來操作,這樣就可以實(shí)現(xiàn)“有序的無序”

最后一個問題,這樣做有什么好處?簡便直接效率高。

資源

消融效果的材質(zhì)包放在github這個庫里,DissoveShader.unitypackage就是。

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

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