Unity动态渲染一个Mesh

 
在unity里动态渲染一个Mesh,需要设置MeshFilter、MeshRenderer、Mesh。
mesh负责提供渲染的网格信息,对于NGUI,最简单的UITexture的Mesh包含:四个顶点,每个顶点是UITexture的世界坐标,四个uv是显示的图片的4个角(0,0 0,1 1,1 1,0),两个三角形,四个颜色值。
MeshRenderer需要设置指定的材质球,该材质球的mainTexture是UITexture显示的图片。
下面代码实际上就可以把指定的mMeshMatTex贴图会知道屏幕上。
Vector3[] newVertices = new []{new Vector3(0,0), new Vector3(0,1),new Vector3(1,1),new Vector3(1,0),};
Vector2[] newUV= new []{new Vector2(0,0), new Vector2(0,1),new Vector2(1,1),new Vector2(1,0),};
int[] triangles = new[] {0, 1, 2, 2, 3, 0};
 
if (mFilter == null) mFilter = gameObject.GetComponent<MeshFilter>();
if (mFilter == null) mFilter = gameObject.AddComponent<MeshFilter>();
 
Mesh mesh = new Mesh();
mesh.hideFlags = HideFlags.DontSave;
 
// Do some calculations...
mesh.vertices = newVertices;
mesh.uv = newUV;
mesh.triangles = newTriangles;
mesh.name = "NGUI Test";
mFilter.mesh = mesh;
 
if (mRenderer == null) mRenderer = gameObject.GetComponent<MeshRenderer>();
if (mRenderer == null) mRenderer = gameObject.AddComponent<MeshRenderer>();
 
Shader shader = Shader.Find("Unlit/Transparent Colored");
mDynamicMat = new Material(shader);
mDynamicMat.name = "[NGUI] " + shader.name;
mDynamicMat.mainTexture = mMeshMatTex;
mRenderer.sharedMaterials = new Material[] { mDynamicMat };
1.MeshFilter MeshRenderer Mesh。
在unity中渲染一个Mesh需要包含MeshFilter、MeshRenderer两个组件以及一个Mesh。
Mesh:网格
作用:Mesh是指模型的网格,建模就是建网格。细看Mesh,可以知道Mesh的主要属性内容包括顶点坐标vertices,法线normals,纹理坐标uv、颜色colors,三角形绘制序列triangles等其他有用属性和功能。因此建网格,就是画三角形;画三角形就是定位三个点。
 
Mesh Filter: 网格过滤器
作用:把Mesh扔给MeshRender将模型或者说是几何体绘制显示出来。
 
Mesh renderer:网格渲染器
从网格过滤器中获得几何体的形状然后进行渲染。
 
Mesh Filter和Mesh renderer的作用:
In a component based system it's common to seperate concepts into seperate components. At the moment the MeshFilter doesn't have many other uses besides being a Mesh-provider for a MeshRenderer. Though the only additional "feature" is when you have a MeshFilter attached with a referenced Mesh, when you add a MeshCollider to the gameobject it automatically takes that mesh reference. This does not happen with the SkinnedMesh renderer. Mainly because it's usually pointless to have a MeshCollider on a skinned mesh as the collider mesh is not skinned anyways.
 
先来看下面一段代码:有点长
using System.Collections.Generic;
using UnityEngine;
 
public class Test: MonoBehaviour {
    // Start is called before the first frame update
    public Texture mUITexture;//UITexture的图片名字,用于显示图片
    public string mSpriteName;//Sample/Slider模式下的图片名字,用于显示图片
    public string mTiledName; //Tiled模式下的图片名字,用于显示图片
    public UIAtlas mAtlas;
 
    [Range(0,1)]
    public float mFillAmount = 0.5f;//Filled模式的进度值
 
    private Texture mMeshMatTex;
    private MeshFilter mFilter;
    private MeshRenderer mRenderer;
    private Material mDynamicMat;
    private Vector4 drawingDimensions = new Vector4(0, 0, 200, 400);
    
    void Start() {
        CreateMesh();
        SetMeshRender();
    }
 
