精华内容
下载资源
问答
  • Unity Mesh - 网格编程

    千次阅读 2019-08-29 21:02:18
    Unity的网格类允许开发者通过编程在游戏运行时来创建和修改网格,它对于我们创建各种图形效果是非常关键的;而网格编程其实就是手动去创建网格顶点的各种信息,如顶点的坐标,纹理坐标,颜色,法线,切线等。 ...

    一、网格类属性及函数

    首先提供官方参考文档:API-Reference-Mesh;Unity区分了Simple和Advanced Mesh Api,Simple API主要是指SetColors等,它会在修改时做一些检测来保证数据的准确性;而Advanced Api则指SetVertexBufferParams等,它会跳过数据有效性的检查,它的速度更快,但是要由开发者保证数据的有效性;

    概述: Mesh类继承自Object类;

    属性

    • vertices / vertexCount:顶点数组(Vector3 [])与顶点数量;
    • triangles:通过设置对vertices的索引来定义三角形数组;
    • uv:设置uv坐标
    • normals:设置法线

    静态方法

    •  

    公有方法

    • RecalculateNormals:重新计算法线;

    二、网格编程简单入门

    Unity的网格类允许开发者通过编程在游戏运行时来创建和修改网格,它对于我们创建各种图形效果是非常关键的;而网格编程其实就是手动去创建网格顶点的各种信息,如顶点的坐标,纹理坐标,颜色,法线,切线等。

    Unity网格结构剖析:网格是由3D空间中排列的一系列三角形组成的,一个三角形由三个顶点组成,所有的顶点存储在顶点数组内,一个三角形只需要存储相应顶点的数组索引即可,所以一个网格的数据就是一个索引数组,但是该数组是3的倍数,因为每三个数据将一次组成一个三角形;由于要处理光照计算以及表现模型的一些表面特性,所以需要在顶点信息中提供法线信息和uv信息,以便在着色器计算中使用;

    1、顶点

    顶点是网格最基础的组成部分,可通过mesh.vertices获取和赋值。mesh.vertices是一个Vector3的数组,每个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;

    2、由顶点组成三角形

    mesh.triangles决定了网格中的三角形的形状和朝向。triangles是一个int数组,此数组长度永远是3的倍数,每三个int代表的是由哪三个顶点并由什么顺序(朝向)来组成一个三角形,例如:

    手动设置三角形的顶点坐标信息:

    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]组成的三角形。此三角形的形状有此三个顶点的位置决定,而它的朝向则由此三个顶点的旋转方向决定:逆时针方向为正对。

    3、设置法线

    给网格赋值顶点并组成三角形,将此网格赋值给场景中的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前需要先调用此两方法。

    4、设置UV

    uv是为了防止与xy混淆的另一种写法。u为x轴,v为y轴,最小值为0,最大值为1无数值限制(材质上的uv值一般是0-1,当进行采样的uv值大于1时会按取1的余数来计算)。uv值用处很多,例如材质texture的采样,也既是贴图。或是利用它的一些特性(范围0-1;同一个uv区域每个像素的uv值皆不同)进行GPU内的一些计算。

    当每个顶点被赋予uv值后,在片段着色阶段,片段着色器将会根据顶点的uv值将此三角形覆盖的每个像素的uv值进行自动插值。

    三、网格相关组件

    Mesh Filter:用于携带网格数据将其传入到Mesh Renderer中,当导入网格资源时,如果网格被蒙皮了,会自动为其创建一个Skinned Mesh Renderer(例如导入人物模型时),如果不是蒙皮,就会创建一个Mesh Filter和一个Mesh Renderer;

    展开全文
  • Unity网格编程篇(二) 非常详细的Mesh编程入门文章

    万次阅读 多人点赞 2017-08-18 14:42:53
    Procedural Grid Programming Form代码实现网格本文目标 创建网格坐标 使用协程计算他们的位置 利用三角形确定一个面 自动生成法线 添加纹理坐标和切线 在这篇教程中我们将利用顶点和三角面创建一个网格。...

    Procedural Grid Programming Form

    代码实现网格

    本文目标

    • 创建网格坐标
    • 使用协程计算他们的位置
    • 利用三角形确定一个面
    • 自动生成法线
    • 添加纹理坐标和切线

    在这篇教程中我们将利用顶点和三角面创建一个网格。

    这里写图片描述

    0.开始

    这篇博客的原文地址:http://catlikecoding.com/unity/tutorials/procedural-grid/ 博主翻译并非100%一字一句翻译,对内容进行部分的增删改。

    1.渲染东西

    • 如果你想要在Unity显示一些东西,你需要一个网格。它可以是一个3D模型从另一个程序导入的(比如33dmax or maya)。它也可以是程序生成的网格。它可以是精灵、UI元素或者是粒子系统,它们一样都是使用unity网格,甚至是屏幕特效也是使用网格渲染的。
    • So,什么是网格?概念上来说网格由图形硬件(GPU Graphics Processing Unit图形处理单元)构成来绘制复杂的材料/东西。它至少包含一组在3D空间中位置明确的点再加一组三角形

      • 最基础的2D图形,由点连接组成,面上的三角形就时此类网格的代表。
    • 因为三角形是平坦的并且拥有直边,所以他们可以完美地被用来显示平坦的和连续的东西,像一个立方体的面孔。曲面的或者是圆的面只能被大量小的三角形来接近组成。如果三角面显示足够的小(不大于一个像素),那么你就不会感觉曲面或者是圆是由三角面组成的。从实时性能角度来讲通常这种情况是不可能的,所以我们总能够在面的某个程度上发现锯齿。

    这里写图片描述

    这里写图片描述

    Unity自带的胶囊体,立方体和球体 着色 vs 线框

    如何显示线框
    - 再视图左上角选择Display Mode,前三个选项分别是Shaded(着色)、Wrieframe(线框)和Shaded Wrieframe着色并带着线框

    • 如果你想要展示一个3D游戏物体,它必须拥有两个组件。
      • MeshFilter 这个组件记录了你想要展示的网格数据
      • MeshRenderer 使用这个组件告诉网格如何渲染,比如使用哪个材质球,是否接受阴影和其他设置。

    这里写图片描述

    为什么是一个材质球数组?
    - 一个网格渲染器可以有多个材质球,它通常被用来渲染多组三角面,也成为子网格。它通常在外界导入的模型中使用,本篇文章不使用多个材质球。

    • 你可以完全改变网格的显示效果通过调整材质球。Unity默认的材质球是简单的白色立体。你可以自己创建一个新的材质球通过Assets->Create->Material并拖拽到你的游戏物体上来代替它。新的材质球默认为Unity Standard Shader,它有一组口控制,你可以调节这些属性得到你想要模型表现。
    • 一个快速添加细节的方法是给你的网格提供一个反射贴图,这个纹理描绘了材质球的基础颜色。当然我们需要知道如何投射纹理到网格的三角面上。这需要添加2D纹理坐标到顶点上。这二维纹理空间被称为U和V,也是常说的UV坐标。UV坐标通常在(0,0)到(1,1)之间,它覆盖了整个纹理。超出范围的坐标将造成clamped或者Tiling平铺的效果,这去取决于纹理设置。

    这里写图片描述这里写图片描述这里写图片描述

    2.创建网格顶点

    • So? 如何创建自己的网格?通过创建一个简单的矩形网格来了解一下。这个网格将包括方形瓷砖(单位长度的四边形)。创建一个新的C#脚本并加入水平和垂直尺寸。
    using UnityEngine;
    using System.Collections;
    
    public class Grid : MonoBehaviour 
    {
        public int xSize, ySize;
    }

    我们是需要引用System.Collections?
    - 我们不需要它去生成我们的网格,但是我引用它因为我稍后将使用协程。

    • 当我们给游戏物体挂上这个脚本,我们需要给它同时添加MeshFilter和MeshRenderer组建。我们可以添加属性在类的前面,Unity将自动为们添加这些类。
    [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
    public class Grid : MonoBehaviour 
    {
        public int xSize, ySize;
    }
    • 现在创建一个空物体并挂载组建,它将自动同时添加MeshFilter和MeshRenderer组建。设置MeshRenderer的材质并确保MeshFilter的mesh属性为未定义。并设置网格尺寸为10和5。

    这里写图片描述

    • 我们生成真实的网格在物理被唤醒的时候,Awake方法将在我们游戏游戏模式时自动调用。
    private void Awake ()
    {
        Generate();
    }
    • 现在我们先考虑顶点位置稍后处理三角面序号。我们需要一个数组存储3D顶点位置。顶点的数量取决于网格的尺寸。我们需要获取每个四边形每个角的顶点,但是相邻的四边形可以共享相同的顶点。所以每个维度商定点数比网格数多一个。

    这里写图片描述
    4x2网格

    private Vector3[] vertices;
    
    private void Generate () 
    {
        vertices = new Vector3[(xSize + 1) * (ySize + 1)];
    }
    • 我们在场景中绘制这些顶点,这样我们就可以正确地核对它们的位置。利用OnDrawGizmos方法来绘制顶点的位置,利用OnDrawGizmos在每个顶点绘制一个小球。
    private void OnDrawGizmos () 
    {
        Gizmos.color = Color.black;
        for (int i = 0; i < vertices.Length; i++) 
        {
            Gizmos.DrawSphere(vertices[i], 0.1f);
        }
    }
    • 什么是Gizmos?

      • 在编辑模式下Gizmos可以提供可视化的提示。它们只在Scene场景中显示不在Play模式下显示,但是你可以通过工具栏调整它们。Gizmos公共类允许你绘制图标,线条,和其他的东西。
      • Gizmos在OnDrawGizmos方法中执行绘制,它被Unity编辑器自动调用。另一个可选的方法是OnDrwaGizmosSelected,它只能被可选的对象调用。
    • 这时在编辑模式下会有一个Bug,因为OnDrawGizmos方法在编辑模式下被调用了,但此时我们的顶点数组为空。为防止这个错误发生,我们在OnDrawGizmos方法中添加一个顶点数组为空就跳出方法的操作。

    private void OnDrawGizmos ()
    {
        if (vertices == null)
            return;
        …
    }

    这里写图片描述

    • 点击Play播放按钮之后,在Scene视图中我们只能看见一个球体在世界的原点。因为我们还没有定位顶点的位置,所以所有的球体重叠在一个位置。我们必须会用双重循环遍历所有的位置。
    private void Generate ()
    {
        vertices = new Vector3[(xSize + 1) * (ySize + 1)];
        for (int i = 0, y = 0; y <= ySize; y++)
        {
            for (int x = 0; x <= xSize; x++, i++)
            {
                vertices[i] = new Vector3(x, y);
            }
        }
    }

    这里写图片描述
    网格的顶点

    • 为什么在Gizmos绘制不能被移动?

      • Gizmos直接使用世界坐标绘制,不是使用对象的本地坐标系,如果你想它们遵循对象的Transform组件,你必须声明使用transform.TransformPoint(vertices[i]) 代替 vertices[i].
    • 现在我们可以看见这些顶点,但是他们生成的顺序我们不能明显地看出来。我们可以使用颜色来标识,但是我们也可以使用协程减慢这个过程的速度。这就是为什么我在这个脚本中引用了System.Collections命名空间。

    private void Awake () 
    {
        StartCoroutine(Generate());
    }
    
    private IEnumerator Generate ()
    {
        WaitForSeconds wait = new WaitForSeconds(0.05f);
        vertices = new Vector3[(xSize + 1) * (ySize + 1)];
        for (int i = 0, y = 0; y <= ySize; y++) 
        {
            for (int x = 0; x <= xSize; x++, i++)
            {
                vertices[i] = new Vector3(x, y);
                yield return wait;
            }
        }
    }

    观察顶点逐个生成

    3.创建网格

    • 现在我们知道顶点的位置是正确的,接下来我们就处理真正的网格。除此之外我们想要我们的组件持有这些顶点,我们必须把顶点指定到MeshFilter.mesh中。我们处理过顶点之后,就可以网格存储在MeshFilter中。
    private Mesh mesh;
    
    private IEnumerator Generate () 
    {
        WaitForSeconds wait = new WaitForSeconds(0.05f);
    
        GetComponent<MeshFilter>().mesh = mesh = new Mesh();
        mesh.name = "Procedural Grid";
    
        vertices = new Vector3[(xSize + 1) * (ySize + 1)];
        …
        mesh.vertices = vertices;
    }

    这里写图片描述
    网格只在Play模式下显示

    • 到目前,在Play模式下我们可以持有一个网格,但是它依旧是不可见的,因为我们并没有给它任何三角面。三角面由顶点数组索引决定。每一个三角面有三个顶点,三个连续的顶点描绘了一个三角形,让我们来先绘制第一个三角面。
    private IEnumerator Generate () 
    {
        …
    
        int[] triangles = new int[3];
        triangles[0] = 0;
        triangles[1] = 1;
        triangles[2] = 2;
        mesh.triangles = triangles;
    }
    • 好的,目前我们已经绘制了一个三角面,但是由于三个顶点在一条直线上,所以生成了一个失败的三角形,它是不可见的。前两个顶点是正确的,第三个顶点应该跳转到下一行的第一个顶点。
    triangles[0] = 0;
    triangles[1] = 1;
    triangles[2] = xSize + 1;
    • 通过以上操作,我们绘制了一个三角形,但是它只能在一个方向可见。这种情况下,只有Z轴的反方向可见,所以你可能需要旋转视角才能看到它。
    • 三角形的哪一面可见是由顶点序号的方向来确定的。默认情况下,如果顶点顺序是顺时针方向的话那么三角形是正面可见。逆时针(就是逆屏幕方向)的三角面是被抛弃的,所以我们不必花费时间去渲染这部分顶点,以为他们通常都是不可见的。

    这里写图片描述
    逆时针和顺时针三角形

    • 所以为了实线从Z轴负方向到正方向可见,我们必须改变顺序相反的顶点的位置。我们交换后两个顶点的序号即可。
    triangles[0] = 0;
    triangles[1] = xSize + 1;
    triangles[2] = 1;

    这里写图片描述
    第一个三角面

    • 现在我们绘制了一个三角面只覆盖了一个四方瓦片的一半,为了覆盖整个瓦片,我们需要第二个三角面。
    int[] triangles = new int[6];
    triangles[0] = 0;
    triangles[1] = xSize + 1;
    triangles[2] = 1;
    triangles[3] = 1;
    triangles[4] = xSize + 1;
    triangles[5] = xSize + 2;

    这里写图片描述
    一个四边形由两个三角面组成

    • 既然这些顶点共用两个顶点,我们就可以减少我们代码的行数,明确地提到每个顶点索引只有一次。
    triangles[0] = 0;
    triangles[3] = triangles[2] = 1;
    triangles[4] = triangles[1] = xSize + 1;
    triangles[5] = xSize + 2;

    这里写图片描述
    第一个四边形

    • 我们可以通过循环来创建剩余第一行的瓦片。尽管我们遍历所有的顶点和三角面序号,但是我们必须要保证顶点和三角面序号是按照顺序的。我们把yield代码的声明放到循环里,我们就不需要等待顶点的出现了。
    int[] triangles = new int[xSize * 6];
    for (int ti = 0, vi = 0, x = 0; x < xSize; x++, ti += 6, vi++)
    {
        triangles[ti] = vi;
        triangles[ti + 3] = triangles[ti + 2] = vi + 1;
        triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
        triangles[ti + 5] = vi + xSize + 2;
        yield return wait;
    }
    • 现在,Gizmos可以立刻渲染出顶点,并且所有的三角面在一段时间后统一出现。要想看到瓦片一个接一个的出现,我们必须每次循环都刷新网格代替掉执行完所有循环刷新。
    mesh.triangles = triangles;
    yield return wait;
    • 现在我们来填充剩余所有的网格通过将单体循环转变为双重循环。一定要注意移动到下一行的时一定要增加一次顶点序号,因为每一行顶点比列数多一。
    int[] triangles = new int[xSize * ySize * 6];
    for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++) 
    {
        for (int x = 0; x < xSize; x++, ti += 6, vi++)
        {
            …
        }
    }

    这里写图片描述
    填充剩余网格

    • 正如你所能看到的,所有的网格都被三角面填充,每一行都是同时填充,因为我们使用了协程。如果你对想过感到满意,你可以移除掉所有的协程代码,这样网格创建就没有任何的延迟了。
    private void Awake () 
    {
        Generate();
    }
    
    private void Generate () 
    {
        GetComponent<MeshFilter>().mesh = mesh = new Mesh();
        mesh.name = "Procedural Grid";
    
        vertices = new Vector3[(xSize + 1) * (ySize + 1)];
        for (int i = 0, y = 0; y <= ySize; y++) {
            for (int x = 0; x <= xSize; x++, i++) {
                vertices[i] = new Vector3(x, y);
            }
        }
        mesh.vertices = vertices;
    
        int[] triangles = new int[xSize * ySize * 6];
        for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++) {
            for (int x = 0; x < xSize; x++, ti += 6, vi++) {
                triangles[ti] = vi;
                triangles[ti + 3] = triangles[ti + 2] = vi + 1;
                triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
                triangles[ti + 5] = vi + xSize + 2;
            }
        }
        mesh.triangles = triangles;
    }
    • 为什么不使用单个四边形?
      • 当我们创建一个平面矩形,我们可以仅仅使用两个三角面。这没有问题。但是更多的顶点结构也可以提供更多的控制和表现。这里也只是一个实验!

    4.生成额外的顶点数据:

    • 我们的网格目前处于一种特殊的情况下。因为我们没有直到目前还没有给他们法线向量,默认的发现向量是(0,0,1)(垂直于屏幕向里),而我们需要的正好相反。

    • 法线工作原理是什么呢?

      • 法线是垂直于面的向量。我们通常使用单位长度的法向量,并向量指向面的外部,而不是内部。
      • 法线可以用于确定光线与顶点的夹角。这个细节的使用取决于Shader。
      • 作为三角面它永远是平的,因此它不应该需要被提供一个单独的法线信息。然而,我们需要造假。在现实中,顶点时不存在法线的,三角面才有。通过附加自定义顶点法线和三角面插着,我们可以奖状我们有一个平滑的曲面代替一堆平的三角面。这个错觉是令人信服的,只要你不去注意网格锋利的轮廓(锯齿)。
    • 法线用于规定每个顶点,所以我们必须填充另一个向量数组。另一种选择,我们可以依据网格的三角面来计算出法线。我们也可以偷懒,向下面这样做。

    private void Generate () 
    {
        …
        mesh.triangles = triangles;
        // 网格自动计算法线向量
        mesh.RecalculateNormals();
    }
    • 法线是如何重新计算的?
      • Mesh.RecalculateNormals方法利用与顶点相连的三角面计算出每一个顶点的法线。计算平面三角形法线平均值,然后使用normalize方法单位话。

    这里写图片描述这里写图片描述
    没有法线vs有法线

    • 接下来是UV坐标。你可能注意到网格目前是颜色统一的,即便是我们给它赋值一个带反射贴图的材质球。这很容易理解,因为我们没有自行给它提供UV坐标,他们默认为(0,0)。

    • 想要纹理适应我们的网格,简单地划分顶点的位置通过网格的大小。

    vertices = new Vector3[(xSize + 1) * (ySize + 1)];
    Vector2[] uv = new Vector2[vertices.Length];
    for (int i = 0, y = 0; y <= ySize; y++) 
    {
        for (int x = 0; x <= xSize; x++, i++) 
        {
            vertices[i] = new Vector3(x, y);
            uv[i] = new Vector2(x / xSize, y / ySize);
        }
    }
    mesh.vertices = vertices;
    mesh.uv = uv;

    这里写图片描述这里写图片描述
    错误的UV坐标 Clamping vs wrapping

    纹理现在显示了,但是它并没有覆盖整个网格。它的真实外观取决于纹理模式是Clamp模式或者是Repeat模式。产生这种现象是因为当前我们是通过整数划分的,UV坐标的计算结果是整数。为了得到正确的在0到1之间的坐标,我们必须使用浮点数。

    uv[i] = new Vector2((float)x / xSize, (float)y / ySize);
    • 纹理现在被投影到了整个纹理上。当我设置网格尺寸为(5,10),这个网格纹理将出现横向拉伸,可以自动反向适应材质的纹理的平铺设置。设置X的坐标为(2,1)将产生双倍。如果把纹理模式设置为Repeat模式,我们可以看到两个四方瓷砖网格。

    这里写图片描述这里写图片描述这里写图片描述
    正确的UV纹理,平铺(1,1) vs 平铺(2,1)

    • 另外一个提那家表面细节的方法是使用法线贴图。这个贴图使用颜色值记录了法相向量。应用这个这个纹理到网格上将产生更多的灯光细节效果,它是单独使用顶点法线产生的。

    这里写图片描述这里写图片描述
    凹凸不平的表面,产生了金属材质的效果

    • 目前应用这个材质球到我们的网格将不会产生任何的凹凸效果。我们需要给我们的网格添加切线向量。

    • 切线的工作原理是什么?

      • 法线贴图在切线空间中定义。这是一个流动在物理表面的3D空间。这个方法使我们能够使用相同的法线贴图在不同的空间和方向。
      • 表面法相用于描述空间中的向上方向。但是哪个方向是正确的?它是有切线决定得。理论上,法线和切线的夹角应该是90度。两者的叉积可求出三维空间的第三个方向。但是现实中这个结果并不是90度,但是效果依然是令人比较满意的。
      • 所以切线是一个三维向量,但是在Unity中它是使用四维向量定义的。第四个值通常是1或者-1,用于控制第三切线空间唯独方向-朝前或朝后,这有助于展示法线贴图,通常用于左右对称的3D模型,像人一样。Untiy的shader执行此计算要求我们使用-1。
    • 当我们有一个平面,所有的切线仅仅指向相同的方向,是正确的。

    vertices = new Vector3[(xSize + 1) * (ySize + 1)];
    Vector2[] uv = new Vector2[vertices.Length];
    Vector4[] tangents = new Vector4[vertices.Length];
    Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
    for (int i = 0, y = 0; y <= ySize; y++)
    {
        for (int x = 0; x <= xSize; x++, i++) 
        {
            vertices[i] = new Vector3(x, y);
            uv[i] = new Vector2((float)x / xSize, (float)y / ySize);
            tangents[i] = tangent;
        }
    }
    mesh.vertices = vertices;
    mesh.uv = uv;
    mesh.tangents = tangents;

    这里写图片描述
    一个平面被伪装成凹凸不平的面

    • 现在你知道如何创建一个简单的网格并使得它在使用材质球的情况下看起来更加复杂。网格需要顶点坐标,三角面,通常还需要UV坐标,经常也需要法线和切线。你也可以添加顶点颜色,尽管Unity标准着色器不使用这个属性。但是你可以自己创建Shader去使用这个颜色属性,如果你想了解如何使用使用自己的Shader使用这个颜色属性,情况另一篇文章。

    结束

    这篇博客的原文地址:http://catlikecoding.com/unity/tutorials/procedural-grid/ 博主翻译并非100%一字一句翻译,对内容进行部分的增删改。

    UGUI组件系列

    Unity框架解读系列

    分享地址(置顶目录包含所有组件的最新下载地址)

    展开全文
  • 在游戏开发过程中,模型一般由美术提供,程序一般只负责使用,以及优化。一般的优化手段就是mesh的合批,高低摸的转换之类,以及捏脸系统等等。但是大家要知道一点,Unity不仅可以处理模型,还可以创建模型,只是在...
    在游戏开发过程中,模型一般由美术提供,程序一般只负责使用,以及优化。一般的优化手段就是mesh的合批,高低摸的转换之类,以及捏脸系统等等。但是大家要知道一点,Unity不仅可以处理模型,还可以创建模型,只是在正常的商业游戏开发中并不需要程序去用代码制作模型。但是知道Unity中模型是怎么构建的,非常有利于我们对开发的深入理解。

    1.Unity中模型是怎么显示的

    (1)简单来说,模型都是用三角面构成。在理解模型的时候,可以直接将模型认作为Mesh网格。所以Mesh网格就是由三角面片组成,如下:图1-1
    这是一个我绘制的一个最简单的正方形面片,他由两个三角形组成,三角形124,和三角形234,注意一下这里的顺序,我们在设置点的时候,必须按照顺时针,因为通常情况下我们只绘制一面,另外一面不需要绘制,当然你也可以,只是会消耗更多的性能。
    (2)看到上面,其实Mesh的原理就已经很清楚了,通过顶点,连接线组成三角形,然后由三角形构成面,最后得到各种各种的模型。当然,上面的例子只是为了让同学们知道Mesh组成原理是什么,因为哪怕是最简单的内建Plane面,也有着一百多个顶点。我们来建一个Plane,看看它的构成
    在这里插入图片描述
    我们可以看到,Unity内建的Plane大小为10x10,有着121个顶点。更丰富的顶点意味着我们可以做丰富的动画,扭曲,折叠等等操作。但是通常也意味着更多的计算量消耗。下面我们就来手动创建一个可以任意设置长宽的Plane面吧

    准备工作

    (1)Mesh,MeshFilter,MeshRender
    Mesh:模型的顶点坐标、法线、uv坐标、三角面信息均包含在Mesh中
    MeshFilter:掌管一个Mesh对象,并从中取得数据处理,最后交给MeshRender绘制
    MeshRender:得到处理后的Mesh信息,并且绘制在空间中。
    (2)我们的工作
    创建一组顶点,按照规则写出三角形序列,然后将接受这个顶点和三角形序列的Mesh交给MeshFIlter即可。

    代码部分

    我会写的比较详细,因此可能会有一部分重复代码
    (1)初始类

    //如果添加的对象上没有MeshFilter,则给对象添加一个MeshFilter
    [RequireComponent(typeof(MeshFilter))]
    //如果添加的对象上没有MeshRenderer,则给对象添加一个MeshRenderer
    [RequireComponent(typeof(MeshRenderer))]
    public class DrawPlane{
    	//实际操作的MeshFilter 
    	private MeshFilter filter;
    	//实际的Mesh
    	private Mesh mesh;
    	//记录顶点信息
    	private List<Vector3> vts = new List<Vector3>();
    	//自身的uv坐标
    	private int[] uvs;
    	//三角形数组
    	private int[] tris;
    	//x轴单位
    	public int xSize;
    	//y轴单位
    	public int ySize;
    	//x轴每单位大小,用于计算坐标
    	public float xSeg;
    	//y轴每单位大小,用于计算坐标
    	public float ySeg;
    	//x轴顶点个数 ==== xSize+1
    	private int xCount;
    	//y轴顶点个数 ==== ySize+1
    	private int yCount;
    	
    	//初始化
    	private void Start(){
    		filter = GetComponent<MeshFilter>();
    		xCount = xSize + 1;
    		yCount = ySize + 1;
    		//得到每个顶点的坐标
    		vts = CalVts();
    		//设置Mesh
    		SetMesh();
    	}
    	
    
    	
    	//计算顶点信息,多少个顶点,每个顶点的坐标等
    	private List<Vector3> CalVts(){
    		//我们以挂载对象的位置作为起始点
    		Vector3 pos = =tranform.position;
    		
    		List<Vector3> vals = new List<Vector3>();
    		for(int i = 0 ; i < xCount ; i++){
    			for(int j = 0 ; j < yCount ; j++){
    				Vector3 vector =pos + new Vector3(i*xSeg,j*ySeg,0);
    				vals.Add(vector);
    			}
    		}
    		return vals;
    	}
    
    	//对Mesh进行设置
    	private void SetMesh(){
    		mesh = new Mesh();
    		//因为每个有效点我们需要绘制两个三角形,共计六个顶点,所以对tris数组初始大小为
    		tris = new int[xSize*ySize*6];
    		//记录每个有效点的三角形起始下标,每个有效点需要画两个三角形,六个顶点,因此每次迭代triCount +=6
    		int triCount = 0 ;
    		for(int i = 0 ; i < ySize ; i++){
    			for(int j = 0 ; j < xSize ; j++){
    				//每个有效点绘制两个三角形
    				//每个顶点,在vts中的下标索引
    				int startIndex = i * xCount + j; 
    				//第一个三角形,三个顶点
    				tris[triCount] = startIndex ;
    				//第一个三角形第二个点为初始点的正上方相邻点
    				tris[triCount+1] = startIndex + xCount ;
    				//第一个三角形第三个点为右边相邻的点
    				tris[triCount+2] = startIndex + 1;
    				
    				//绘制第二个三角形,
    				//第二个三角形第一个点为初始点的正上方相邻的点
    				tris[triCount+3] = startIndex + xCount ;
    				//第二个三角形第二个点为初始点右上方的点
    				tris[triCount+4] = startIndex + xCount + 1;
    				//第二个三角形第三个点为初始点的右边相邻的点
    				tris[triCount+5] = startIndex + 1;
    				
    				triCount += 6;
    			}
    		}
    		
    		//将顶点传给mesh
    		mesh.vertices = vts.ToArray();
    		//将三角形的绘制序列传给mesh
    		mesh.triangles = tris;
    		
    		//至此我们的三角形其实已经绘制成功了,但是面并没有uv坐标,如果了解uv的同学值得,纹理就是通过顶点之间uv坐标的插值来计算颜色值的。
    		//因此我们还需要计算一下uv坐标
    		//有多少个顶点,就有多少个uv坐标
    		//uv坐标的范围在0-1之间,其实就相当于给顶点坐标做一个归一化处理
    		uvs = new int[vts.Count];
    		float xOffset = 1.0f / xCount;
    		float yOffset = 1.0f / yCount;
    		for(int i = 0 ; i < yCount ; i++){
    			for(int j = 0 ; j < xCount; j++){
    				//设置每个顶点的uv坐标
    				//这里我们也可以做一些小巧思,比如y轴翻转,或者x轴翻转,大家能想到怎么做吗
    				uvs[i * xCount+ j] = new Vector2(j * xOffset , i * yOffset );
    			}
    		}
    		//将uv设置给mesh
    		mesh.uv = uvs;
    		//这三个方法是相当于对mesh用新数据进行重置
    		mesh.RecalculateBounds();
            mesh.RecalculateNormals();
            mesh.RecalculateTangents();
            //将mesh交给filter
            filter.mesh = mesh;
    	}
    }
    

    (2)将脚本挂载在一个空物体上,设置xSize,ySize,xSeg,ySeg即可,比如我们现在得到一个5x7,x,y轴单位长度为1的plane面

    在这里插入图片描述
    当然我们也可以将这个Plane导出作为一个模型,或者对这个Plane添加一些顶点动画,还是开头说的,学会用程序制作模型,对实际开发的意义并不大。但是能帮助我们更好的理解模型构建的流程,对模型的优化,使用有很大的帮助。

    展开全文
  • string temp = transform.TransformPoint(transform.GetComponent<MeshFilter>().mesh.vertices[i]).y.ToString(); 这行代码是得到mesh文件顶点的世界坐标的Y轴,然后我发现无论shader怎么去修改mesh的顶点...
    string temp = transform.TransformPoint(transform.GetComponent<MeshFilter>().mesh.vertices[i]).y.ToString();

    这行代码是得到mesh文件顶点的世界坐标的Y轴,然后我发现无论shader怎么去修改mesh的顶点,这里的y轴永远是0,于是乎问了一下群里的大佬,得知 shader是不修改mesh网格的数据,

    卒~~~ 浪费一下午时间,又得从头开始。

    另外 我推断原因可能是  shader是GPU对mesh的操作,然而c#代码是对cpu的mesh操作,可能是这个原因造成的吧,才疏学浅,身边也没有大佬指点。

    以上如果有不对  请告知。

    展开全文
  • MeshAnimation在物体的顶点比较多的情况下,悲剧是显而可见的,我一个一个的点选顶点肯定得累死,而且对于形态的调控不是很方便,应该说是很麻烦,要知道,骨骼动画因为有了骨骼以及蒙皮信息而有了灵魂,所以变形...
  • mesh deformation资料

    千次阅读 2017-09-08 16:14:32
    其重点在于:①把问题概括成某个有约束的能量项②求解(一般用凸优化知识,也可能使用遗传算法,蚁群算法等智能方法,而matlab在这方面编程较为简单)。下面汇总一些很有用的资料:(来自于GraphCon图形控这个...

空空如也

空空如也

1
收藏数 6
精华内容 2
关键字:

unitymesh编程