首页 > 编程语言 >程序化创建Mesh

程序化创建Mesh

时间:2024-07-29 22:54:00浏览次数:8  
标签:vert int 创建 Vector3 vertices mesh Mesh triangles 程序化

3D模型一般是由网格(Mesh)和纹理(Texture)两部分构成。那什么是网格?从概念上讲,网格是图形硬件用来绘制复杂内容的结构。它至少包含一组基于三维空间点的顶点,以及一组连接这些点的三角形(最基本的2D形状)。而网格是由这些三角形(直角等腰三角形)构成的表面。那什么又是纹理?纹理是应用于3D模型表面的图像,用于增加模型的细节和真实感。纹理可以是物体表面的图案、颜色或材质效果,如木材的纹理、皮肤的质感等。纹理映射(Texture Mapping)是将纹理图像映射到3D模型表面的过程,通过这个过程,可以使模型看起来更加逼真。

在Unity中我们制作3D游戏时,需要各种各样的模型,你可以选择用3DMax、Maya等建好模型后导入Unity,也可以选择在Unity中用代码程序化生成网格,然后再加上各种细节,从而生成模型。比如程序化生成地型等。下面我们会用Perlin噪声图去生成山洞地型。


1. 绘制三角形

要绘制网格,我们需要一个也具有MeshFilter和MeshRenderer组件的游戏对象。我们可以强制将这些组件添加到同一游戏对象中,方法是为其赋予RequireComponent具有两种组件类型作为参数的属性。