    /// <summary>
    /// 参考UIDrawCall.GenerateCachedIndexBuffer生成newTriangles
    /// 参考UIDrawCall.UpdateGeometry生成Mesh并设置参数
    /// </summary>
    private void CreateMesh() {
        Vector3[] newVertices;
        Vector2[] newUV;
        //GetUITextureVertices(out newVertices, out newUV);
        //GetNormalSpriteVertices(out newVertices, out newUV);
        //GetSliderSpriteVertices(out newVertices, out newUV);
        //GetTiledSpriteVertices(out newVertices, out newUV);
        GetFilledSpriteVertices(out newVertices, out newUV);
        
        int indexCount = (newVertices.Length >> 1) * 3; //四个顶点构成两个三角形,共2*3 = 6个顶点
        int[] newTriangles = new int[indexCount];
        int index = 0;
        
        for (int i = 0; i < newVertices.Length; i += 4)
        {
            newTriangles[index++] = i;
            newTriangles[index++] = i + 1;
            newTriangles[index++] = i + 2;
 
            newTriangles[index++] = i + 2;
            newTriangles[index++] = i + 3;
            newTriangles[index++] = i;
        }
        
        if (mFilter == null) mFilter = gameObject.GetComponent<MeshFilter>();
        if (mFilter == null) mFilter = gameObject.AddComponent<MeshFilter>();
        
        Mesh mesh = new Mesh();
        mesh.hideFlags = HideFlags.DontSave;
 
        // Do some calculations...
        mesh.vertices = newVertices;
        mesh.uv = newUV;
        mesh.triangles = newTriangles;
        mesh.name = "NGUI Test";
        mFilter.mesh = mesh;
    }
 
    /// <summary>
    /// 显示一个普通Texture,顶点数4
    /// </summary>
    /// <param name="vert"></param>
    /// <param name="uvs"></param>
    private void GetUITextureVertices(out Vector3[] vert, out Vector2[] uvs) {
        Vector4 v = drawingDimensions;
        vert = new []{new Vector3(v.x,v.y), new Vector3(v.x,v.w),
            new Vector3(v.z,v.w),new Vector3(v.z,v.y), };
        uvs = new []{new Vector2(0,0), new Vector2(0,1),new Vector2(1,1),new Vector2(1,0), };
        mMeshMatTex = mUITexture;
    }
    
    /// <summary>
    /// 显示Sample的Sprite,参考UISprite.OnFill,顶点数4
    /// </summary>
    /// <param name="vert"></param>
    /// <param name="uvs"></param>
    private void GetNormalSpriteVertices(out Vector3[] vert, out Vector2[] uvs) {
        Vector4 v = drawingDimensions;
        vert = new []{new Vector3(v.x,v.y), new Vector3(v.x,v.w),
            new Vector3(v.z,v.w),new Vector3(v.z,v.y), };
 
        UISpriteData sd = mAtlas.GetSprite(mSpriteName);
        var tex = mAtlas.texture;
        mMeshMatTex = tex;
        
        var outer = new Rect(sd.x, sd.y, sd.width, sd.height);
        outer = NGUIMath.ConvertToTexCoords(outer, tex.width, tex.height);
        uvs = new []{new Vector2(outer.xMin,outer.yMin), new Vector2(outer.xMin,outer.yMax),
            new Vector2(outer.xMax,outer.yMax),new Vector2(outer.xMax,outer.yMin), };
    }
    
