unity shader 内置变量 Unity游戏研究

官网Manual:http://docs.unity3d.com/Manual/SL-UnityShaderVariables.html

unity提供大量的内置变量,来供我们使用,主要包括一些 :矩阵运算/变形,光影,时间等等。用户可以在CGIncludes目录下的UnityShaderVariables.cginc进行查看。


_WorldSpaceCameraPos:float3——相机的世界坐标

_ProjectionParams:float4——投影参数(x=+-1,y=相机近裁剪面,z=相机远裁剪面,w=1/FarPlane)

_ScreenParams:float4——屏幕参数(x=相机渲染宽度,y=相机渲染高度,z=1+1/x,w=1+1/y)


new 发布于 2017-3-23 03:23

KcrusKal、Prime最短路径算法 Unity游戏研究

克鲁斯卡尔算法,克鲁斯卡尔

给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树. 求最小生成树的算法

(1) 克鲁斯卡尔算法

图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的.该方法对于边相对比较多的不是很实用,浪费时间.

(2) 普里姆算法

图的存贮结构采用邻接矩阵.此方法是按各个顶点连通的步骤进行,需要用一个顶点集合,开始为空集,以后将以连通的顶点陆续加入到集合中,全部顶点加入集合后就得到所需的最小生成树 .


下面来具体讲下:

克鲁斯卡尔算法

方法:将图中边按其权值由小到大的次序顺序选取,若选边后不形成回路,则保留作为一条边,若形成回路则除去.依次选够(n-1)条边,即得最小生成树.(n为顶点数)



第一步:由边集数组选第一条边



第二步:选第二条边,即权值为2的边



第三步:选第三条边,即权值为3的边



第四步:选第四条边,即权值为4的边



第五步:选第五条边

  


普里姆算法

方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的所有边中选取权值最小的一条边作为生成树的边,并将集合外的那个顶点加入到集合中,表示该顶点已连通.再用集合内的顶点与集合外的顶点构成的边中找最小的边,并将相应的顶点加入集合中,如此下去直到全部顶点都加入到集合中,即得最小生成树.

例在下图中从1点出发求出此图的最小生成树,并按生成树的边的顺序将顶点与权值填入表中.



———————>先写出其邻接矩阵



第一步:从①开始,①进集合,用与集合外所有顶点能构成的边中找最小权值的一条边

①——②权6

①——③权1 -> 取①——③边

①——④权5   第二步:③进集合,①,③与②,④,⑤,⑥构成的最小边为

①——④权5

③——⑥权4 -> 取③——⑥边 第三步:⑥进集合,①,③,⑥与②,④,⑤构成的各最小边

①——②权6

③——②权5

⑥——④权2 -> 取⑥——④边 第四步:④进集合,①,③,⑥,④与②,⑤构成的各最小边

①——②权6

③——②权5 -> 取③——②边

⑥——⑤权6 第四步:②进集合,①,③,⑥,②,④与⑤构成的各最小边

②——⑤权3 -> 取②——⑤边


这也是在网上找到的一个Kruskal和Prim构造过程图,贴出来:

   


new 发布于 2016-10-9 07:22

Lightmap在PC上与android和ios的区别以及解决方法 Unity游戏研究

原文:http://www.ceeger.com/forum/read.php?tid=24457&fid=2

Lightmap在PC上与android和ios的区别以及解决方法

1、  问题描述 

相信很多人碰到过Lightmap的一些问题: 

烘培好Lightmap之后,在PC上看起来相当给力,而打包成ios或android之后,就傻眼了,Lightmap往往就出现了改变,例如灯光曝光度不够、光照颜色偏冷色调、有时候甚至黄色光也能变成绿色光等等。 

2、造成LightmapPCiosandroid上表现不同的原因。 

在u3d里,Lightmap的格式是.exr(openEXR),exr格式的储存方式是使用4*16Bit RGBA来储存数据的,即是说,它使用四个通道分别为RGB和alpha,每个通道占16位储存空间,每个像素占48位储存空间来储存数据。所以EXR格式的图片颜色值域范围就达到了[-65504,65504],远大于8bit(颜色值域[0,255],用浮点数表示就是[0,1])格式所能储存的数值范围。 

