【Unity编辑器】UVPreview扩展

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/mobilebbki399/article/details/78359703
最近为美术开发一个可以直接在unity中预览uv的编辑器,考虑到实用性,直接扩展了GameObject的Inspector,在GameObject的Preview中增加绘制UV的预览,支持查看模型光照贴图的UV布局:

效果:
UV预览:


光照贴图UV预览:


关键技术:几何着色器,C#反射

核心功能即UV的渲染,因此放到最前面讲:
第一步、uvmesh和uv绘制shader
最开始打算使用GL或Handles.DrawLine来绘制( 注意Handles.DrawLine实际上就是封装了GL而已),但是对于面数高的模型效率不太高,所以考虑直接用集合着色器。可以通过mesh的uv信息生成一个用于渲染uv的mesh,也可以考虑直接在着色器中计算uv来渲染。
另外注意:由于几何着色器的特性需要target4.0,且PC上需要DX10以上才可以使用,如果当前Build Setting里并非PC平台,或没设置里没有勾选DX11,或这Graphics Emulation中勾选了OpenGl es2.0,则可能导致渲染uv的shader无法工作,不过unity为我们提供了一个Shader Tag: “ForceSupported”=”True”,加上这个标签后,除非你的显卡本身不支持几何着色器或不支持DX,否则不管当前的设置是什么都会强制支持。

UV渲染shader:
Tags{ "RenderType" = "Transparent" "Queue"="Transparent" "ForceSupported"="True" }
Pass
{
    cull off
    zwrite off
    blend srcalpha oneminussrcalpha
    CGPROGRAM
    #pragma vertex vert
    #pragma geometry geom
    #pragma fragment frag
    #pragma exclude_renderers opengl
    #pragma target 4.0
         
    #include "UnityCG.cginc"
 
    struct appdata
    {
        float4 vertex : POSITION;
    };
 
    struct v2g
    {
        float4 vertex : SV_POSITION;
        float4 worldPos : TEXCOORD0;
    };
 
    struct g2f {
        float4 vertex : SV_POSITION;
        float4 worldPos : TEXCOORD0;
    };
 
    float4 _Color;
    uniform float4x4 clipMatrix;
 
    v2g vert(appdata v)
    {
        v2g o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.worldPos = mul(unity_ObjectToWorld, v.vertex);
        return o;
    }
 
    [maxvertexcount(5)]
    void geom(triangle v2g i[3], inout LineStream<g2f> os)
    {
        g2f o;
 
        o.vertex = i[0].vertex;
        o.worldPos = i[0].worldPos;
        os.Append(o);
 
        o.vertex = i[1].vertex;
        o.worldPos = i[1].worldPos;
        os.Append(o);
 
        o.vertex = i[2].vertex;
        o.worldPos = i[2].worldPos;
        os.Append(o);
 
        os.RestartStrip();
 
        o.vertex = i[0].vertex;
        o.worldPos = i[0].worldPos;
        os.Append(o);
 
        o.vertex = i[2].vertex;
        o.worldPos = i[2].worldPos;
        os.Append(o);
         
    }
 
    fixed4 frag(g2f i) : SV_Target
    {  
        float4 clipPos = mul(clipMatrix, i.worldPos);
        if (clipPos.x > 1 || clipPos.x < 0 || clipPos.y>1 || clipPos.y < 0)
            discard;
        return _Color;
    }
    ENDCG
}

第二步:绘制UV:
这一步我首先根据mesh的uv信息计算并生成一个uvmesh,即一个直接将uv坐标作为顶点坐标的mesh(当然这一步可以直接在shader中完成)。
然后直接使用Graphics.DrawMeshNow来绘制uvmesh,这里有一个难点,GUI绘制传递的是一个Rect,而绘制mesh需要一个矩阵,并且在uvpreview编辑器中,我需要保证uv托盘始终是长宽比为1,而且需要可以缩放的,因此这里就涉及到一个将Rect转换为2D GUI绘制矩阵的问题,代码如下:

private void DrawUVMesh(Rect rect, int uvID, int pass)
   {
 
        Matrix4x4 matrix = default(Matrix4x4);
        //计算并设置裁剪矩阵
        m_BoardLineMaterial.SetMatrix("clipMatrix", GetGUIClipMatrix(rect));
 
        //非光照贴图布局模式下直接计算绘制矩阵
        if (!m_LightMapLayoutMode)
            matrix = RefreshMatrix(rect);
        for (int i = 0; i < m_UVDatas.Count; i++)
        {
            if (m_UVDatas[i].disable)
                continue;
            if (m_UVDatas[i].target == null)
                continue;
            m_BoardLineMaterial.SetPass(pass);
            Mesh mesh = null;
            if (m_LightMapLayoutMode)
            {
                //光照贴图布局模式下需要根据每个Renderer的LightMapScaleOffset来计算绘制矩阵
                Renderer renderer = m_UVDatas[i].target.GetComponent<Renderer>();
                if (renderer.lightmapIndex != LightMapIndex)
                    continue;
                Vector4 lmST = renderer.lightmapScaleOffset;
 
                matrix =
                    RefreshMatrix(rect, lmST.z, lmST.w, lmST.x, lmST.y);
                mesh = m_UVDatas[i].uvMeshs[1];
                if (mesh == null)
                    mesh = m_UVDatas[i].uvMeshs[0];
            }
            else
            {
                mesh = m_UVDatas[i].uvMeshs[uvID];
            }
            if (mesh)
                Graphics.DrawMeshNow(mesh, matrix);
        }
    }