[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
public class CreateMesh : MonoBehaviour{ }

Mesh包含但不限于:

  • 顶点坐标数组
  • 三角形顶点顺序的索引数组
  • 顶点在UV坐标系中的位置信息
[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
public class DrawTriangle : MonoBehaviour
{
    private Mesh mesh;
    private Vector3[] vertices;//顶点坐标数组
    private int[] triangles;//三角形顶点绘制顺序数组

    private void Start()
    {
        mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;
        vertices = new Vector3[]
        {
            Vector3.zero, Vector3.right, Vector3.up,
        };
        triangles = new int[]
        {
            0, 1, 2
        };
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.normals = new Vector3[] {
            Vector3.back, Vector3.back, Vector3.back
        };
    }
}

三角形顺序

顺时针和逆时针:
当使用顺时针的顶点顺序去绘制三角形,这意味着三角形可以从外部去看见他们,而内部或者说是背面是不会被绘制的。而使用逆时针的顶点顺序去绘制三角形,表现是相反的。

法向量

法向量是一个单位长度的向量,它描述了如果你站在表面上时的局部向上方向。因此,这些向量直接指向远离表面的方向。因此,我们的三角形表面的法向量应该是Vector3.back,指向网格局部空间中 Z 轴负方向的下方。但如果没有提供法向量,Unity 默认使用前向向量,因此三角形会从错误的一侧被照亮的。

虽然定义表面的法向量才有意义,但网格会定义每个顶点的法向量。用于着色的最终表面法线是通过在三角形表面上插入顶点法线来找到的。通过使用不同的法向量,可以为平面三角形添加表面曲率的幻觉。这使得网格看起来是光滑的,而实际上它们是多面的。

Vector3在设置顶点位置后,我们可以通过将数组分配给网格的属性来将法线向量添加到顶点normals。 Unity 检查数组是否具有相同的长度,如果我们提供的法线向量数量错误,Unity 将失败并发出警告。


2. 绘制圆筒

[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
public class DrawCylinder : MonoBehaviour
{
   private Mesh mesh;
   private Vector3[] vertices;//顶点坐标数组
   private int[] triangles;//三角形顶点绘制顺序数组
   public int xSize = 20;
   public int zSize = 20;
   public float radius = 5f;
   public float zLength = 5f;//z方向的长度

   private void Start()
   {
      mesh = new Mesh();
      GetComponent<MeshFilter>().mesh = mesh;
      CreateShape();//绘制图形
      UpdateMesh();//更新网格信息
   }
   
   void CreateShape()
   {
      vertices = new Vector3[(xSize + 1) * (zSize + 1)];
      int i = 0;
      float angle;
      float xPos;
      float yPos;
      float zPos;
      
      //填充顶点数组
      for (int z = 0; z <= zSize ; z++)
      {
         for (int x = 0; x <= xSize; x++)
         {
            angle = 2f * Mathf.PI * x / xSize;
            xPos = radius * Mathf.Cos(angle);
            yPos = radius * Mathf.Sin(angle);
            zPos = z * zLength;
            vertices[i] = new Vector3(xPos, yPos, zPos);

            i++;
         }
      }

      //填充三角形顶点数组
      triangles = new int[xSize * zSize * 2 * 3];
      int vert = 0;
      int tris = 0;
      for (int z = 0; z < zSize; z++)
      {
         for (int x = 0; x < xSize; x++)
         {
            triangles[tris + 0] = vert + 0;
            triangles[tris + 1] = vert + xSize + 1;
            triangles[tris + 2] = vert + 1;
            triangles[tris + 3] = vert + 1;
            triangles[tris + 4] = vert + xSize + 1;
            triangles[tris + 5] = vert + xSize + 2;

            vert++;
            tris += 6;
         }
         vert++;
      }
   }

   void UpdateMesh()
   {
      mesh.Clear();
      mesh.vertices = vertices;
      mesh.triangles = triangles;
      mesh.RecalculateNormals();
   }

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

      for (int i = 0; i < vertices.Length; i++)
      {
         Gizmos.DrawSphere(vertices[i], .1f);
      }
   }

}

绘制圆筒的过程实际上相当于将一张A4纸从一端开始卷起,卷成一个圆筒。圆筒的绘制在于如何将各个顶点放在正确的位置,我们可以画一个圆去理解,如下图。

image

所以就可以计算出x、y的位置
xPos = radius * Mathf.Cos(angle); yPos = radius * Mathf.Sin(angle);
基于这个原理,我们就可以从A4纸的左下角开始从左到右、从下到上一排一排的绘制三角形,直至绘制成圆筒。下图是我从内部绘制的圆筒。

image


3. 利用Perlin函数绘制山洞地型

[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
public class DrawByPerlin : MonoBehaviour
{
   private Mesh mesh;
   private Vector3[] vertices;
   private int[] triangles;
   public int xSize = 25;
   public int zSize = 30;

   public float radius = 8f;//改变地形的半径
   public float offset = 1000f;//改变地形的形状外观
   public float perlinScale = 0.06f;//改变地形的平滑度
   public float waveHeight = 6f;//改变地形的凸凹程度
   public float zLength = 2f;//改变地形的长度

   private void Start()
   {
      mesh = new Mesh();
      GetComponent<MeshFilter>().mesh = mesh;
      CreateShape();
      UpdateMesh();
   }


   void CreateShape()
   {
      vertices = new Vector3[(xSize + 1) * (zSize + 1)];
      int i = 0;
      float angle = 0;
      float xPos;
      float yPos;
      float zPos;

      for (int z = 0; z <= zSize ; z++)
      {
         for (int x = 0; x <= xSize; x++)
         {
            angle = 2f * Mathf.PI * x / xSize;
            xPos = radius * Mathf.Cos(angle);
            yPos = radius * Mathf.Sin(angle);
            zPos = z * zLength;
            vertices[i] = new Vector3(xPos, yPos, zPos);
            //Perlin 标度和偏移量为 Perlin 噪声创建 X 和 Z 值
            float pX = (xPos * perlinScale) + offset;
            float pZ = (zPos * perlinScale) + offset;
            //圆柱体切面中心
            Vector3 center = new Vector3(0, 0, zPos);
            //使用perlin噪声和波高使顶点向内移动
            vertices[i] += (center - vertices[i]).normalized * Mathf.PerlinNoise(pX, pZ) * waveHeight;

            i++;
         }
      }

      //填充顶点数组
      triangles = new int[xSize * zSize * 6];
      int vert = 0;
      int tris = 0;
      for (int z = 0; z < zSize; z++)
      {
         for (int x = 0; x < xSize; x++)
         {
            triangles[tris + 0] = vert + 0;
            triangles[tris + 1] = vert + xSize + 1;
            triangles[tris + 2] = vert + 1;
            triangles[tris + 3] = vert + 1;
            triangles[tris + 4] = vert + xSize + 1;
            triangles[tris + 5] = vert + xSize + 2;

            vert++;
            tris += 6;
         }
         vert++;
      }
   }

   void UpdateMesh()
   {
      mesh.Clear();
      mesh.vertices = vertices;
      mesh.triangles = triangles;
      mesh.RecalculateNormals();
   }

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

      for (int i = 0; i < vertices.Length; i++)
      {
         Gizmos.DrawSphere(vertices[i], .1f);
      }
   }

}

image

标签:vert,int,创建,Vector3,vertices,mesh,Mesh,triangles,程序化
From: https://blog.csdn.net/2403_83681139/article/details/140732677

相关文章

  • 创建sshd服务容器,并使宿主机与容器免密通信
    #docker创建私有网络,用于将来docker创建的容器指定静态ipdockernetworkcreate--driverbridge--subnet=192.168.1.0/24--gateway=192.168.1.1my_custom_network部署一个名为my_sshd_container12的Docker容器container_name="my_sshd_container12"ip=192.168.1.12doc......
  • Django安装、项目创建及默认文件介绍
    1.安装pipinstalldjango【注】:Django安装完后,存放于Lib下的site-packages中。2.创建项目终端创建"D:\Python\Python312\Scripts\django-admin.exe"startprojectmysite2django-admin.exestartprojectmysite2#Scripts已加入环境变量可以直接使用django-admin.exe进......
  • Ansible创建逻辑卷
    环境:受控主机清单文件:[dev]192.168.10.129[all:vars]ansible_ssh_user=rootansible_ssh_pass=123磁盘:受控主机需要存在一块空的磁盘。使用192.168.10.129主机上的sdb创建逻辑卷。yml文件:ansible模块:lvg:管理主机的物理卷及卷组设备lvol:管理主机的逻辑卷设备files......
  • ORA-01658创建表或索引报错分析
    一、报错信息某项目最近在SQLLoader导数据时偶尔会报错,类似如下:SQLloaderORA-01658unabletocrealeINITIALextentforsegmentintablespaceADS5GP2P_1这个报错的意思是,没有足够的连续空间为表或索引创建INITIALextent:[oracle@node1:1~]$oerrora165801658......
  • 一, 创建工程,引入依赖
    一,创建工程,引入依赖@目录一,创建工程,引入依赖创建工程工程间的关系的建立配置各个工程当中的pow配置信息,相关的依赖父工程(也就是总项目工程)的pow配置demo-module06-generate模块中pow配置:Mybatis逆向工程的pow配置demo-module05-environment模块中的pow配置:环境......
  • 手动创建springboot项目工程
    不知道为什么基于官方的架构直接创建springboot项目工程会很慢(而且还容易卡住)试试手动创建会不会好一些基于官方架构创建——https://www.cnblogs.com/yansans/p/18305819  newproject,选中maven工程,如图——》create注意:1.archetype选maven-archetype-quic......
  • vscode 创建QT最简工程
    1.前提条件qt安装cmake安装参考:https://blog.csdn.net/qq_51355375/article/details/139890889vscode环境配置参考:https://blog.csdn.net/qq_51355375/article/details/1407334952.工程创建include放头文件src.cpp文件CMakeList.texcmake配置文件工程结构按......
  • LoRa MESH网络拓扑及其物联网应用场景简介
    什么是LORAMESH组网技术LORAMESH组网技术是一种基于LORA传输的Mesh组网方案,LoRaMESH网络允许设备之间以多跳(multi-hop)的方式进行无线通信。LoRaMESH组网技术被广泛运用于解决“最后一公里”问题,是实现设备之间低功耗、广覆盖通信的重要手段。LoRaMESH网络拓扑简介LoRaMES......
  • Django项目快速上手:从安装到创建应用
    Django项目快速上手:从安装到创建应用安装Django首先,确保你已经安装了Python和pip。然后,使用以下命令来安装Django:pipinstalldjango安装成功后,你就可以开始创建Django项目了。创建Django项目打开你的终端或命令提示符,然后使用django-admin命令来创建一个新的Djan......
  • 如何在 FastAPI 中手动创建 UploadFile?
    背景我正在尝试为我的FastAPI应用程序编写测试。我有一个对象ImageRecord,它将图像上传到S3。方法签名如下:asyncdefupload_image(file:UploadFile,s3_client=None)->'ImageRecord':API逻辑(即实际的@app.post函数)调用此方法来上传对象。我想......