但是EXR格式的Lightmap打包成 android或ios之后,就变成LDR格式(可能是单通道8bit)的了,这就丢失的很多光照信息。例如在PC上烘培出的灯光亮度值是2000,转变成LDR格式后,亮度值就变成了255,这就是为什么打包成android或ios后灯光曝光度不够、光照颜色偏冷色调的原因。 

3、 解决方法。 

HDR有几种比较常见的编码格式,这里用的编码格式是LogLuv,就是将RGBA_FP32bit(每个通道32bit或16bit)的数据编码到RGBA32(每个通道8bit)的图片中。LogLuv算法几乎能100%还原unity切换平台后导致Lightmap丢掉的精度信息,算法如下:

   //FP32(RGB) to LogLUV
   fixed4 EncodeLogLuv(fixed3 vRGB)
   {
       fixed3x3 M = fixed3x3(
           0.2209, 0.3390, 0.4184,
           0.1138, 0.6780, 0.7319,
           0.0102, 0.1130, 0.2969 );
       fixed4 vResult;
       fixed3 Xp_Y_XYZp = mul(vRGB, 
M);
       Xp_Y_XYZp = max(Xp_Y_XYZp, 
fixed3(1e-6, 1e-6, 1e-6));
       vResult.xy = Xp_Y_XYZp.xy / 
Xp_Y_XYZp.z;
       fixed Le = 2 * 
log2(Xp_Y_XYZp.y) + 127;
       vResult.w = frac(Le);
       vResult.z = (Le - 
(floor(vResult.w*255.0f)) / 255.0f) / 255.0f;
       return vResult;
}
//LogLuv to FP32(RGB)
   fixed3 DecodeLogLuv(in fixed4 vLogLuv)
   {
       fixed3x3 InverseM = fixed3x3(
          6.0014, -2.7008, -1.7996,
          -1.3320, 3.1029, -5.7721,
          0.3008, -1.0882, 5.6268 );
       fixed Le = vLogLuv.z * 255 + 
vLogLuv.w;
       fixed3 Xp_Y_XYZp;
       Xp_Y_XYZp.y = exp2((Le - 127) 
/ 2);
       Xp_Y_XYZp.z = Xp_Y_XYZp.y / 
vLogLuv.y;
       Xp_Y_XYZp.x = vLogLuv.x * 
Xp_Y_XYZp.z;
       fixed3 vRGB = mul(Xp_Y_XYZp, 
InverseM);
return max(vRGB, 0); 
} 

4、 实现步骤 

1、 在PC平台烘培好光照贴图Lightmap_PC(.exr格式)。 

2、 通过Lightmap_PC生成新的光照贴图Lightmap_LogLuv(.png格式,RGBA32bit,单通道是8bit)。生成过程就用上面提到的LogLuv算法进行编码。这里用GPU来进行生成,有类似GPGPU的思想。如下: 

//用LogLuv算法生成的Lightmap输出到temp上,然后就将temp保存到一张.png格式的图片。
       public bool SaveRenderTextureToPNG(Texture inputTex,Shader outputShader, string contents, string pngName)
{
             RenderTexture temp = RenderTexture.GetTemporary(inputTex.width, inputTex.height, 0, RenderTextureFormat.ARGB32);
             Material mat = new Material(outputShader);
             Graphics.Blit(inputTex, temp, mat);
             bool ret = SaveRenderTextureToPNG(temp, contents,pngName);
             RenderTexture.ReleaseTemporary(temp);
             return ret;
       } 
//将RenderTexture保存成一张png图片
         public bool SaveRenderTextureToPNG(RenderTexture rt,string contents, string pngName)
         {
              RenderTexture prev = RenderTexture.active;
              RenderTexture.active = rt;
              Texture2D png = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);
              png.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
              byte[] bytes = png.EncodeToPNG();
              if (!Directory.Exists(contents))
                   Directory.CreateDirectory(contents);
              FileStream file = File.Open(contents + "/" + pngName + ".png", FileMode.Create);
              BinaryWriter writer = new BinaryWriter(file);
              writer.Write(bytes);
              file.Close();
              Texture2D.DestroyImmediate(png);
              png = null;
              RenderTexture.active = prev;
              return true;
         } 

