网格数据含义
顶点:模型的点
纹理:把图片覆盖或投射到物体表面方法。定义模型中顶点和贴图之间的位置关系,如何把一张图片贴在模型表面
三角形序列:组成三角形面的顶点顺序. 描述哪三个顶点构成一个三角形,比如说取模型中的第3个顶点和第9个顶点和第99个顶点构成一个三角形.
法线:直于三角形表面的方向的向量
Unity3d Mesh类
顶点数组 vertices :保存的是模型所有顶点的数组。
纹理数组 uv :保存的是所有纹理坐标的数组
三角形序列数组 triangles :保存顶点数组下标的数组
法线数组 normals:保存三角形法线向量的数组。
在一个模型中
顶点数量 和 UV数量 和 三角形序列数组相等,mesh.vertices.Length == mesh.uv.Length == mesh.triangles.Length
模型的三角形数量等于 顶点除以三就等于三角形的数量.(mesh.vertices.Length / 3)
法线数量和三角形数量相等 . (mesh.vertices.Length / 3) == normals.Length
三角形序列数组的长度必须是3的倍数,(mesh.triangles.Length % 3) == 0
顶点坐标 vertices
如上图,是一个正方形面片
有4个顶点坐标:
第0个顶点(0,0,0) 注:这里是按照顶点数组中的数组下标,顶点数组中第一个顶点是0
第1个顶点(0,0,1)
第2个顶点(0,1,1)
第3个顶点(0,1,0)
顶点数组 Vector3[] vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 0) };
每三个顶点组成一个三角形,
这个正方形面片有2个三角形组成. 注意:任意复杂的模型都是由三角形面组成.
那 描述哪三个顶点构成一个三角形?
由 第0个顶点、第3个顶点、第2个顶点, 组成第一个三角形
由 第0个顶点、第2个顶点、第1个顶点, 组成第二个三角形
简写
(0,3,2)
(0,2,1)
也可以从其他点开始取,只要按照顺时针取,如:
(3,2,0)
(2,1,0)
或者
(2,0,3)
(1,0,2)
三角形序列数组
int[] triangles = new int[] { 0,3,2,0,2,1};
纹理坐标 UV
uv坐标系是 (x,y) 二维坐标,取值范围是从0.00f到1.00f(右上角,最大值),从左到右,自下而上增加的坐标系。
把图片贴到这个坐标上,映射为下面坐标,不管图片长宽多大 ,也就是把图片每个颜色映射为一个uv点
贴图texture绘制时uv要和顶点一一对应绘制 , 从该UV点取颜色
顶点(0,0,0) => uv(0,0)
顶点(0,1,0) => uv(0,1)
顶点(0,1,1) => uv(1,1)
顶点(0,0,1) => uv(1,0)
将点(u,v)对应的彩色参数值映射到相应的三维曲面(s,t)上,使三维曲面表面得到彩色图案。
绘制物体表面上一点时,采用响应的纹理图案中相应点的颜色值。
用数学函数定义随机高度场,配合着法线,来生成简单的二维纹理图案,从而在模型表面粗糙纹理
法线向量 normals
垂直于三角形表面的方向
值类型【Vector3[] normals】,
计算法线的方法
Vector3 ab = b - a;
Vector3 ac = c - a;
Vector3 normal = Vector3.Cross(ab,ac);//蓝色朝上的向量,就是法线
Vector3 opposite = Vector3.Cross(ac,ab);//红色色朝下的向量
三角形序列 triangles
取哪三个顶点构成一个三角形,比如说取第3个顶点和第9个顶点和第99个顶点 构成三角形.这个顺序要安装顺时针
值类型【int[] triangles】
也叫三角面 、三角形、三角形面、顶点序列、三角面的顶点顺序、三角形面顶点序列、顶点的渲染顺序。
Unity里是左手坐标系,取顶点顺序安装顺时针时表示正面,逆时针表示背面。而unity3d在渲染时默认只正面,背面是看不见的。因为只渲染三角形一面,也就是外面,里面不渲染.所以模型内部是不渲染的,如果需要双面渲染 :请用双面渲染sharder.
每三个顶点坐标按照顺时针的顺序组成一个三角形面.保存的是顶点坐标数组(vertices)的下标。也就是说模型的三角形数量等于 顶点除以三就等于三角形的数量.
模型绘制过程
模型空间 => 世界空间 => 摄像机空间 =>齐次坐空间
从模型空间(Model Space)(顶点都相对于模型的中心定义)
世界空间(World Space)顶点都相对于世界空间中心定义)
摄像机空间(Camera Space)(顶点都相对于摄像机定义)摄像机所观察到的圆锥体空间(上图中以近似的方式呈现),通过Z的取值近剪裁、远剪裁。
齐次坐空间(Homogeneous Space)(顶点都在一个小立方体中定义。立方体内的物体都会在屏幕上显示)
照都是在世界坐标系中进行计算,这时用到的法向量也应该是定义在世界坐标系中。所以我们需要设法将顶点法向量从模型坐标系中转换到世界坐标系中网格模型的顶点的法向量一开始是定义在模型坐标系中的,在将模型布置在场景中后,根据光照模型计算颜色时需要用到顶点法向量,由于光。
我们已经知道通过缩放、旋转、平移三种基本操作合成的model矩阵可以将网格顶点坐标从模型坐标系中转换到世界坐标系中。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MeshTest : MonoBehaviour
{
public MeshRenderer meshRenderer;
public MeshFilter meshFilter;
public Mesh mesh;
void Start()
{
meshFilter = GetComponent<MeshFilter>();
if (meshFilter == null)
{
meshFilter = gameObject.AddComponent<MeshFilter>();
}
meshRenderer = GetComponent<MeshRenderer>();
if (meshRenderer == null)
{
meshRenderer = gameObject.AddComponent<MeshRenderer>();
}
meshFilter.mesh = CreatMeshTrangle();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
meshFilter.mesh =CreatMeshSquare();
}
if (Input.GetKeyDown(KeyCode.T))
{
meshFilter.mesh = CreatMeshTrangle();
}
}
/// <summary>
/// 创建正方形面片
/// </summary>
/// <returns></returns>
static Mesh CreatMeshSquare()
{
var mesh = new Mesh();
float z = 0;
mesh.vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 0) };
mesh.uv = new Vector2[] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0) };
mesh.triangles = new int[] { 0, 1, 2, 0, 2, 3 };
mesh.name = "正方形面片";
return mesh;
}
/// <summary>
/// 创建三角形面片
/// </summary>
/// <returns></returns>
static Mesh CreatMeshTrangle()
{
var mesh = new Mesh();
mesh.vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 0, 0) };
mesh.triangles = new int[] { 0, 1, 2 };
mesh.uv = new Vector2[] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0) };
mesh.name = "三角形面片";
return mesh;
}
}
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;
/// <summary>
/// 显示mesh信息,并且修改
/// </summary>
public class ShowMeshDetail : MonoBehaviour
{
public MeshFilter meshFilter;
public Mesh mesh;
public bool openFixStart = false;
public bool openFixOver = false;
/// <summary>
/// 顶点坐标
/// </summary>
[HeaderAttribute("顶点坐标")]
public Vector3[] vertices;
/// <summary>
/// 三角形序列
/// </summary>
[HeaderAttribute("三角形序列")]
public int[] triangles;
/// <summary>
/// 纹理坐标
/// </summary>
[HeaderAttribute("纹理坐标")]
public Vector2[] uv;
/// <summary>
/// 法线
/// </summary>
[HeaderAttribute("法线")]
public Vector3[] normals;
// Use this for initialization
Vector3 LineOffset = new Vector3(0, 0, 0);
private List<int> AllLinePoints = new List<int>();
private List<int> DrawLine = new List<int>();
public bool isInit = false;
void Start()
{
openFixOver = openFixStart = isInit = false;
Init();
}
void Init()
{
if (isInit) return;
meshFilter = GetComponent<MeshFilter>();
if (meshFilter != null && meshFilter.mesh != null)
{
UpdateValue();
LineOffset.z = mesh.bounds.size.z == 0 ? -1 : mesh.bounds.size.z * -2;
for (int i = 0, imax = triangles.Length; i < imax; ++i)
{
AllLinePoints.Add(triangles[i]);
}
AllLinePoints.Add(AllLinePoints[0]);
DrawLine.Add(AllLinePoints[0]);
DrawLine.Add(AllLinePoints[1]);
isInit = true;
}
}
Vector3 TransformPoint(int i)
{
return transform.TransformPoint(vertices[i]) + LineOffset;
}
void UpdateValue()
{
mesh = meshFilter.mesh;
vertices = mesh.vertices;
triangles = mesh.triangles;
uv = mesh.uv;
normals = mesh.normals;
}
void NewMesh()
{
var new_mesh = new Mesh();
new_mesh.vertices = vertices;
new_mesh.triangles = triangles;
new_mesh.uv = uv;
//重新计算mesh.bounds,通过mesh.bounds可访问size,center等一些网格的属性
new_mesh.RecalculateBounds();
//自动将每个顶点的法线调整到正确方向,既是与面的朝向一致
new_mesh.RecalculateNormals();
mesh = meshFilter.mesh = new_mesh;
}
// Update is called once per frame
void Update()
{
Init();
if (meshFilter == null || mesh == null || MeshIsSafe() == false) return;
if (openFixOver)
{
openFixStart = false;
}
if (openFixStart)
{
return;
}
if (openFixOver)
{
NewMesh();
openFixOver = false;
}
UpdateValue();
ClickTrianglesMoveIndexShow();
}
/// <summary>
/// 点击鼠标左键,通过线条 演示 triangles 索引顺序
/// </summary>
void ClickTrianglesMoveIndexShow()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
if (DrawLine.Count == AllLinePoints.Count)
{
DrawLine.Clear();
DrawLine.Add(AllLinePoints[0]);
DrawLine.Add(AllLinePoints[1]);
}
else
{
DrawLine.Add(AllLinePoints[DrawLine.Count]);
}
}
for (int ip = 0; ip < DrawLine.Count - 1; ip++)
{
Debug.DrawLine(TransformPoint(DrawLine[ip]), TransformPoint(DrawLine[ip + 1]) , Color.green);
}
}
void OnGUI()
{
for (int ip = 0; ip < DrawLine.Count; ip++)
{
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.red;
var sb= "index:" + ip;
Handles.Label(TransformPoint(DrawLine[ip]), sb.ToString(), style);
}
}
/// <summary>
/// 验证 mesh 数据长度是否正确
/// </summary>
/// <param name="mesh"></param>
bool MeshIsSafe()
{
var vertex_count = mesh.vertices.Length;
var uv_count = mesh.uv.Length;
var triangle_count = mesh.triangles.Length;
bool isSafe = vertex_count > 0
&& vertex_count == uv_count //顶点和uv 11对应
&& (triangle_count % 3) == 0;//三角形大小必须是3的倍数
if (isSafe == false)
{
Debug.LogError("mesh 数据错误");
}
return isSafe;
}
}