天天看点

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

上文实现了UITexture的流光效果,但是有时候我们希望使用图集的UISprite也能有流光效果,那怎么办呢。一种做法是将图片独立出来,使用UITexture,结合上文的方法,这里我们来看看直接操作UISprite该怎么做。

需要注意的是,本文有些使用到的原理在上文已经解释过了,就不再赘述。建议大家还是先看前一篇。

先写Shader

流光Shader的原理都是差不多的,这里的关键问题在于:UV坐标,因为纹理是图集这张大图,uv坐标怎么对应到流光纹理上呢,这个其实是个简单的数学问题,看下图就明白了:

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

这是一个Atlas,A点代表Sprite的左下角,B点是右上角,整个坐标系表示的是uv坐标,问题就转化为:已知C点相对于大图的uv坐标,求C点相对于Sprite小图的uv坐标。

A点坐标 = ( spriteOffsetX/atlasWidth, spriteOffsetY/atlasHeight )
B点坐标 = ( (spriteOffsetX+spriteWidth)/atlasWidth, (spriteOffsetY+spriteHeight)/atlasHeight )
C点相对于大图的uv坐标 = ( cUvX, cUvY )
那么C点相对于小图的uv坐标 = ( x, y )

x = (cUvX - A.x)/(B.x - A.x)
x = (cUvx - spriteOffsetX/atlasWidth) / (spriteWidth/atlasWidth)
y同理
           

那么我们的Shader代码就出来了,同样在默认的Transparent Colored的代码基础上进行修改:

Properties
    {
        ...
        //流光相关
        _WidthRate ("Sprite.width/Atlas.width", float) = 1
        _HeightRate ("Sprite.height/Atlas.height", float) = 1
        _XOffset("offsetX/Atlas.width", float) = 0
        _YOffset("offsetY/Atlas.height", float) = 0
        //流光纹理
        _FlowLightTex("FlowLight Texture",2D) = "white"{}
        //流光强度
        _FlowLightPower("FlowLight Power",float) = 1
        //流光开关,0关闭,1开启
        _IsOpenFlowLight ("IsOpenFlowLight", float) = 0
        //流光的偏移,通过设置此值来达到uv动画效果
        _FlowLightOffset("FlowLight Offset", float) = 0
    }
    ...
    fixed4 frag (v2f IN) : COLOR
    {
        fixed4 colorMain = tex2D(_MainTex, IN.texcoord) * IN.color;

        //如果开启流光
        if(_IsOpenFlowLight > 0.5) {
            //计算部分
            float2 flow_uv = float2( (IN.texcoord.x-_XOffset)/_WidthRate, (IN.texcoord.y-_YOffset)/_HeightRate );
            flow_uv.x /= 2;
            flow_uv.x -= _FlowLightOffset;
            fixed4 colorFlowLight = tex2D(_FlowLightTex, flow_uv) * _FlowLightPower;
            colorFlowLight.rgb *= colorMain.rgb;
            colorMain.rgb += colorFlowLight.rgb;
            colorMain.rgb *= colorMain.a;
        }

        return colorMain;
    }
           

好了,接下来让UISprite用上自己的Shader

自定义材质

我们知道,UISprite默认使用的是Atlas对应的材质,如果我们把整个Atlas的材质换了,那么同一图集的都会受到影响,跟我们期望的不一样。

所以这里修改UISprite的源码,加个Material的变量,让我们可以随意的给某一个UISprite指定材质:

//UISprite.cs
...
//我们加的变量
public Material _testMaterial;

//修改自带方法的返回
public override Material material { 
    get { 
        if (_testMaterial != null)
            return _testMaterial;
        else if (mAtlas != null)
            return mAtlas.spriteMaterial;
        else
            return null;
    } 
}
...
           
NGUI的流光shader解决方案(二)(支持UITexture和使用图集的UISprite)

Ok,万事俱备,就差控制脚本了。

控制脚本

首先起一个脚本来设置上面Shader中的几个变量:

using UnityEngine;
using System.Collections;

public class FlowLightHelpTool : MonoBehaviour
{
    private float widthRate;
    private float heightRate;
    private float xOffsetRate;
    private float yOffsetRate;
    private UISprite sprite;

    void Awake()
    {
        sprite = GetComponent<UISprite>();
        widthRate = sprite.GetAtlasSprite().width * 1.0f / sprite.atlas.spriteMaterial.mainTexture.width;
        heightRate = sprite.GetAtlasSprite().height * 1.0f / sprite.atlas.spriteMaterial.mainTexture.height;
        xOffsetRate = sprite.GetAtlasSprite().x * 1.0f / sprite.atlas.spriteMaterial.mainTexture.width;
        yOffsetRate = (sprite.atlas.spriteMaterial.mainTexture.height-(sprite.GetAtlasSprite().y + sprite.GetAtlasSprite().height)) * 1.0f / sprite.atlas.spriteMaterial.mainTexture.height;
    }

    private void Start()
    {
        sprite.material.SetFloat("_WidthRate", widthRate);
        sprite.material.SetFloat("_HeightRate", heightRate);
        sprite.material.SetFloat("_XOffset", xOffsetRate);
        sprite.material.SetFloat("_YOffset", yOffsetRate);
    }
}
           

再加上跟上一篇差不多的控制脚本:

using UnityEngine;
using System.Collections;

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

    private float mUvAdd;
    private bool mIsPlaying;

    void Awake () {
        UISprite sp = gameObject.GetComponent<UISprite> ();
        sp.onRender += UpdateMaterial;

        mUvAdd = 0;
        mIsPlaying = true;
    }

    //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)

TODO

– 目前发现UISprite使用这种方案后,在手机上,图片的清晰度会下降,目前我还没有时间来研究这块,也欢迎大家留言讨论

– 这种方案会增加DrawCall,因为使用了独立的材质,没有办法