3、 用新生成的光照贴图Lightmap_LogLuv替换原来的光照贴图Lightmap_PC。因为Lightmap_PC在切换android或ios会自动将Lightmap_PC压缩为LDR格式,这就丢失了精度。 

5、编写生成工具

我已经写好了一个简单的工具,可以将当前的场景下所有用Diffuse Shader的模型自动替换成新的Shader,改新的Shader将不再使用Unity原来.EXR格式的光照贴图,而是使用新生成的光照贴图。 

将会在” Assets/Data/Lightmap/当前场景名字”目录下生成对应的光照贴图,如下: 

将当前的场景下所有用Diffuse Shader的模型自动替换成新的Shader,如下:

原文:http://www.ceeger.com/forum/read.php?tid=24457&fid=2


new 发布于 2016-3-11 09:24

Unity A*寻路 C# Unity游戏研究

首先看了这篇翻译外国人的文章http://www.raywenderlich.com/zh-hans/21503/a%E6%98%9F%E5%AF%BB%E8%B7%AF%E7%AE%97%E6%B3%95%E4%BB%8B%E7%BB%8D



1.定义地图节点,及初始化地图数据

using UnityEngine;
using System.Collections.Generic;

public class Map
{
    /// <summary>
    /// 初始化地图
    /// </summary>
    /// <returns></returns>
    public static Dictionary<string, MapInfo> GetMap()
    {
        Dictionary<string, MapInfo> temp = new Dictionary<string, MapInfo>();

        for (int i = 0; i < 10; i++)
        {
            string s = "";
            for (int j = 0; j < 10; j++)
            {
                int tt = 0;
                if (i > 1 && i < 8 && j == 5)
                {
                    tt = 1;
                }
                MapInfo mi = new MapInfo(i, j, tt);
                temp.Add(i + "-" + j, mi);
                s += mi.tag + " ";
            }
            Debug.Log(s);
        }
        return temp;
    }
}

/// <summary>
/// 地图节点
/// </summary>
public class MapInfo
{
    /// <summary>
    /// X
    /// </summary>
    public int x;
    /// <summary>
    /// Y
    /// </summary>
    public int y;
    /// <summary>
    /// 是否可行走
    /// </summary>
    public int tag;
    /// <summary>
    /// G
    /// </summary>
    public int gValue;
    /// <summary>
    /// H
    /// </summary>
    public int hValue;
    /// <summary>
    /// 父节点
    /// </summary>
    public MapInfo parent;

    public MapInfo()
    { }

    /// <summary>
    /// 构造
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="tag"></param>
    public MapInfo(int x, int y,int tag)
    {
        this.x = x;
        this.y = y;
        this.tag = tag;
        this.gValue = 0;
        this.hValue = 0;
        this.parent = null;
    }
}

2.由起点终点寻路,有注释,知道原理的话,应该很容易懂

using UnityEngine;
using System.Collections.Generic;

public class AStar : MonoBehaviour
{
    /// <summary>
    /// 地图
    /// </summary>
    Dictionary<string, MapInfo> map;
    /// <summary>
    /// open列表
    /// </summary>
    Dictionary<string, MapInfo> openList = new Dictionary<string, MapInfo>();
    /// <summary>
    /// close列表
    /// </summary>
    Dictionary<string, MapInfo> closeList = new Dictionary<string, MapInfo>();

    /// <summary>
    /// 当前点
    /// </summary>
    MapInfo currentV;
    /// <summary>
    /// 当前点的相邻节点列表
    /// </summary>
    Dictionary<string, MapInfo> adjancentMap;

    // Use this for initialization
    void Start () 
    {
        map = Map.GetMap();
        MapInfo st = map["5-2"];//start
        MapInfo end = map["6-8"];//end

        FindPath(st, end);
    }