    /// <summary>
    /// 显示Slider的Sprite,参考UIBasicSprite.SlicedFill。九宫格模式
    /// 顶点数4*9(九宫格分成的9块区域,每块区域的四个顶点)
    /// </summary>
    /// <param name="vert"></param>
    /// <param name="uv"></param>
    private void GetSliderSpriteVertices(out Vector3[] vert, out Vector2[] uv) 
    {
        UISpriteData sd = mAtlas.GetSprite(mSpriteName);
        var tex = mAtlas.texture;
        mMeshMatTex = tex;
        
        var outer = new Rect(sd.x, sd.y, sd.width, sd.height);
        var inner = new Rect(sd.x + sd.borderLeft, sd.y + sd.borderTop,
            sd.width - sd.borderLeft - sd.borderRight,
            sd.height - sd.borderBottom - sd.borderTop);
        
        outer = NGUIMath.ConvertToTexCoords(outer, tex.width, tex.height);
        inner = NGUIMath.ConvertToTexCoords(inner, tex.width, tex.height);
        
        Vector2[] mTempPos = new Vector2[4];
        Vector2[] mTempUVs = new Vector2[4];
        Vector4 v =drawingDimensions;//设置渲染的Sprite长高为100
        Vector4 br = new Vector4(sd.borderLeft, sd.borderBottom, sd.borderRight, sd.borderTop);
        
        mTempPos[0] = new Vector2(v.x, v.y);
        mTempPos[1] = new Vector2(v.x + br.x, v.y + br.y);
        mTempPos[2] = new Vector2(v.z - br.z, v.w - br.w);
        mTempPos[3] = new Vector2(v.z, v.w);
 
        mTempUVs[0] = new Vector2(outer.xMin, outer.yMin);
        mTempUVs[1] = new Vector2(inner.xMin, inner.yMin);
        mTempUVs[2] = new Vector2(inner.xMax, inner.yMax);
        mTempUVs[3] = new Vector2(outer.xMax, outer.yMax);
        
        List<Vector3> verts = new List<Vector3>();
        List<Vector2> uvs = new List<Vector2>();
        
        for (int x = 0; x < 3; ++x)
        {
            int x2 = x + 1;
 
            for (int y = 0; y < 3; ++y)
            {
                int y2 = y + 1;
 
                verts.Add(new Vector3(mTempPos[x].x, mTempPos[y].y));
                verts.Add(new Vector3(mTempPos[x].x, mTempPos[y2].y));
                verts.Add(new Vector3(mTempPos[x2].x, mTempPos[y2].y));
                verts.Add(new Vector3(mTempPos[x2].x, mTempPos[y].y));
 
                uvs.Add(new Vector2(mTempUVs[x].x, mTempUVs[y].y));
                uvs.Add(new Vector2(mTempUVs[x].x, mTempUVs[y2].y));
                uvs.Add(new Vector2(mTempUVs[x2].x, mTempUVs[y2].y));
                uvs.Add(new Vector2(mTempUVs[x2].x, mTempUVs[y].y));
            }
        }
 
        vert = verts.ToArray();
        uv = uvs.ToArray();
    }
    
    /// <summary>
    /// 显示Tiled的Sprite,参考UIBasicSprite.TiledFill。平铺模式
    /// 重复次数Tiled.x = widget.width/(tex.width + padding.left + padding.right)
    /// 重复次数Tiled.y = widget.height/(tex.height + padding.top + padding.bottom)
    /// 顶点数4 * Mathf.Ceil(Tiled.x) * Mathf.Ceil(Tiled.y)
    /// </summary>
    /// <param name="vert"></param>
    /// <param name="uv"></param>
    private void GetTiledSpriteVertices(out Vector3[] vert, out Vector2[] uv) 
    {
        UISpriteData sd = mAtlas.GetSprite(mTiledName);
        var tex = mAtlas.texture;
        mMeshMatTex = tex;
        
        var inner = new Rect(sd.x + sd.borderLeft, sd.y + sd.borderTop,
            sd.width - sd.borderLeft - sd.borderRight,
            sd.height - sd.borderBottom - sd.borderTop);
        
        inner = NGUIMath.ConvertToTexCoords(inner, tex.width, tex.height);
        
        Vector4 u = new Vector4(inner.xMin, inner.yMin, inner.xMax, inner.yMax);
        Vector4 p = new Vector4(sd.paddingLeft,sd.paddingBottom,sd.paddingRight,sd.paddingTop);
        var size = new Vector2(sd.width, sd.height);
        Vector4 v = drawingDimensions;//设置渲染的Sprite长高为100
        
        List<Vector3> verts = new List<Vector3>();
        List<Vector2> uvs = new List<Vector2>();
        
        float x0 = v.x;
        float y0 = v.y;
        float u0 = u.x;
        float v0 = u.y;
        
        while (y0 < v.w)
        {
            y0 += p.y;
            x0 = v.x;
            float y1 = y0 + size.y;
            float v1 = u.w;
 
            if (y1 > v.w) //超出上边界时处理
            {
                v1 = Mathf.Lerp(u.y, u.w, (v.w - y0) / size.y);
                y1 = v.w;
            }
 
            while (x0 < v.z)
            {
                x0 += p.x;
                float x1 = x0 + size.x;
                float u1 = u.z;
 
                if (x1 > v.z)//超出左边界时处理
                {
                    u1 = Mathf.Lerp(u.x, u.z, (v.z - x0) / size.x);
                    x1 = v.z;
                }
 
                verts.Add(new Vector3(x0, y0));
                verts.Add(new Vector3(x0, y1));
                verts.Add(new Vector3(x1, y1));
                verts.Add(new Vector3(x1, y0));
 
                uvs.Add(new Vector2(u0, v0));
                uvs.Add(new Vector2(u0, v1));
                uvs.Add(new Vector2(u1, v1));
                uvs.Add(new Vector2(u1, v0));
                
 
                x0 += size.x + p.z;
            }
 
            y0 += size.y + p.w;
        }
        
        vert = verts.ToArray();
        uv = uvs.ToArray();
    }
    