矩阵转换相关函数:
/// <summary>
    /// 绘制矩阵计算
    /// </summary>
    /// <param name="r"></param>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="w"></param>
    /// <param name="h"></param>
    /// <returns></returns>
    private Matrix4x4 RefreshMatrix(Rect r, float x = 0, float y = 0, float w = 1, float h = 1)
    {
        //该矩阵会在绘制区域r发生变化时更新,并根据r的宽高比自适应绘制区域
        r.x = r.x - (uvPanelScale.x - 1) * r.width / 2;
        r.y = r.y - (uvPanelScale.y - 1) * r.height / 2;
        r.x += uvPanelPosition.x;
        r.y += uvPanelPosition.y;
        r.width *= uvPanelScale.x;
        r.height *= uvPanelScale.y;
        return GetGUISquareMatrix(r, x, y, w, h);
    }
 
    private static Matrix4x4 GetGUISquareMatrix(Rect r, float x = 0, float y = 0, float w = 1, float h = 1)
    {
        //该矩阵会在绘制区域r发生变化时更新,并根据r的宽高比自适应绘制区域
        Matrix4x4 m_Matrix = new Matrix4x4();
        float aspect = r.width / r.height;
        if (aspect > 1)
        {
            m_Matrix.m00 = r.height * w;
            m_Matrix.m03 = r.x + r.width / 2 - r.height / 2 + r.height * x;
            m_Matrix.m11 = -r.height * h;
            m_Matrix.m13 = r.y + r.height - y * r.height;
        }
        else
        {
            m_Matrix.m00 = r.width * w;
            m_Matrix.m03 = r.x + x * r.width;
            m_Matrix.m11 = -r.width * h;
            m_Matrix.m13 = r.y + r.height / 2 + r.width / 2 - y * r.width;
        }
        m_Matrix.m33 = 1;
        return m_Matrix;
    }
 
    /// <summary>
    /// 通过GUI绘制区域获取GUI裁剪矩阵计算
    /// </summary>
    /// <param name="r">绘制区域</param>
    /// <returns></returns>
    public static Matrix4x4 GetGUIClipMatrix(Rect r)
    {
        //fx = (x-r.x)/r.width;
        //fy = (y-r.y)/r.height;
        Matrix4x4 matrix = new Matrix4x4();
        matrix.m00 = 1 / r.width;
        matrix.m03 = -r.x / r.width;
        matrix.m11 = 1 / r.height;
        matrix.m13 = -r.y / r.height;
        matrix.m33 = 1;
        return matrix;
    }


其中GetGUIClipMatrix的作用是计算一个GUI裁剪矩阵,并传递到Shader中,保证uv超出绘制矩形的区域可以裁剪掉。

第三步、扩展GameObjectInspector
我们实现某些代码的时候需要扩展其Inspector界面,可以通过继承Editor类来实现,但是注意到unity本身已经为GameObject实现了Inspector类:GameObjectInspector,但该类是内部类,我们无法扩展:


这时候就要用上我们CSharp强大的反射功能,来实现不继承GameObjectInspector的情况下扩展默认GameObjectInspector了:
实际上这里真正需要用到反射的部分只有两个:
反射1.从UnityEditor程序集中反射获取到UnityEditor.GameObjectInspector类,并使用该类来构造GameObjectInspector:
System.Type gameObjectorInspectorType = typeof (Editor).Assembly.GetType("UnityEditor.GameObjectInspector");
m_GameObjectInspector = Editor.CreateEditor(target, gameObjectorInspectorType);

之后我们重载Editor类的大部分方法,由于我们只扩展DrawPreview方法,所以其它重载的方法直接调用刚刚获取到的GameObjectInspector的默认行为就行了。
这里唯一需要注意的是OnHeaderGUI方法:


可以看到OnHeaderGUI是个受保护的方法,因此即便我们重载了它,也没办法调用GameObjectInspector的默认OnHeaderGUI,这时就必须再次用到反射了。

反射2.从GameObjectInspector类中反射OnHeaderGUI方法:

void OnEnable()
{
     System.Type gameObjectorInspectorType = typeof (Editor).Assembly.GetType("UnityEditor.GameObjectInspector"); 
     m_OnHeaderGUI = gameObjectorInspectorType.GetMethod("OnHeaderGUI", BindingFlags.NonPublic | BindingFlags.Instance);
     m_GameObjectInspector = Editor.CreateEditor(target, gameObjectorInspectorType);
}
 
protected override void OnHeaderGUI()
{
    if (m_OnHeaderGUI != null)
    {
        m_OnHeaderGUI.Invoke(m_GameObjectInspector, null);
    }
}

这样就可以实现在在不继承GameObjectInspector的情况下来扩展GameObjectInspector的功能了。


猜你喜欢

转载自blog.csdn.net/mobilebbki399/article/details/78359703