    /// <summary>
    /// 寻路
    /// </summary>
    /// <param name="start">起点</param>
    /// <param name="end">终点</param>
    public void FindPath(MapInfo start,MapInfo end)
    {
        openList.Add(start.x + "-" + start.y, start);

        do
        {
            currentV = GetTheLowestFrom(openList);

            closeList.Add(currentV.x + "-" + currentV.y, currentV);
            openList.Remove(currentV.x + "-" + currentV.y);

            if (closeList.ContainsKey(end.x + "-" + end.y))
            {
                Debug.Log("FindPath");

                PrintThePath(end);
                break;
            }

            adjancentMap = AdjacentList(currentV);

            foreach (string k in adjancentMap.Keys)
            {
                if (closeList.ContainsKey(k))
                {
                    continue;
                }

                if (!openList.ContainsKey(k))
                {
                    adjancentMap[k].parent = currentV;
                    adjancentMap[k].gValue = currentV.gValue + 1;
                    adjancentMap[k].hValue = GetManhattanDistance(adjancentMap[k], end);
                    openList.Add(k, adjancentMap[k]);
                }
                else
                {
                    int g = currentV.gValue + 1;
                    if (g < adjancentMap[k].gValue)
                    {
                        adjancentMap[k].gValue = g;
                        adjancentMap[k].parent = currentV;
                    }
                }
            }

        } while (openList.Count > 0);
    }

    /// <summary>
    /// 获取openlist中F最小的节点
    /// </summary>
    /// <param name="open"></param>
    /// <returns></returns>
    public MapInfo GetTheLowestFrom(Dictionary<string, MapInfo> open)
    {
        MapInfo result=null;
        int min = 10000;
        foreach (MapInfo m in open.Values)
        {
            if (m.gValue + m.hValue < min)
            {
                result = m;
                min = m.gValue + m.hValue;
            }
        }
        return result;
    }

    /// <summary>
    /// 获取当前节点的相邻节点
    /// </summary>
    /// <param name="m">当前节点</param>
    /// <returns></returns>
    public Dictionary<string, MapInfo> AdjacentList(MapInfo m)
    {
        Dictionary<string, MapInfo> resultDic=new Dictionary<string,MapInfo>();

        string left = (m.x - 1) + "-" + m.y;
        string right = (m.x + 1) + "-" + m.y;
        string top = m.x + "-" + (m.y - 1);
        string bot = m.x + "-" + (m.y + 1);

        if (map.ContainsKey(left))
        {
            if(map[left].tag==0)
                resultDic.Add(left, map[left]);
        }

        if (map.ContainsKey(right))
        {
            if (map[right].tag == 0)
                resultDic.Add(right, map[right]);
        }

        if (map.ContainsKey(top))
        {
            if (map[top].tag == 0)
                resultDic.Add(top, map[top]);
        }

        if (map.ContainsKey(bot))
        {
            if (map[bot].tag == 0)
                resultDic.Add(bot, map[bot]);
        }
        return resultDic;
    }

    /// <summary>
    /// 获得两个点的曼哈顿距离
    /// 作为估值函数
    /// </summary>
    /// <param name="st"></param>
    /// <param name="end"></param>
    /// <returns></returns>
    public int GetManhattanDistance(MapInfo st, MapInfo end)
    {
        int result = 0;
        result = Mathf.Abs(st.x - end.x) + Mathf.Abs(st.y - end.y);
        return result;
    }

    /// <summary>
    /// 输出路径
    /// </summary>
    /// <param name="end">终点</param>
    public void PrintThePath(MapInfo end)
    {
        string s = "";
        MapInfo m = end;
        while (m.parent != null)
        {
            s += "("+m.parent.x + "," + m.parent.y + ")->";            
            m = m.parent;
        }
        Debug.Log(s);
    }
}


标签: A Star A*

new 发布于 2016-3-1 01:37

制作UGUI美术字体 Unity游戏研究

制作美术字体的教程网上有很多了,我借鉴了网上众多大神的方法, 在此感谢他们。

额外工具:BMFont  下载
先用BMFont编辑美术字,并导出文本信息和图片。

PS:导出图片的大小适合你的美术图片集就好,不要有太大浪费。

导出的文件

有了这两个文件就可以在unity里做美术字体了。

先把这两个文件导入unity工程,然后再创建一个customFont和一个材质球。

然后就是读取数据文件了,这里使用了NGUI里的脚本BMFontReader等

再用脚本读取数据并赋到customFont里,代码

using UnityEngine;
using System.Collections;
using UnityEditor;

