Skip to content

简介

目前 SandMod 仅支持通过 Type Script 脚本对 网格(Mesh)进行自定义。Mesh 类是 游戏对象(Game Object)网格几何体的基本脚本接口,它使用数组来表示三角形、顶点位置、法线和纹理坐标等网格数据,并且还提供其他协助自定义网格的属性和函数。您可以使用 Mesh 类来进行网格的自定义。

访问游戏对象的网格

要在 游戏对象(Game Object)上引用 网格(Mesh)数据并让几何图形可见,需要使用 网格渲染器(Mesh Renderer)组件(Component)或 蒙皮网格渲染器(Skinned Mesh Renderer)组件引用网格数据,因此需要获取对应游戏对象中引用的网格数据,常规步骤如下:

  • 使用 GetComponent(type:Component) 获取对应渲染器组件。
  • 通过 MeshRenderer.meshMeshRenderer.sharedMeshSkinnedMeshRenderer.sharedMesh 获取对应的网格数据。

例如获取当前挂载脚本游戏对象所引用的 网格资产(Mesh Asset)的网格数据,代码示例如下:

typescript
class MeshDataObtaining extends Component {

    /*
    编辑运行时事件;
    */
    OnStart(): void {
        //获取 网格渲染器(MeshRenderer)并将其保存在命名为 meshRenderer 的变量中;
        let meshRenderer:MeshRenderer = this.gameObject.GetComponent(MeshRenderer);
        // 获取网格数据,保存在命名为 meshData 的变量中;
        let meshData:Mesh = meshRenderer.mesh;
        //这里编辑获取网格数据后的操作;
    }

}

其中 MeshRenderer.meshMeshRenderer.sharedMesh 使用上有是否创建副本数据以及是否直接修改母板数据的区别。

如果该网格渲染器已经引用了网格资产或分配了网格数据:

  • MeshRenderer.mesh 代码将创建一份该网格数据的副本,并且将返回该副本,此后通过 MeshRenderer.mesh 访问的都是这份网格数据的副本。
  • 一旦使用 MeshRenderer.mesh 代码访问游戏对象的网格数据,在当前运行状态下,与原来分配到的网格资产的引用关系就会丢失,而是直接引用网格资产的网格数据的副本。
  • 通过使用 MeshRenderer.mesh 代码,您只能修改当前游戏对象的网格数据。使用相同网格资产的其他游戏对象的网格数据将不会被修改。

相反,MeshRenderer.sharedMesh 代码会直接更改网格渲染器引用的网格资产的网格数据。因此建议仅使用此代码读取网格数据,而不是写入数据,因为您可能会修改导入的资源,并且使用此网格资产的所有其他游戏对象都会受到影响,且即使游戏项目停止运行,也无法撤销对此网格所做的更改。

创建网格数据

目前 SandMod 仅支持在有 网格渲染器(Mesh Renderer)组件(Component)的 游戏对象(Game Object)中动态创建网格数据。

如果该游戏对象没有为对应的网格渲染器分配网格资产或网格数据,调用代码 MeshRenderer.mesh,则会创建并为该网格渲染器分配新网格,您可以使用 Mesh.name 给这个新的网格命名,但是无法将该网格的网格数据创建新的网格资产。

自定义网格常规步骤

  • 确定网格顶点位置。
  • 根据顶点索引绘制三角面。
  • 设置顶点的法线信息。
  • 设置每个顶点的 UV 纹理坐标,绘制网格渲染后的外观。

顶点数组

自定义网格时需要优先确定好网格的各个顶点的 位置(Position)和在顶点数组中的索引,这是因为 SandMod 系统检索网格数据属性的方式,在对应属性数组中设置数据,然后根据该数据在数组的索引将属性分配给相同索引的顶点。

定义顶点数组的代码格式如下:

typescript
//举例网格有 (0,0,0)、(1,0,0)、(0,1,0)、(1,1,0) 4 个顶点;
let vertices:Vector3[] = [
    new Vector3(0, 0, 0),
    new Vector3(1, 0, 0),
    new Vector3(0, 1, 0),
    new Vector3(1, 1, 0)
]
//设置网格类的顶点数据为该顶点数组;
Mesh.vertices = vertices;

【Mesh_Customize001.png】

上述代码案例显示效果如图。

三角面

接下来,需要设置网格的三角面。SandMod 中每个网格模型都是由多个三角面组成,每个三角面由顶点数组中的三个顶点元素组成。在三角面数组中,使用顶点数组的三个索引定义每个三角面由哪 3 个顶点组成。

值得注意的是,索引数据的三角面数组中每组三角形顶点的读取顺序用于确定该三角面是面向 摄像机(Camera)还是背对摄像机,按摄像机方向读取三角形顶点在索引数组中的顺序是 顺时针(CW)方向,系统才会认为该面是面向摄像机,即为可见的。

为此三角面数组的代码格式如下:

typescript
let tris:number[]  = [
    //第 1 个三角面由顶点数组第 0 个、第 1 个和第 2 个顶点组成;
    0, 1, 2,
    //第 2 个三角面由顶点数组第 1 个、第 3 个和第 2 个顶点组成;
    1, 3, 2
];
//设置网格类的三角面数据为该三角面数组;
Mesh.triangles = tris;

【Mesh_Customize002.png】

上述代码案例显示效果如图。

法线

具有顶点和三角形的网格在场景中是可被观察的,但系统无法对其进行正确的着色,因为它还没有法线数据。系统使用法线来计算光线从网格表面反射的方式,所以当您添加法线时,系统才会知道该如何对该网格进行着色,但需要场景中有灯光照射该网格才能看到效果。

为网格添加法线数据的代码格式如下:

typescript
//设置每个顶点的法线方向,案例中采用的是最简单的法线方向;
let normals:Vector3[] = [
    new Vector3(0, 0, 1),
    new Vector3(0, 0, 1),
    new Vector3(0, 0, 1),
    new Vector3(0, 0, 1),
]
//设置网格类的法线数据为该法线数组;
Mesh.normals = normals;

【Mesh_Customize003.png】

上述代码案例法线概念如图。

纹理坐标

要在网格材质上正确显示纹理,请将纹理坐标(UV)添加到网格中。纹理坐标是相对坐标,值范围介于 0 和 1 之间。网格中的每个顶点都有一个纹理坐标,用于指定材质纹理上的采样位置。

为网格添加纹理坐标的代码格式如下:

typescript
//设置每个顶点对应的纹理坐标,案例中直接 4 个顶点覆盖整张贴图;
let uv1:Vector2[]  = [
    new Vector2(0, 0),
    new Vector2(1, 0),
    new Vector2(0, 1),
    new Vector2(1, 1)   
]
//设置网格类的纹理坐标数据为该纹理坐标数组;
Mesh.uv1 = uv1;

【Mesh_Customize004.png】

上述代码案例添加纹理坐标数据后的网格显示效果如图。

脚本案例

案例会采用上述流程创建一个四边形平面的网格,如果您需要创建其他形状的网格或自定义网格的形状或渲染效果,流程和方法与本篇所描述的内容、流程一致。

SandMod 以三角形而非四边形处理和显示几何体。这意味着四边形网格会由两个三角形组成。

【Mesh_Customize005.png】

创建的正方形平面网格效果如图。

结合上述流程,创建四边形平面网格的代码如下:

typescript
class PlaneMeshCreation extends Component {

    //定义公共变量定义四边形的宽和高;
    public width:number = 1;
    public height:number = 1;

    /*
    编辑运行时事件;
    */
    OnStart(): void {
        //为游戏对象添加网格渲染器组件,并保存在命名为 meshRenderer 的变量中;
        let meshRenderer:MeshRenderer = this.gameObject.AddComponent(MeshRenderer);

        //创建材质,并保存在命名为 material 的变量中;
        let material:Material = new Material(Shader.Find("Engine/Default"));

        //创建网格,并保存在命名为 mesh 的变量中,且命名该网格资源为 PlaneMesh;
        let mesh:Mesh = new Mesh();
        mesh.name = "PlaneMesh";

        //设置网格的顶点,并设置网格类的顶点数据为该顶点数组;
        let vertices:Vector3[] = [
            new Vector3(0, 0, 0),
            new Vector3(this.width, 0, 0),
            new Vector3(0, this.height, 0),
            new Vector3(this.width, this.height, 0)
        ]
        mesh.vertices = vertices;

        //设置网格类的三角面数据为该三角面数组;
        let tris:number[]  = [
            //四边形左下角三角面,由顶点数组第 0 个、第 1 个和第 2 个顶点组成;
            0, 1, 2,
            //四边形右上角三角面,由顶点数组第 1 个、第 3 个和第 2 个顶点组成;
            1, 3, 2
        ];
        mesh.triangles = tris;

        //设置每个顶点的法线方向,并设置网格类的法线数据为该法线数组;
        let normals:Vector3[] = [
            Vector3.forward.Mul(1),
            Vector3.forward.Mul(1),
            Vector3.forward.Mul(1),
            Vector3.forward.Mul(1)
        ]
        mesh.normals = normals;

        //设置每个顶点对应的纹理坐标,并设置网格类的纹理坐标数据为该纹理坐标数组;
        let uv1:Vector2[]  = [
            new Vector2(0, 0),
            new Vector2(1, 0),
            new Vector2(0, 1),
            new Vector2(1, 1)   
        ]
        mesh.uv1 = uv1;

        //设置网格的网格数据和材质;
        meshRenderer.mesh = mesh;
        meshRenderer.material = material;
    }

}

当该脚本挂载在场景中的 空游戏对象(Empty Game Object)上时,运行游戏项目后显示效果如下:

【Mesh_Customize006.png】