• 顶点
  • 三角形
  • UV
  • 法线
  • 例子:双面网格的实现
  • 材质


顶点

顶点是网格最基础的组成部分,可通过mesh.vertices获取和赋值。
mesh.vertices是一个Vector3的数组,每个Vector3为此顶点与此游戏物体的相对坐标(local position)。
顶点的位置,数量没有任何限制。几个同样的顶点可以组合成若干不同形状,不同数量的三角形。

// 手动设置顶点:
mesh = new Mesh ();
Vector3[] v3s=new Vector3[3];
v3s [0] = new Vector3 (0, 0, 0);
v3s [1] = new Vector3 (0, 1, 0);
v3s [2] = new Vector3 (1, 0, 0);
mesh.vertices = v3s;

三角形

triangles是一个int数组,这个数组是包含顶点数组索引的三角形列表。
三角形数组的大小是3的倍数,每三个int代表的是由哪三个顶点并由什么顺序(朝向)来组成一个三角形。
mesh.triangles决定了网格中的三角形的形状和朝向。

// 手动设置三角形:
int[] index = new int[3];
index [0] = 0;
index [1] = 1;
index [2] = 2;
mesh.triangles = index;

等号右侧的0,1,2表明网格中将会有一个以 mesh.vertices[0],mesh.vertices[1],和mesh.vertices[2]组成的三角形。
此三角形的形状有此三个顶点的位置决定,而它的朝向则由此三个顶点的旋转方向决定。
三角形的三个点顺时针的是朝向我们,逆时针则背对我们。
任何一个三角形必须单独设定顶点组合顺序,且此顺序与其他三角形不发生关系。
mesh内的顶点可以用来组合三角形,也可不用来组合三角形,此处无绝对的对应关系。

UV

uv坐标,简单的来讲就是某个点对应于贴图中的坐标。uv坐标系是从0到1,从左到右,自下而上增加的坐标系。
无数值限制(材质上的uv值一般是0-1,当进行采样的uv值大于1时会按取1的余数来计算)。
uv值用处很多,例如材质texture的采样,或是利用它的一些特性(范围0-1;同一个uv区域每个像素的uv值皆不同)进行GPU内的一些计算。

mesh.uv作为一个Vector2数组与mesh.vertices的长度必须一致,每个uv的顺序与vertices的顺序也是一一对应>,既uv[0]代表的是vertices[0]的uv值。而与三角形组成的顺序无关。

当每个顶点被赋予uv值后,在片段着色阶段,片段着色器将会根据顶点的uv值将此三角形覆盖的每个像素的uv值进行自动插值。
uv插值是以三角形为单位进行的,既三角形ABC内各个像素的uv值与其他周边三角形各顶点的uv值是无关的。

法线

可通过mesh.normals获取。如果网格不包含法线,一个空的数组将会被返回。
给网格赋值顶点并组成三角形,将此网格赋值给场景中的meshfilter,meshrenderer就可以将此三角形渲染出来了,但是法线并不一定是正确的,例如上面声明的三个顶点:

v3s [0] = new Vector3 (0, 0, 0);
v3s [1] = new Vector3 (0, 1, 0);
v3s [2] = new Vector3 (1, 0, 0);

不论是由0-1-2还是2-1-0的顺序组成三角形,此三角形的法线总是指向z轴方向,在有光照的场景中会出现错误的效果,这里unity提供了一个方法:
mesh.RecalculateNormals(); //自动将每个顶点的法线调整到正确方向,既是与面的朝向一致
mesh.RecalculateBounds(); //重新计算mesh.bounds,通过mesh.bounds可访问size,center等一些网格的属性
在将mesh赋值给meshfilter前需要先调用此两方法。

normal是影响光照效果,即在光照下的凹凸平滑程度,所以用插值平均后的Normal代替所有的顶点的各自的normal可使低精度模型看起来平滑。

// 编辑器下显示 normal 的脚本
using UnityEngine;  

[ExecuteInEditMode]  
public class ShowNormals : MonoBehaviour {  
    public float length = 1;  
    public Vector3 bias;  

