一.簡介
關於這部分的學習說起來真的有點繞,個人感覺這個屬於進階級別的開發問題了,但又屬於不得不會的內容,於是硬着頭皮搞下去!
二.UISprite的材質
長期以來,一直搞不懂怎樣設置UISprite的材質,UGUI的Sprite材質很easy,在Image組件上可以輕鬆找到:
NGUI的在哪呢?
找到使用的圖集:
終於驚喜的發現,這個圖集上有Material,其實這個應該是我一直以來沒有注意的地方。
檢視一下Shader資源:
爲什麼會有這麼多相似的Shader呢?
三.UIDrawCall
觀察下來,這個腳本控制着所有NGUI渲染相關的Shader替換。
當物體將要被渲染時,NGUI首先會更新一次UI所有使用的材質:
更新材質的時候需要創建材質,新材質的Shader會根據剪裁次數(ClipCount)來選擇不同版本的Shader,比如剪裁1次則使用 “Hidden/Unlit/Transparent Colored 1”
四.剪裁區域參數的設置
UIPanel的剪裁設置有4種,這裏研究的是最常用的SoftClip
當選擇了Soft Clip後可以設置剪裁區域的大小、中心、邊緣的柔和度。
這些參數的效果會在UIDraw中的被調用,每次OnWillRenderObject函數都會調用SetClipping函數設置剪裁區域:
SetClipping函數將計算好的參數設置到實際用到的Shader中。
看一下剪裁1次所使用Shader的頂點與片元着色器的實現:
在頂點着色器的最後一步
o.worldPos = v.vertex.xy * _ClipRange0.zw + _ClipRange0.xy;
這個是把被剪裁的UI Mesh的模型空間的頂點先按照剪裁區域的長寬進行縮放,然後按照剪裁區域的中心點進行平移,將模型頂點轉換到Panel的剪裁區域的局部空間下,並將其“歸一化”。
在片元着色器的第一步
float2 factor = (float2(1.0, 1.0) - abs(IN.worldPos)) * _ClipArgs0;
這個將用來判斷該頂點的x、y分量是否處於剪裁區域[-1,1]內。
後面正式用到
col.a *= clamp( min(factor.x, factor.y), 0.0, 1.0);
如果factor的任意x、y分量小於0(即不在剪裁區域內),那麼輸出顏色的透明度alpha將會爲0,從而達到剪裁的效果。
五.NGUI DrawCall的基本統計
將UIDraw最上面的宏定義打開:
保存後,在編輯器界面稍等片刻,會發現這些:
原來,NGUI會將所有能夠一同提交渲染的UIMesh合併成了一個網格,而每個合併的網格就對應着1個NGUI DrawCall。
同時合併後的網格的縮放大小與UIPanel相同,所以UIPanel下的UI Mesh的模型頂點和Panel實際上處在同一座標系下。
【NGUI內置shader 的計算方式也驗證了這點】
滑動列表合併後的Mesh:
【注意右下角】
六.正式剪裁粒子
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
[RequireComponent(typeof(ParticleSystemRenderer))]
public class UIParticle : MonoBehaviour {
private UIPanel panel;
private ParticleSystemRenderer pRenderer;
private Material dyMaterial;
private UIWidget coverWidget;
// Use this for initialization
void Start () {
//找到這個粒子系統最近的父節點的UIPanel
panel = GetComponentInParent<UIPanel> ();
//找到這個粒子系統的Renderer
pRenderer = GetComponent<ParticleSystemRenderer> ();
dyMaterial = new Material (Shader.Find ("Hidden/Unlit/Transparent Colored 1"))
{
//提升其渲染隊列至4000
renderQueue = 4000,
//使用原始material的texture
mainTexture = pRenderer.sharedMaterial.mainTexture
};
pRenderer.material = dyMaterial;
/* 爲什麼不採用下面這種做法?
* pRenderer.material.renderQueue = 4000;
* 這樣的話,Unity會幫助我們自動創建動態材質
* 如此,該動態引用無法得到,很難判斷
* Destroy時也不好操作
*/
}
void OnWillRenderObject()
{
if (panel.hasClipping)
{
//裁剪區域
Vector4 cr = panel.drawCallClipRange;
//裁剪邊兒的柔和度
Vector2 soft = panel.clipSoftness;
Vector2 sharpenss = new Vector2 (1000.0f, 1000.0f);
if (soft.x > 0f)
sharpenss.x = cr.z / soft.x;
if (soft.y > 0f)
sharpenss.y = cr.w / soft.y;
//經過測試粒子系統產生的Mesh是不受UIPanel縮放比影響的
//所以要將其縮放比記錄下來
float scale = panel.transform.lossyScale.x;
//粒子系統的頂點座標系相對於panel會有一定的偏移,所以要將其position記錄下來
Vector3 position = panel.transform.position;
Debug.Assert (dyMaterial != null, "dyMaterial 創建失敗!!!!!!");
//座標變化的順序:縮放、旋轉、平移,這裏不考慮粒子系統的旋轉
dyMaterial.SetVector
(
Shader.PropertyToID ("_ClipRange0"),
new Vector4 (
-cr.x / cr.z - position.x/scale / cr.z,
-cr.y / cr.w - position.y/scale / cr.w,
1f / cr.z / scale,
1f / cr.w / scale
)
);
dyMaterial.SetVector(Shader.PropertyToID("_ClipArgs0"),new Vector4(sharpenss.x,sharpenss.y,0,1));
}
}
void OnDestroy()
{
DestroyImmediate (dyMaterial);
dyMaterial = null;
}
}
七.應用
八.參考鏈接
http://blog.csdn.net/tkokof1/article/details/52089289
九.完整腳本