    /// <summary>
    /// 显示Filled的Sprite,参考UIBasicSprite.FilledFill。进度条模式
    /// 顶点数4*1(实际只有一块经过裁切后的区域,不显示区域直接被丢弃了),这边只展示最简单的FillDirection.Horizontal模式
    /// </summary>
    /// <param name="vert"></param>
    /// <param name="uv"></param>
    private void GetFilledSpriteVertices(out Vector3[] vert, out Vector2[] uv) {
        Vector4 v = drawingDimensions;
        UISpriteData sd = mAtlas.GetSprite(mSpriteName);
        var tex = mAtlas.texture;
        mMeshMatTex = tex;
        
        var outer = new Rect(sd.x, sd.y, sd.width, sd.height);
        outer = NGUIMath.ConvertToTexCoords(outer, tex.width, tex.height);
        Vector4 u = new Vector4(outer.xMax, outer.yMin, outer.xMin, outer.yMax);
        
        float fill = (u.z - u.x) * mFillAmount;
        v.z = v.x + (v.z - v.x) * mFillAmount;
        u.z = u.x + fill;
        
        vert = new Vector3[4];
        uv = new Vector2[4];
        vert[0] = new Vector2(v.x, v.y);
        vert[1] = new Vector2(v.x, v.w);
        vert[2] = new Vector2(v.z, v.w);
        vert[3] = new Vector2(v.z, v.y);
 
        uv[0] = new Vector2(u.x, u.y);
        uv[1] = new Vector2(u.x, u.w);
        uv[2] = new Vector2(u.z, u.w);
        uv[3] = new Vector2(u.z, u.y);
    }
 
    /// <summary>
    /// 参考UIDrawCall.RebuildMaterial生成一个Mesh,这边忽略了裁剪的逻辑
    /// </summary>
    private void SetMeshRender() 
    {
        if (mRenderer == null) mRenderer = gameObject.GetComponent<MeshRenderer>();
        if (mRenderer == null) mRenderer = gameObject.AddComponent<MeshRenderer>();
        
        Shader shader = Shader.Find("Unlit/Transparent Colored");
        mDynamicMat = new Material(shader);
        mDynamicMat.name = "[NGUI] " + shader.name;
        mDynamicMat.mainTexture = mMeshMatTex;
        mRenderer.sharedMaterials = new Material[] { mDynamicMat };
    }
}
测试方式:在场景中创建空对象并挂载Test脚本,注意设置如下参数,并注意相机的位置,就可以正确测试NGUI对UITexture、UISprite的SampleSliderTiledFilled的模式的实现。
2.CreateMesh。
创建了一个Mesh,并设置了Mesh渲染所必需的的vertices、uv、triangles参数。
动态挂载了Mesh渲染所依托的MeshFilter的组件。
3.SetMeshRender。
创建了一个MeshRenderer,并设置了render的材质球属性,包括shader、mainTexture。
4.三角形计算:三角形实际指示了Mesh渲染的正反面。
int indexCount = (newVertices.Length >> 1) * 3; //四个顶点构成两个三角形,共2*3 = 6个顶点
int[] newTriangles = new int[indexCount];
int index = 0;
 
for (int i = 0; i < newVertices.Length; i += 4)
{
    newTriangles[index++] = i;
    newTriangles[index++] = i + 1;
    newTriangles[index++] = i + 2;
 
    newTriangles[index++] = i + 2;
    newTriangles[index++] = i + 3;
    newTriangles[index++] = i;
}
5.根据不同Type计算顶点值和UV值。
具体计算看上面的注释就很清楚了。
GetUITextureVertices(out newVertices, out newUV);
GetNormalSpriteVertices(out newVertices, out newUV);
GetSliderSpriteVertices(out newVertices, out newUV);
GetTiledSpriteVertices(out newVertices, out newUV);
GetFilledSpriteVertices(out newVertices, out newUV);
当然,NGUI做了很多额外的工作,这边只是简单地展示其显示原理,方便后续对NGUI的源码的学习。
原文地址:https://www.cnblogs.com/wang-jin-fu/p/13508953.html