    void Update() {  
        Mesh mesh = GetComponent<MeshFilter>().sharedMesh;  

        Vector3[] vertices = mesh.vertices;  
        Vector3[] normals = mesh.normals;  

        for (var i = 0; i < normals.Length; i++)  
        {  
            Vector3 pos = vertices[i];  
            pos.x *= transform.localScale.x;  
            pos.y *= transform.localScale.y;  
            pos.z *= transform.localScale.z;  
            pos += transform.position + bias;  

            Debug.DrawLine  
            (  
                pos,  
                pos + normals[i] * length, Color.red);  
        }  
    }  
}

例子:双面网格的实现

可以利用上面的知识实现一个双面的网格,既是利用四个顶点以顺时针方向组成两个三角形再以逆时针方向组成两个三角形,四个顶点赋uv值后每个点的正反两面的uv皆相同,进而出现镜像效果。此效果与shader调用cull off选项后的效果相同,以下为实现方法:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Triangle : MonoBehaviour {
    public MeshFilter meshFilter;
    private Mesh mesh;

    void Start () {
        mesh = new Mesh ();

        Vector3[] v3s = new Vector3[4];
        v3s [0] = new Vector3 (0, 0, 0);
        v3s [1] = new Vector3 (0, 1, 0);
        v3s [2] = new Vector3 (1, 0, 0);
        v3s [3] = new Vector3 (1, 1, 0);
        mesh.vertices = v3s;

        int[] index = new int[12];
        index [0] = 0;
        index [1] = 1;
        index [2] = 2;

        index [3] = 3;
        index [4] = 2;
        index [5] = 1;

        index [6] = 1;
        index [7] = 0;
        index [8] = 2;

        index [9] = 3;
        index [10] = 1;
        index [11] = 2;
        mesh.triangles = index;

        Vector2[] uvs = new Vector2[4];
        uvs [0] = Vector2.zero;
        uvs [1] = Vector2.up;
        uvs [2] = Vector2.right;
        uvs [3] = Vector2.one;
        mesh.uv = uvs;

        mesh.RecalculateBounds();
        mesh.RecalculateNormals();

        meshFilter.mesh = mesh;
    }
}

材质

sharedMaterial是公用的Material,所有用到这个材质的MeshRendered都会引用这个Material。改变sharedMaterial的属性也会改变mat文件。
material是独立的Material,改变material的属性不会影响到其他对象,也不会影响mat文件。

MeshRenderer mr = GetComponent<MeshRenderer> ();
Material smat = mr.sharedMaterial;
Material mat = mr.material;
Debug.Log (smat == mr.sharedMaterial);
Debug.Log (mat == mr.sharedMaterial);
Debug.Log (mat == mr.material);
// 打印结果:False,True,True

Material newMat = new Material (Shader.Find ("Standard"));  
mr.material = newMat;  
Debug.Log (newMat == mr.sharedMaterial);  
Debug.Log (newMat == mr.material);  
Material tmpMat = mr.material;  
Debug.Log (newMat == tmpMat);  
Debug.Log (tmpMat == mr.sharedMaterial); 
// 打印结果:True,False,False,True

由此,我们可以得出结论:
material的set方法里面会把传入的值赋给_sharedMaterial,并且会再新建一个_sharedMaterial的拷贝,并赋值给_material。(因为值不同,所以get的时候又会新建一个Material。)
sharedMaterial会更改对应的材质球的属性,material则是根据sharedMaterial的设置返回一个新的材质实例。

public Material material {  
    get {  
        if (_sharedMaterial == _material) {  
            return _material;  
        }  
        _material = new Material (_sharedMaterial);  
        return _material;  
    }  
    set {  
        _sharedMaterial = value;  
        _material = new Material (_sharedMaterial);  
    }  
}

至于SharedMaterial的get和set方法,似乎并不会修改变量的值,只是单纯的取值赋值而已。

最后总结一下二者的使用时机:
当只修改材质的参数的时候,使用material属性,确保其他对象不会受影响。
当需要修改材质的时候,直接赋值给sharedMaterial,否则赋值给material会产生内存泄露。