【Unity Shader】 消融效果的實現
1.前言
參加騰訊2018遊戲崗校招結果出師未捷身先死,連面試機會都沒有(-_-||),想想筆試自己三道程式設計題0個ac也就釋懷了233,忙著實習實在沒精力複習演算法題,精力有限啊...
吐槽完畢迴歸主題
咱最近在玩wy的神都夜行錄,這款手遊畫面還是挺不錯的,就是肝疼(萬惡的wy策劃)。
刷本的時候看見了如下圖這個消融效果,就想著怎麼用unity shader來實現,畢竟消融效果在遊戲中是非常常見的。
2.實現思路
1.使用clip函式對片元進行裁剪來實現模型的消融
clip(value):當value值小於0時,裁減掉該片元,否則保留。
2.使用下面的噪聲圖來實現偽隨機消融,這樣能使效果看起來更加自然。關於什麼是噪聲,可以去看樂樂大師姐(在下小迷弟一枚)寫的關於噪聲的博文-
3.關於顏色侵蝕過渡的實現
從上面的效果圖中可以看見模型在消融時,有明顯的色彩侵蝕過程,這種色彩變化為消融提供了很好的過渡效果,為了實現這種效果,我們可以設定一個侵蝕顏色的閾值,根據這個閾值來判斷和返回侵蝕過程中的色彩變化。
//控制侵蝕程度
float _Erode;
//控制侵蝕的顏色閾值
float _ErodeThreshold;
4.光照
直接使用Lambert光照模型,光照的衰減交給unity內建巨集來處理
5.關於展示用的模型
手殘黨的福音,給大家推薦一款小巧的免費體素建模軟體-MagicaVoxel, 上手簡單,不好用不要錢~
來給大家展示一下我的作品:
怎麼樣,是不是神還原,哈哈哈哈
3.程式碼
下面是完整的shader程式碼,關鍵部分加了註釋:
Shader "Unlit/Melt" { Properties{ _MainTex("Base(rgb)", 2D) = "white"{} _NoiseMap("NoiseMap", 2D) = "white"{} _StartColor("StarColor", Color) = (0,0,0,0) _EndColor("EndColor", Color) = (0,0,0,0) _MeltThreshold("MeltThreshold", Range(0, 1)) = 0 _Erode("Erode", Range(0.0, 1.0)) = 0.98 _ErodeThreshold("ErodeThreshold", Range(0.0, 1.0)) = 0.71 } SubShader{ CGINCLUDE #include "Lighting.cginc" #include "UnityCG.cginc" #include "AutoLight.cginc" sampler2D _MainTex; float4 _MainTex_ST; sampler2D _NoiseMap; //消融邊緣起始顏色 fixed4 _StartColor; //最終顏色 fixed4 _EndColor; //消融閾值 float _MeltThreshold; //控制侵蝕程度 float _Erode; //控制侵蝕顏色閾值 float _ErodeThreshold; struct a2v{ float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; }; struct v2f{ float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; float2 uv : TEXCOORD2; SHADOW_COORDS(3) }; v2f vert(a2v v){ v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i) : SV_Target{ //使用噪聲圖取樣 fixed3 melt = tex2D(_NoiseMap, i.uv).rgb; //取樣閾值與設定閾值比較,小於設定的閾值就裁剪掉該片元 clip(melt.r - _MeltThreshold); //光照計算部分,使用蘭伯特漫反射光照模型 //紋理取樣得到反射率 fixed3 albedo = tex2D(_MainTex, i.uv).rgb; //世界法線 fixed3 worldNormal = normalize(i.worldNormal); //入射光 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); //計算環境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; //漫反射 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, -worldLightDir)); //最終光照 fixed3 lightColor = diffuse * atten + ambient; //侵蝕計算部分 float result = _MeltThreshold / melt.r; if(result > _Erode){ //如果結果大於消融顏色的閾值,則返回消融結束部分的顏色,否則返回初始顏色 if(result > _ErodeThreshold) { return _EndColor; } return _StartColor; } //直接返回光照後顏色 return fixed4(lightColor, 1); } ENDCG Pass{ Tags{ "RenderType" = "Opaque"} Cull off CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG } } FallBack Off }
為了看見整個模型的消融過程,我選擇關閉了剔除
Cull Off //關閉遮擋剔除
建立一個指令碼來控制消融的程度,實現自動播放的效果
Melt.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Melts : MonoBehaviour {
public Material material;
[Range(0.01f, 1.0f)]
public float meltSpeed = 0.2f;
private float meltThreshold = 0.0f;
void Start(){
material.SetFloat("_MeltThreshold", 0);
}
void Update(){
//使用時間控制消融閾值
meltThreshold = Mathf.Repeat(Time.time * meltSpeed, 6.0f);
material.SetFloat("_MeltThreshold", meltThreshold);
}
}
4.實現效果
來看看實現效果:
emmm,好像顏色有什麼地方不對勁....
截圖與遊戲效果圖對比看看
通過對比可以發現,遊戲截圖中的色彩明顯亮於我所使用的顏色。
這五毛錢特效可沒臉拿出去見人啊 QAQ
5.修改後的效果
想起從樂樂學姐那裡偷師學來的bloom效果,翻出之前寫的程式碼加上去試試看效果咋樣
!!! 瞬間感覺高大上了,並且有一種金屬感,效果的還原度也很高
關於bloom效果
bloom效果屬於影象處理的一種,這種影象處理技術在渲染中常被稱為後處理,由指令碼和shader共同實現。
bloom的原理簡單的來說就是對影象中亮度較高的區域進行高斯模糊處理,由於篇幅的原因我這裡不貼出具體程式碼,對bloom感興趣的可以看看我的github,我把該效果的原始碼放在了GitHub上。