主要使用噪聲和透明度測試,從噪聲圖中讀取某個通道的值,而後使用該值進行透明度測試。
主要代碼以下:html
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r; clip(cutout - _Threshold);
完整代碼點這裏git
若是純粹這樣鏤空,則效果太樸素了,所以一般要在鏤空邊緣上弄點顏色來模擬火化、融化等效果。github
第一種實現很簡單,首先定義_EdgeLength和_EdgeColor兩個屬性來決定邊緣多長範圍要顯示邊緣顏色;而後在代碼中找到合適的範圍來顯示邊緣顏色。
主要代碼以下:測試
//Properties _EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1 _EdgeColor("Border Color", Color) = (1,1,1,1) ... //Fragment if(cutout - _Threshold < _EdgeLength) return _EdgeColor;
完整代碼點這裏3d
第一種純顏色的效果並不太好,更好的效果是混合兩種顏色,來實現一種更加天然的過渡效果。
主要代碼以下:code
if(cutout - _Threshold < _EdgeLength) { float degree = (cutout - _Threshold) / _EdgeLength; return lerp(_EdgeFirstColor, _EdgeSecondColor, degree); }
完整代碼點這裏orm
爲了讓過渡更加天然,咱們能夠進一步混合邊緣顏色和物體本來的顏色。
主要代碼以下:htm
float degree = saturate((cutout - _Threshold) / _EdgeLength); //須要保證在[0,1]以避免後面插值時顏色過亮 fixed4 edgeColor = lerp(_EdgeFirstColor, _EdgeSecondColor, degree); fixed4 col = tex2D(_MainTex, i.uvMainTex); fixed4 finalColor = lerp(edgeColor, col, degree); return fixed4(finalColor.rgb, 1);
完整代碼點這裏blog
爲了讓邊緣顏色更加豐富,咱們能夠進而使用漸變紋理:
而後咱們就能夠利用degree來對這條漸變紋理採樣做爲咱們的邊緣顏色:ip
float degree = saturate((cutout - _Threshold) / _EdgeLength); fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree)); fixed4 col = tex2D(_MainTex, i.uvMainTex); fixed4 finalColor = lerp(edgeColor, col, degree); return fixed4(finalColor.rgb, 1);
爲了從特定點開始消融,咱們須要把片元到特定點的距離考慮進clip中。
第一步須要先定義消融開始點,而後求出各個片元到該點的距離(本例子是在模型空間中進行):
//Properties _StartPoint("Start Point", Vector) = (0, 0, 0, 0) //消融開始點 ... //Vert //把點都轉到模型空間 o.objPos = v.vertex; o.objStartPos = mul(unity_WorldToObject, _StartPoint); ... //Fragment float dist = length(i.objPos.xyz - i.objStartPos.xyz); //求出片元到開始點距離
第二步是求出網格內兩點的最大距離,用來對第一步求出的距離進行歸一化。這一步須要在C#腳本中進行,思路就是遍歷任意兩點,而後找出最大距離:
public class Dissolve : MonoBehaviour { void Start () { Material mat = GetComponent<MeshRenderer>().material; mat.SetFloat("_MaxDistance", CalculateMaxDistance()); } float CalculateMaxDistance() { float maxDistance = 0; Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices; for(int i = 0; i < vertices.Length; i++) { Vector3 v1 = vertices[i]; for(int k = 0; k < vertices.Length; k++) { if (i == k) continue; Vector3 v2 = vertices[k]; float mag = (v1 - v2).magnitude; if (maxDistance < mag) maxDistance = mag; } } return maxDistance; } }
同時Shader裏面也要同時定義_MaxDistance來存放最大距離的值:
//Properties _MaxDistance("Max Distance", Float) = 0 //Pass float _MaxDistance;
第三步就是歸一化距離值
//Fragment float normalizedDist = saturate(dist / _MaxDistance);
第四步要加入一個_DistanceEffect屬性來控制距離值對整個消融的影響程度:
//Properties _DistanceEffect("Distance Effect", Range(0.0, 1.0)) = 0.5 ... //Pass float _DistanceEffect; ... //Fragment fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r * (1 - _DistanceEffect) + normalizedDist * _DistanceEffect; clip(cutout - _Threshold);
上面已經看到一個合適_DistanceEffect的效果了,下面貼出_DistanceEffect爲1的效果圖:
這就完成了從特定點開始消融的效果了,不過有一點要注意,消融開始點最好是在網格上面,這樣效果會好點。
利用這個從特定點消融的原理,咱們能夠實現場景切換。
假設咱們要實現以下效果:
由於咱們原來的Shader是從中間開始鏤空的,和圖中從四周開始鏤空有點不一樣,所以咱們須要稍微修改一下計算距離的方式:
//Fragment float normalizedDist = 1 - saturate(dist / _MaxDistance);
這時候咱們的Shader就能從四周開始消融了。
第二步就是須要修改計算距離的座標空間,原來咱們是在模型空間下計算的,而如今很明顯多個不一樣的物體會同時受消融值的影響,所以咱們改成世界空間下計算距離:
//Vert o.worldPos = mul(unity_ObjectToWorld, v.vertex); //Fragment float dist = length(i.worldPos.xyz - _StartPoint.xyz);
完整代碼點這裏
爲了讓Shader應用到場景物體上好看點,我加了點漫反射代碼。
第三步爲了計算全部場景的物體的頂點到消融開始點的最大距離,我定義了下面這個腳本:
public class DissolveEnvironment : MonoBehaviour { public Vector3 dissolveStartPoint; [Range(0, 1)] public float dissolveThreshold = 0; [Range(0, 1)] public float distanceEffect = 0.6f; void Start () { //計算全部子物體到消融開始點的最大距離 MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>(); float maxDistance = 0; for(int i = 0; i < meshFilters.Length; i++) { float distance = CalculateMaxDistance(meshFilters[i].mesh.vertices); if (distance > maxDistance) maxDistance = distance; } //傳值到Shader MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>(); for(int i = 0; i < meshRenderers.Length; i++) { meshRenderers[i].material.SetVector("_StartPoint", dissolveStartPoint); meshRenderers[i].material.SetFloat("_MaxDistance", maxDistance); } } void Update () { //傳值到Shader,爲了方便控制全部子物體Material的值 MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>(); for (int i = 0; i < meshRenderers.Length; i++) { meshRenderers[i].material.SetFloat("_Threshold", dissolveThreshold); meshRenderers[i].material.SetFloat("_DistanceEffect", distanceEffect); } } //計算給定頂點集到消融開始點的最大距離 float CalculateMaxDistance(Vector3[] vertices) { float maxDistance = 0; for(int i = 0; i < vertices.Length; i++) { Vector3 vert = vertices[i]; float distance = (vert - dissolveStartPoint).magnitude; if (distance > maxDistance) maxDistance = distance; } return maxDistance; } }
這個腳本同時還提供了一些值來方便控制全部場景的物體。
像這樣把場景的物體放到Environment物體下面,而後把腳本掛到Environment,就能實現以下結果了:
理解了上面的從特定點開始消融,那麼理解從特定方向開始消融就很簡單了。
下面實現X方向消融的效果。
第一步求出X方向的邊界,而後傳給Shader:
using UnityEngine; using System.Collections; public class DissolveDirection : MonoBehaviour { void Start () { Material mat = GetComponent<Renderer>().material; float minX, maxX; CalculateMinMaxX(out minX, out maxX); mat.SetFloat("_MinBorderX", minX); mat.SetFloat("_MaxBorderX", maxX); } void CalculateMinMaxX(out float minX, out float maxX) { Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices; minX = maxX = vertices[0].x; for(int i = 1; i < vertices.Length; i++) { float x = vertices[i].x; if (x < minX) minX = x; if (x > maxX) maxX = x; } } }
第二步定義是從X正方向仍是負方向開始消融,而後求出各個片元在X份量上與邊界的距離:
//Properties _Direction("Direction", Int) = 1 //1表示從X正方向開始,其餘值則從負方向 _MinBorderX("Min Border X", Float) = -0.5 //從程序傳入 _MaxBorderX("Max Border X", Float) = 0.5 //從程序傳入 ... //Vert o.objPosX = v.vertex.x; ... //Fragment float range = _MaxBorderX - _MinBorderX; float border = _MinBorderX; if(_Direction == 1) //1表示從X正方向開始,其餘值則從負方向 border = _MaxBorderX;
主要效果就是上面的從特定方向消融加上灰燼向特定方向飛散。
首先咱們須要生成灰燼,咱們能夠延遲clip的時機:
float edgeCutout = cutout - _Threshold; clip(edgeCutout + _AshWidth); //延至灰燼寬度處才剔除掉
這樣能夠在消融邊緣上面留下一大片的顏色,而咱們須要的是細碎的灰燼,所以咱們還須要用白噪聲圖對這片顏色再進行一次Dissolve:
float degree = saturate(edgeCutout / _EdgeWidth); fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree)); fixed4 finalColor = fixed4(lerp(edgeColor, albedo, degree).rgb, 1); if(degree < 0.001) //粗略代表這是灰燼部分 { clip(whiteNoise * _AshDensity + normalizedDist * _DistanceEffect - _Threshold); //灰燼處用白噪聲來進行碎片化 finalColor = _AshColor; }
下一步就是讓灰燼可以向特定方向飛散,實際上就是操做頂點,讓頂點進行偏移,所以這一步在頂點着色器中進行:
float cutout = GetNormalizedDist(o.worldPos.y); float3 localFlyDirection = normalize(mul(unity_WorldToObject, _FlyDirection.xyz)); float flyDegree = (_Threshold - cutout)/_EdgeWidth; float val = max(0, flyDegree * _FlyIntensity); v.vertex.xyz += localFlyDirection * val;
具體原理參考 Unity案例介紹:Trifox裏的遮擋處理和溶解着色器(一)
完整代碼點這裏 我這裏的實現是簡化版。
項目代碼在Github上,點這裏查看
《Unity Shader 入門精要》
Tutorial - Burning Edges Dissolve Shader in Unity
A Burning Paper Shader
Unity案例介紹:Trifox裏的遮擋處理和溶解着色器(一)
《Trifox》中的遮擋處理和溶解着色器技術(下)