NGUI的流光shader解决方案(一)(支持UITexture和使用图集的UISprite)

前言

之前的Unity项目中,UI部分需要的流光比较多,特别是一些使用图集的UISprite,为节省图片资源,必然是使用shader来实现。
搜了很多前人的分享,发现一些有意思的解决思路,但并没有很完善的分享,而且我们的需求还包括自定义流光图、流光间隔、流光时间等。所以自己做了一套,这里正好整理分享一下。

本方案最终实现的流光效果如下:

此流光效果,有以下优点:
- 支持UITexture
- 支持使用图集的UISprite,且每个UISprite可以独立效果
- 可以自定义流光的图、流光速度、流光时间间隔、流光强度等

不过,也有以下缺点或者说是待完善的地方:
- 应用于UISprite时,图片的清晰度会受到影响,变得模糊一点
- 原理是通过独立的材质球来做,所以会增加NGUI的drawcall数量

实现UITexture的流光

先写shader

先看看我们的UITexture图片和流光图片:
NGUI的流光shader解决方案(一)(支持UITexture和使用图集的UISprite)_第1张图片

Shader我们是在默认的Transparent Colored的代码基础上进行修改,新增的变量定义部分:

Properties
{
    ...
    //流光纹理
    _FlowLightTex("FlowLight Texture",2D) = "white"{}
    //流光强度
    _FlowLightPower("FlowLightPower",float) = 1
    //流光开关,0关闭,1开启
    _IsOpenFlowLight ("IsOpenFlowLight", float) = 0
    //流光的偏移,通过设置此值来达到uv动画效果
    _FlowLightOffset("FlowLight Offset", float) = 0
}

主要逻辑是在像素处理函数中来做,原理就是通过变化uv取流光纹理的颜色,叠加到主纹理上即可,直接上代码:

fixed4 frag(v2f IN)
{
    fixed4 colorMain = tex2D(_MainTex, IN.texcoord);
    //如果开启流光
    if(_IsOpenFlowLight > 0.5) {
        float2 uvFlowLight = IN.texcoord;
        //uv减半处理
        uvFlowLight.x /= 2;
        //根据速度变化
        uvFlowLight.x -= _FlowLightOffset;
        //用计算后的uv取流光那张图
        fixed4 colorFlowLight = tex2D(_FlowLightTex, uvFlowLight) * _FlowLightPower;
        //颜色叠加计算
        colorFlowLight.rgb *= colorMain.rgb;
        colorMain.rgb += colorFlowLight.rgb;
        colorMain.rgb *= colorMain.a;
    }
    return colorMain;
}

对于颜色叠加计算这块,以上面的2张图为例,多解释一下:

colorFlowLight.rgb *= colorMain.rgb;

这句的结果相当于:
NGUI的流光shader解决方案(一)(支持UITexture和使用图集的UISprite)_第2张图片

colorMain.rgb += colorFlowLight.rgb;

这句话把上面这张图再叠加到原图上,就有了流光的高亮效果,如图:
NGUI的流光shader解决方案(一)(支持UITexture和使用图集的UISprite)_第3张图片

colorMain.rgb *= colorMain.a;

最后这句是保持和原图同样的透明度而已。
好了,现在我们的shader已经准备好了,就差使用到UITexture上了。

应用到UITexture

首先我们创建一个对应上面shader的材质球,接下来写一个C#脚本,挂在UITexture上,这样我们就可以自由调整参数,来实现我们想要的效果了。

using UnityEngine;
using System.Collections;

public class EffectFlowLightForTex : MonoBehaviour{
    //起始的uv坐标
    public float mUvStart = 0f;
    //uv移动的速度
    public float mUvSpeed = 0.02f;
    //一次动画uv移动的最大长度
    public float mUvXMax = 0.9f;
    //流光的间隔时间
    public float mTimeInteval = 3f;

    public Material mCurMaterial = null;
    private float mUvAdd;
    private bool mIsPlaying;

    void Awake () {
        UITexture tex = gameObject.GetComponent ();
        //onRender是什么鬼?这里稍后解释
        tex.onRender += UpdateMaterial;

        mUvAdd = 0;
        mIsPlaying = true;

        tex.material = mCurMaterial;
    }

    //NGUI更新Material的回调
    private void UpdateMaterial(Material mat) {
        if (mIsPlaying) {
            //逐帧移动uv
            mUvAdd += mUvSpeed;
            mat.SetFloat ("_FlowLightOffset", mUvStart + mUvAdd);
            mat.SetFloat ("_IsOpenFlowLight", 1f);

            //如果移动的uv已经超过最大值,重置,准备下一次流光
            if (mUvAdd >= mUvXMax) {
                mIsPlaying = false;
                mat.SetFloat ("_IsOpenFlowLight", 0f);
                Invoke ("PlayOnceAgain", mTimeInteval);
            }
        } else {
            mat.SetFloat ("_IsOpenFlowLight", 0f);
        }
    }

    //再次触发流光
    private void PlayOnceAgain() {
        mUvAdd = 0;
        mIsPlaying = true;
    }

}

好,挂上脚本,设置一下参数,就能看到文章开头的流光效果了:
NGUI的流光shader解决方案(一)(支持UITexture和使用图集的UISprite)_第4张图片

NGUI的onRender

大家可能会奇怪,上面的代码中的onRender什么鬼,我直接在脚本内部,起个定时器什么的来修改shader的参数不就行了吗。有兴趣的同学可以尝试一下,不管用的。
这个问题一开始我也遇到了,google一下,原来这和NGUI的渲染机制有关。大概解释一下:
NGUI在渲染的时候,大家都知道,会合并DrawCall,合并的必然是使用同一材质球的元素,NGUI内部会新建一个Material,然后UIDrawCall会进行一次渲染,渲染的时候就会调用onRender这个回调,并且把这个新建的Material传过来,方便我们做一些自定义的操作

看看UIWidget中的onRender定义大家也就都明白了:

/// 
/// Set the callback that will be triggered when the widget is being rendered (OnWillRenderObject).
/// This is where you would set material properties and shader values.
/// 

public UIDrawCall.OnRenderCallback onRender
{
    ...
}

好了,基于UITexture的流光解决方案就是以上这些了,那么接下来使用图集的UISprite怎么办呢?鉴于篇幅已经很长了,我们下一篇再说。

你可能感兴趣的:(Unity)