以前玩Tencent的仙劍4手遊時,殺死boss會看到boss有「消融」的效果,就是身體上有多個洞洞而後往四周擴散直至屍體徹底消失,但效果是沒有關閉背面剔除的「穿幫」效果,可能也是考慮性能因素。 emmmm,那下面開始詳細講解消融等等噪聲相關應用的實現~html
· 消融效果算法
噪音,就是「沙沙渣渣渣」的雜亂無章的聲音,在圖形學中使用噪聲是爲了把一些隨機變量來引入到程序中,如火焰、地形、雲朵的模擬等等都要使用隨機變量。有人可能會問,直接使用random這種函數不就行了嗎?爲何要引入這麼多算法生成的特定噪聲呢?緣由在於生成的隨機值太「隨機」了,在圖形學中稱這種噪聲爲「白噪聲」(功率譜密度在整個頻域內均勻分佈的噪聲),通俗理解就是相似小時候電視機上面的雪花噪聲,app
(二維的白噪聲紋理)dom
噪聲的基礎來自於隨機數,若屏幕上的每一個像素點給一個0~1之間的隨機數來表示象素點的亮度,就能獲得上面的白噪聲紋理,很像老式電視機故障時的黑白雪花。這種隨機圖像並無太大的做用,由於每一個點的隨機數都是離散的,相互徹底沒有關係,能看到這幅圖像中的世界是徹底沒有邏輯,沒有能量的。 既然基本隨機噪音因離散性而沒意義,那就人爲讓噪音連續起來,這種平滑的連續化處理就是插值,在離散數據中間用函數插值的方法把空隙填滿空間就天然連續了。函數
簡單的線性插值獲得的棱角分明的結構,指數/三角函數等插值組合能造成各類插值算法,從而對應多種不一樣噪聲。如Perlin噪聲被大量用於雲朵、火焰和地形等天然環境的模擬;Simplex噪聲在其基礎上進行改進,提升效率和效果;而Worley噪聲被提出用於模擬一些多孔結構,例如紙張、木紋等。有關噪聲算法的詳解,可參考 【圖形學】談談噪聲 。性能
無論怎樣,對簡單隨機噪音插值後都能獲得一種像黴菌表面的圖像,本節的噪聲紋理以下圖:測試
開始編碼,表現效果就是從不一樣的區域開始,並向隨機方向擴張,最後整個物體都將消失不見。編碼
原理:使用噪聲紋理進行取樣,將取樣的結果和某個控制消融程度的閾值比較,若小於閾值就用clip函數將對應像素裁剪掉(透明度測試處理),不裁剪掉的像素就和另外兩個顏色來進行混合插值處理從而達到燒焦的效果,而且將光照陰影和衰減也按照一樣的處理,避免錯誤的投射陰影。spa
完整代碼以下:.net
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Shaders/Dissolve" { Properties { _BurnAmount ("Burn Amount",Range(0.0, 1.0)) = 0.0 //消融程度,0爲正常顯示,1爲物體會徹底消融 _LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1 //模擬燒焦效果的線寬,值越大表示火焰邊緣蔓延範圍越廣 _MainTex ("Base (RGB)", 2D) = "white" {} //物體自己的漫反射紋理 _BumpMap ("Normal Map", 2D) = "bump" {} //法線紋理 _BurnMap("Burn Map", 2D) = "white"{} //噪聲紋理 //火焰邊緣的兩種顏色 _BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1) _BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1) } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry"} Pass { Tags { "LightMode"="ForwardBase" } //關閉面片剔除, 則模型正面/背面都被渲染,由於消融會裸露模型內部的構造 Cull Off CGPROGRAM #include "Lighting.cginc" #include "AutoLight.cginc" #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag fixed _BurnAmount; fixed _LineWidth; sampler2D _MainTex; sampler2D _BumpMap; sampler2D _BurnMap; fixed4 _BurnFirstColor; fixed4 _BurnSecondColor; float4 _MainTex_ST; float4 _BumpMap_ST; float4 _BurnMap_ST; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uvMainTex : TEXCOORD0; float2 uvBumpMap : TEXCOORD1; float2 uvBurnMap : TEXCOORD2; float3 lightDir : TEXCOORD3; float3 worldPos : TEXCOORD4; SHADOW_COORDS(5) }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); //使用 TRANSFORM_TEX內置宏計算三張紋理的紋理座標 o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex); o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap); o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap); //將光源從模型空間變換到切線空間 TANGENT_SPACE_ROTATION; o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz; //陰影 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i) : SV_Target { //對噪聲紋理採樣 fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb; //返回值範圍是0~1,小於消融閾值的像素被剔除 clip(burn.r - _BurnAmount); float3 tangentLightDir = normalize(i.lightDir); fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap)); fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir)); /* smoothstep( float _Min, float _Max, float _X ) 實現平滑過渡,讓燒焦區域顏色混合漸變 若_X 比 _Min,小於返回 0; 若_X 比 _Max 大則返回 1; 在範圍 [_Min, _Max]內返回介於 0 和 1 之間的值 smoothstep 函數用於在一段時間範圍內逐漸但非線性地增長屬性,如「不透明度」(Opacity)從 0 增長到 1。*/ //混合係數t,值爲0不須要混合,像素爲正常模型顏色;值爲1表示像素位於消融邊界處 fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount); /*插值函數lerp實現顏色漸變過渡 lerp(a, b, w):a與b爲floatX或fixedX等同種類型,返回值是對應同種類型 當w爲0時返回a,爲1時返回b,0~1之間,以比重w將a b進行線性插值計算。*/ fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t); burnColor = pow(burnColor, 5); UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //Mark! step(a, x):Returns (x >= a) ? 1 : 0 fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount)); return fixed4(finalColor, 1); } ENDCG } //透明度處理的物體的陰影作一樣處理,以避免被剔除的區域仍會向其餘物體投射陰影從而「穿幫」 Pass { Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #include "UnityCG.cginc" fixed _BurnAmount; sampler2D _BurnMap; float4 _BurnMap_ST; struct v2f { V2F_SHADOW_CASTER; float2 uvBurnMap : TEXCOORD1; }; v2f vert(appdata_base v) { v2f o; TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap); return o; } fixed4 frag(v2f i) : SV_Target { fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb; clip(burn.r - _BurnAmount); SHADOW_CASTER_FRAGMENT(i) } ENDCG } } FallBack "Diffuse" }
注意,最後經過step判斷函數再對混合係數t作條件處理,因一開始_BurnAmount爲0時,t可能爲1或不爲0,lerp函數運算致使顯示了_BurnColor,實際上剛開始_BurnAmount爲0時不顯示任何消融效果。
運行遊戲,顯然有了!
(_BurnAmount = 0.3)
(_BurnAmount = 0.5)
明顯看到關閉了剔除以後,背後的消融也不會穿幫。若是把剔除打開,我的以爲效果差很少且減少性能消耗,
(_BurnAmount = 0.5)
資料連接:
Unity Shader-死亡溶解效果 [UnityShader]溶解與重現效果(差很少)
一種基於邊緣Bloom的溶解shader的實現 (效果挺酷)