NGUI學習筆記(七):UIPanel剪裁粒子效果

一.簡介
關於這部分的學習說起來真的有點繞,個人感覺這個屬於進階級別的開發問題了,但又屬於不得不會的內容,於是硬着頭皮搞下去!

二.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

九.完整腳本

http://pan.baidu.com/s/1kU8o9vx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章