先上一張效果圖:
這篇文章我準(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)造這種“有序的無序” ?
消融效果
先上代碼,如果你不了解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
就是。