Unity动态渲染一个Mesh
目录:NGUI源码学习
在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的Sample\Slider\Tiled\Filled的模式的实现。
|
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的源码的学习。
一直想把之前工作、学习时记录的文档整理到博客上,一方面温故而知新,一方面和大家一起学习 -程序小白