public class MyFont : Editor
{
    [MenuItem("MakeFont/Make")]
    static void Make()
    {
        Object[] objs = Selection.objects;
        if (objs.Length != 2)
        {
            Debug.LogError("请选中Custom Font文件与BMFont导出的fnt数据文件");
            return;
        }
        Font m_myFont;
        TextAsset m_data;
        if (objs[0].GetType() == typeof(TextAsset)&&objs[1].GetType()==typeof(Font))
        {
            Debug.Log("text");
            m_data = (TextAsset)objs[0];
            m_myFont = (Font)objs[1];
            Debug.Log("FontName:" + m_myFont.name + "\nData:" + m_data.name);
        }
        else if (objs[1].GetType() == typeof(TextAsset) && objs[0].GetType() == typeof(Font))
        {            
            m_data = (TextAsset)objs[1];
            m_myFont = (Font)objs[0];
            Debug.Log("FontName:"+m_myFont.name+"\nData:"+m_data.name);
        }
        else
        {
            Debug.LogError("请选中Custom Font文件与BMFont导出的fnt数据文件");
            Debug.Log("FontName:null" + "\nData:null");
            return;            
        }

        BMFont mbFont = new BMFont();
        BMFontReader.Load(mbFont, m_data.name, m_data.bytes);  // 借用NGUI封装的读取类
        CharacterInfo[] characterInfo = new CharacterInfo[mbFont.glyphs.Count];
        for (int i = 0; i < mbFont.glyphs.Count; i++)
        {
            BMGlyph bmInfo = mbFont.glyphs[i];
            CharacterInfo info = new CharacterInfo();
            info.index = bmInfo.index;
            info.uv.x = (float)bmInfo.x / (float)mbFont.texWidth;
            info.uv.y =1- (float)bmInfo.y / (float)mbFont.texHeight;
            info.uv.width = (float)bmInfo.width / (float)mbFont.texWidth;
            info.uv.height =- (float)bmInfo.height / (float)mbFont.texHeight;
            info.vert.x = (float)bmInfo.offsetX;
            info.vert.y = (float)bmInfo.offsetY;           
            info.vert.width = (float)bmInfo.width;
            info.vert.height = (float)bmInfo.height;
            info.width = (float)bmInfo.advance;
            characterInfo[i] = info;
        }
        m_myFont.characterInfo = characterInfo;

        Debug.Log("OK");
    }
}

操作方法:选中customFont文件和导出的数据文件,再点击菜单 MakeFont>>Make,这样就把数据绑定了。

接下来就是把图片丢到材质球上,再把材质球丢到customFont的Default Material里(也可以不丢)。

然后创建一个Text看下效果,

PS:如果字体重叠了,可以把Text的width加大。下面是图片和材质等的设置,可参考。

这个shader是拿的国外一个收费插件的,花了我13刀!可以向我要。

字体文件包括customFont,材质,图片。


new 发布于 2016-3-1 01:28

uGUI长按事件 Unity游戏研究

突然发现uGUI没有给出可以直接用的长按事件。。。

上个没有注释的代码,代码说明一切。

using UnityEngine;
using System.Collections;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class UILongPressEvent : MonoBehaviour,IPointerDownHandler,IPointerExitHandler,IPointerUpHandler
{
    [SerializeField]
    UnityEvent m_onLongPress = new UnityEvent();

    float interval = 0.1f;
    float longPressDelay = 0.5f;

    private bool isTouchDown = false;
    private bool isLongpress = false;
    private float touchBegin = 0;
    private float lastInvokeTime = 0;

    // Update is called once per frame
    void Update () 
    {
        if (isTouchDown)
        {
            if (isLongpress)
            {
                if (Time.time - lastInvokeTime > interval)
                {
                    m_onLongPress.Invoke();
                    lastInvokeTime = Time.time;
                }
            }
            else
            {
                if (Time.time - touchBegin > longPressDelay)
                {
                    isLongpress = true;
                }
            }
        }
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        touchBegin = Time.time;
        isTouchDown = true;
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        isTouchDown = false;
        isLongpress = false;
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        isTouchDown = false;
        isLongpress = false;
    }
}



标签: uGUI 长按

new 发布于 2016-2-25 09:40