Unity中用Mesh画一个圆环
Probuider
前几天在做一个小项目的时候,用到了Unity自带的一个包ProBuilder其中的Arch生成1/4圆。
挺好玩的,可以在直接Unity中根据需要用Mesh定制生成图形,而不用建模软件。
但是存在一个小问题,就是在使用的时候他的中心点是在生成图形的左下角。
旋转的时候不符合我的需求,我想要的是生成的时候旋转中心在圆心的位置,所以准备自己定制一个。
目标
关于Mesh生成图形的原理可以参考这篇文章,讲得虽然不算很详细,但足够了解基本概念了。
目标是生成下面图中的一个1/4空心圆柱体
我们切换到Wireframe模式下,可以看出它是有一个一个的顶点,并通过一条条的直线连接起来。那么我们如何确定这些顶点和线的位置呢?
小目标-生成一个面
其实很简单的,我们一步一步慢慢来。一次生成一整个会有点麻烦,我们可以一面一面来。只要生成了第一个面,其他的面也是类似的方法生成就好。
在前面我们提到了我们要的是生成一个圆柱体,圆柱体一个的重要性质就是可以由一个圆形叠加产生,也就是只要我们生成一个圆形,就完成了大部分的工作。
我们知道3D建模就是由一个一个的三角形组合成的,所以我们要用三角形来模拟来一个空心的圆。
在Probuilder中生成这样一个空心圆柱体用的是Arch,它有几个参数,分别是
\(\color{#1E90FF}{Radius}\) 半径,圆心到最外圈的距离
\(\color{#1E90FF}{Thickness}\) 厚度,圆心到最外圈的距离-圆心到最内圈的距离
\(\color{#1E90FF}{Depth}\) 深度
\(\color{#1E90FF}{NumberOfSides}\) 由多少个面组成,面越多越平滑,性能也越差
\(\color{#1E90FF}{DrawArchDegrees}\) 总共绘制的角度
\(\color{#1E90FF}{NumberOfSides}\)中的面是指由两个三角形一头一尾拼成的梯形,多个头大脚小的梯形拼在一起便成了我们需要的圆形。
原理已经知道了,那下一步只要确定三角形顶点的位置就OK了。至于如何确定三角形顶点的位置,我们可以再看下这张图。
是不是瞬间清晰明了,红线的交汇处就是圆心的位置,数字则是每个顶点的编号。
我们假设圆心在原点,数字0-1所在的线为180度线。\(\color{#1E90FF}{Increment}\) = \(\color{#1E90FF}{DrawArchDegrees}\)/\(\color{#1E90FF}{NumberOfSides}\)就是线与线之间的角度。每条线的角度可以由\(\color{#1E90FF}{180-Increment*i}\)得到。i为第几条线。
线上的点可以由\(\color{#1E90FF}{y = r* sinθ, y = r* cosθ}\)得到。
//顶点坐标 vertexList.Clear(); float incrementAngle = DrawArchDegrees / NumberOfSides; //小于等于是因为n+1条线才能组成n个面 for (int i = 0; i <= NumberOfSides; i++) { float angle = 180 - i * incrementAngle; float innerX = (Radius - Thickness) * Mathf.Cos(angle * Mathf.Deg2Rad); float innerY = (Radius - Thickness) * Mathf.Sin(angle * Mathf.Deg2Rad); vertexList.Add(new Vector3(innerX, innerY, 0)); float outsideX = Radius * Mathf.Cos(angle * Mathf.Deg2Rad); float outsideY = Radius * Mathf.Sin(angle * Mathf.Deg2Rad); vertexList.Add(new Vector3(outsideX, outsideY, 0)); }
在上面的代码中我们已经计算出了顶点的位置,下一步我们要做的是按顺序插入三角形顶点的位置。从Mesh这篇文章中我们可以知道,只有是三角形是正面的情况下才会被渲染。
而正反面可以通过法线的朝向进行判断,向外的面就是正面,相反的就是背面。
在Unity中,法线的朝向可以由左手法则得到。拿出左手,伸直,拇指与其他四个指头垂直,然后四指弯曲,指尖朝向循环的方向,拇指就指向法线的方向。
也就是说在上图中,我们想渲染三角形,顺序应该是类似这样的012,321, 234, 543。
//三角形索引 triangleList.Clear(); int direction = 1; for (int i = 0; i < NumberOfSides * 2; i++) { int[] triangleIndexs = getTriangleIndexs(i, direction); direction *= -1; for (int j = 0; j < triangleIndexs.Length; j++) { triangleList.Add(triangleIndexs[j]); } }
\(\color{#F08080}{getTriangleIndexs}\)代码如下
int[] getTriangleIndexs(int index, int direction) { int[] triangleIndexs = new int[3] { 0,1,2}; for (int i = 0; i < triangleIndexs.Length; i++) { triangleIndexs[i] += index; } if (direction == -1) { int temp = triangleIndexs[0]; triangleIndexs[0] = triangleIndexs[2]; triangleIndexs[2] = temp; } return triangleIndexs; }
至于uv坐标就更简单了,把内圈顶点uv坐标中的Y固定为0,外圈顶点uv坐标中的Y固定为1,而x坐标由\(\color{#1E90FF}{1/NumberOfSides}\)得到:
//UV索引 uvList.Clear(); for (int i = 0; i <= NumberOfSides; i++) { float angle = 180 - i * incrementAngle; float littleX = (1.0f / NumberOfSides) * i; uvList.Add(new Vector2(littleX, 0)); float bigX = (1.0f / NumberOfSides) * i; uvList.Add(new Vector2(bigX, 1)); }
完整代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; //[RequireComponent(typeof(MeshFilter))] //[RequireComponent(typeof(MeshRenderer))] //[ExecuteInEditMode] public class DrawArch : MonoBehaviour { public float Radius = 20.0f; //外圈的半径 public float Thickness = 10.0f; //厚度,外圈半径减去内圈半径 public float Depth = 1.0f; //厚度 public float NumberOfSides = 30.0f; //由多少个面组成 public float DrawArchDegrees = 90.0f; //要绘画多长 public Material archMaterial = null; private List<Vector3> vertexList = new List<Vector3>(); private List<int> triangleList = new List<int>(); private List<Vector2> uvList = new List<Vector2>(); // Start is called before the first frame update void Start() { GenerateVertex(); } void GenerateVertex() { //顶点坐标 vertexList.Clear(); float incrementAngle = DrawArchDegrees / NumberOfSides; //小于等于是因为n+1条线才能组成n个面 for (int i = 0; i <= NumberOfSides; i++) { float angle = 180 - i * incrementAngle; float innerX = (Radius - Thickness) * Mathf.Cos(angle * Mathf.Deg2Rad); float innerY = (Radius - Thickness) * Mathf.Sin(angle * Mathf.Deg2Rad); vertexList.Add(new Vector3(innerX, innerY, 0)); float outsideX = Radius * Mathf.Cos(angle * Mathf.Deg2Rad); float outsideY = Radius * Mathf.Sin(angle * Mathf.Deg2Rad); vertexList.Add(new Vector3(outsideX, outsideY, 0)); } //三角形索引 triangleList.Clear(); int direction = 1; for (int i = 0; i < NumberOfSides * 2; i++) { int[] triangleIndexs = getTriangleIndexs(i, direction); direction *= -1; for (int j = 0; j < triangleIndexs.Length; j++) { triangleList.Add(triangleIndexs[j]); } } //UV索引 uvList.Clear(); for (int i = 0; i <= NumberOfSides; i++) { float angle = 180 - i * incrementAngle; float littleX = (1.0f / NumberOfSides) * i; uvList.Add(new Vector2(littleX, 0)); float bigX = (1.0f / NumberOfSides) * i; uvList.Add(new Vector2(bigX, 1)); } Mesh mesh = new Mesh() { vertices = vertexList.ToArray(), uv = uvList.ToArray(), triangles = triangleList.ToArray(), }; mesh.RecalculateNormals(); gameObject.AddComponent<MeshFilter>().mesh = mesh; gameObject.AddComponent<MeshRenderer>().material = archMaterial; } int[] getTriangleIndexs(int index, int direction) { int[] triangleIndexs = new int[3] { 0,1,2}; for (int i = 0; i < triangleIndexs.Length; i++) { triangleIndexs[i] += index; } if (direction == -1) { int temp = triangleIndexs[0]; triangleIndexs[0] = triangleIndexs[2]; triangleIndexs[2] = temp; } return triangleIndexs; } }
未完待续。。。
- Unity5.3 不改变原代码,将ModifyVertices换成ModifyMesh的一个方法
- Unity 中的 Mesh 及绘制圆环
- Unity 创建一个扇形Mesh
- unity 一个mesh renderer上有两个材质球更换材质球的方法
- 运用unity中Mesh,做一个信息指向的效果。
- Unity Mesh网格编程(四) 记一个大坑 shader是不修改mesh网格顶点的
- unity, Gizmos.DrawMesh一个坑
- unity 合并skinnedMeshRenderer中遇到的一个大坑
- 带一个圆环的尼姆博弈
- 自定义View 自定义一个带箭头的圆环详解 加速 减速 暂停 变色
- unity各种报错之:Mesh.colors is out of bounds. The supplied array needs ...
- Unity 创建一个蜂窝状的图形
- Unity技巧制作一个简单的NPC
- 简单支持unity同一个项目多开的方式
- 关于unity动态添加游戏对象问题,如何动态添加一个菜单【一】
- Unity。。。刚体在c#脚本里调用rigidbody.freezePosition或者冻结任意一个轴或组合冻结
- 从iOS应用中,启动一个Unity App
- [Unity通信]一个基于socket的3DARPG网络游戏(二):消息分类处理和json的使用
- 【Unity&VS2015】VS2015的一个莫名其妙的问题
- Unity 3D 创建Mesh(二)