【Godot4.x】Mesh相关知识总结

embedded/2024/9/22 12:30:29/

概述

很早之前发布过一篇关于几何体程序生成的文章,当时对于三角面和网格的构造其实还没有特别深入的认识,直到自己脑海里想到用二维数组和点更新的方式构造2D类型的多边形Mesh结构,也意识到在Godot中其实Mesh不仅是3D网格,也可以构造2D网格。

在查看一些文章以及复习Godot相关的内置文档之后,发现基于三角面的Mesh构造,其基本结构包含两种形式,一种是三角扇,一种是三角带。几乎所有的几何图形和三维立体结构,都可以用这两种结构组合生成。

所以本文是仍然一个很基础的总结,但是后续基于这些总结,能搞出什么,就不一定了。

网格(Mesh)的基础知识

网格分类

按照计算机图形学,网格(Mesh)大致可以分为三角网格(Triangle Mesh)和多边形网格。三角网格的所有面都是由三角形组成的,而多边形网格是由四边形或更多边形组成的。

而在Godot中,Mesh(网格)是一种资源类型,存储三维空间中基于三角面的几何体数据,通常用于3D场景的MeshInstance3D节点显示3D物体。

但同时,在2D场景中,也有对应的MeshInstance2D节点,可以显示2D网格。

Godot并没有提供直接编辑模型网格的能力。网格还是需要使用像Blender这样的3D建模软件来创建,然后再导入到Godot中使用。

三角和三角面

无论是2D平面还是3D空间,都需要使用三个点来定义一个三角形。在Mesh中,每三个点定义一个三角面,这个三角面有正面和背面之分,而正面、背面是由定义它的三个顶点的环绕顺序和最终的三角面法向量决定的。

环绕顺序

当我们定义一组三角形顶点时,我们会以特定的环绕顺序来定义它们,可能是顺时针(Clockwise)的,也可能是逆时针(Counter-clockwise)的。每个三角形由3个顶点所组成,我们会从三角形中间来看,为这3个顶点设定一个环绕顺序。 – 面剔除 - LearnOpenGL CN (learnopengl-cn.github.io)

在这里插入图片描述

Godot 对三角形图元模式的正面使用顺时针环绕顺序。而OpenGL默认将逆时针顶点所定义的三角形当做是正向三角形。

Triangle_Fanfont_29">三角扇(Triangle Fan)

巧妙的从一个“共点”出发,可以更轻松的构造连续性的“共边”三角面集合,也就更容易创建多边形。这种基于共点构造的连续相邻三角面结构被称为“三角扇”,可以构造开放和闭合的三角扇结构。

在这里插入图片描述

矩形是三角扇的一种特殊形式,可以拓扑为一般的三角扇形式。

在这里插入图片描述

Triangle_Stripfont_40">三角带(Triangle Strip)

还有一种三角面结构交“三角带”,特点是相邻三角形共边

在这里插入图片描述

三角网格数据表示

了解三角网格的两种连续结构——三角带和三角扇之后,就可以基于两种结构进行顶点存储的设计。三角带和三角扇的顶点存储顺序是不一样的。

在这里插入图片描述

一般用两个数组,分别存储顶点数据和三角面数据,它们分别被称为顶点表和索引表。

  • 顶点表:用于顺序存储顶点(不重复)
  • 索引表:用每三个顶点的索引值代表一个三角面

三角带和三角扇如果顶点表顺序得当,就可以暗含三角形的信息。

可以通过遍历形式,快速生成索引表。

比如:

  • 上图左的三角扇,其三角形集合为:[[0,1,2],[0,2,3],[0,3,4],[0,4,5]],其规律十分明显,用遍历顶点表的方式可以快速生成
  • 上图右的三角带,其三角形集合为:[[0,1,2],[3,2,1],[2,3,4],[5,4,3]],其实可以理解为:[[0,1,2],[1,2,3].reverse(),[2,3,4],[3,4,5].reverse()],也很容易通过遍历顶点表生成。

三角带与三角扇的顶点数与三角形的关系

顶点数三角形数
31
42
53
64

可以看到无论是三角带还是三角扇,都符合顶点数 = 三角形数 + 2的规律。

三角带与三角扇网格生成函数

# 通过顶点表返回三角扇结构的三角形集合
func triangle_fan_3d(vetexs:PackedVector3Array) -> Array[PackedVector3Array]:var triangles:Array[PackedVector3Array]if vetexs.size() >2:for i in range(vetexs.size()-2):var tri:PackedVector3Array = [vetexs[0],vetexs[i+1],vetexs[i+2]]triangles.append(tri)return triangles# 通过顶点表返回三角带结构的三角形集合
func triangle_strip_3d(vetexs:PackedVector3Array) -> Array[PackedVector3Array]:var triangles:Array[PackedVector3Array]if vetexs.size() >2:for i in range(vetexs.size()-2):var tri:PackedVector3Arrayif i % 2 == 0: # 奇数项tri = [vetexs[i],vetexs[i+1],vetexs[i+2]]else:tri = [vetexs[i],vetexs[i+1],vetexs[i+2]]tri.reverse()  # 翻转triangles.append(tri)return triangles

测试:

@tool
extends EditorScriptvar points:PackedVector3Array = [Vector3(0,0,0),Vector3(0,1,0),Vector3(1,1,0),Vector3(1,0,0),
]func _run() -> void:print(triangle_fan_3d(points))

打印输出:

[[(0, 0, 0), (0, 1, 0), (1, 1, 0)], [(0, 0, 0), (1, 1, 0), (1, 0, 0)]]
@tool
extends EditorScriptvar points:PackedVector3Array = [Vector3(0,0,0),Vector3(0,1,0),Vector3(2,0,0),Vector3(0,3,0),
]func _run() -> void:print(triangle_strip_3d(points))

输出:

[[(0, 0, 0), (0, 1, 0), (2, 0, 0)], [(0, 3, 0), (2, 0, 0), (0, 1, 0)]]

注意

这里为了方便演示效果,直接获取了三角形的顶点组合,实际中,主需要生成顶点索引组成的三角形列表就可以了。


顶点着色

三角网格的每个顶点都可以设置一个单独的颜色。

贴图与UV坐标

三角网格的每个顶点都可以设置一个UV坐标,用于对应贴图的一部分。

法向量与切向量

平滑组

程序式几何体生成

涉及SurfaceToolArrayMeshImmediateMesh以及 MeshDataTool等内部类。

使用SurfaceTool

在这里插入图片描述
在这里插入图片描述

依次添加顶点形式

@tool
extends Node3D@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var mesh_instance_2d: MeshInstance2D = $MeshInstance2Dfunc _enter_tree() -> void:await ready	var sf = SurfaceTool.new()sf.begin(Mesh.PRIMITIVE_TRIANGLES)# 添加第1点sf.set_color(Color.AQUA)sf.set_uv(Vector2(0,0))sf.add_vertex(Vector3(0,0,0))# 添加第2点sf.set_color(Color.AQUAMARINE)sf.set_uv(Vector2(0,1))sf.add_vertex(Vector3(0,1,0))# 添加第3点sf.set_color(Color.RED)sf.set_uv(Vector2(1,1))sf.add_vertex(Vector3(1,1,0))mesh_instance_2d.mesh = sf.commit()

在这里插入图片描述

  • 顶点颜色在MeshInstance3D不起作用,在MeshInstance2D中起作用
  • MeshInstance2D中,要让纹理起作用,必须在添加顶点之前用set_uv()设定UV坐标

)

用数组形式添加

@tool
extends Node3D@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var mesh_instance_2d: MeshInstance2D = $MeshInstance2Dfunc _enter_tree() -> void:await ready	var sf = SurfaceTool.new()sf.begin(Mesh.PRIMITIVE_TRIANGLES)# 多个三角形组成的三角扇var triangles:= [# 第1个三角形Vector3(0,0,0),Vector3(0,1,0),Vector3(1,1,0)]var uvs:=[# 第1个三角形对应顶点的UV坐标Vector2(0,0),Vector2(0,1),Vector2(1,1)]sf.add_triangle_fan(triangles,uvs)mesh_instance_3d.mesh = sf.commit()mesh_instance_2d.mesh = sf.commit()

创建2个三角面组成矩形

@tool
extends Node3D@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var mesh_instance_2d: MeshInstance2D = $MeshInstance2Dfunc _enter_tree() -> void:await ready	var sf = SurfaceTool.new()sf.begin(Mesh.PRIMITIVE_TRIANGLES)# 多个三角形组成的三角扇var triangles:= [# 第1个三角形Vector3(0,0,0),Vector3(0,1,0),Vector3(1,1,0),# 第2个三角形Vector3(1,1,0),Vector3(1,0,0),Vector3(0,0,0),]var uvs:=[# 第1个三角形对应顶点的UV坐标Vector2(0,0),Vector2(0,1),Vector2(1,1),# 第2个三角形对应顶点的UV坐标Vector2(1,1),Vector2(1,0),Vector2(0,0),]sf.add_triangle_fan(triangles,uvs)mesh_instance_3d.mesh = sf.commit()mesh_instance_2d.mesh = sf.commit()

在这里插入图片描述

使用“共点”和构造扇面的思维,顶点和uv数据都可以大大简化。

# 不重复的顶点
var vertexs = [Vector3(0,0,0),Vector3(0,1,0),Vector3(1,1,0),Vector3(1,0,0),
]
# UV坐标
var uv_arr = [Vector2(0,0),Vector2(0,1),Vector2(1,1),Vector2(1,0),
]# 多个三角形组成的三角扇
var triangles:= [# 第1个三角形vertexs[0],vertexs[1],vertexs[2],# 第2个三角形vertexs[0],vertexs[2],vertexs[3],
]var uvs:=[# 第1个三角形对应顶点的UV坐标uv_arr[0],uv_arr[1],uv_arr[2],# 第2个三角形对应顶点的UV坐标uv_arr[0],uv_arr[2],uv_arr[3],
]

直接构造ArrayMesh实例

@tool
extends Node3D@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var mesh_instance_2d: MeshInstance2D = $MeshInstance2Dfunc _enter_tree() -> void:await ready	# 多个三角形组成的三角扇var triangles:PackedVector3Array= [# 第1个三角形Vector3(0,0,0),Vector3(0,1,0),Vector3(1,1,0),# 第2个三角形Vector3(1,1,0),Vector3(1,0,0),Vector3(0,0,0),]var uvs:PackedVector2Array=[# 第1个三角形对应顶点的UV坐标Vector2(0,0),Vector2(0,1),Vector2(1,1),# 第2个三角形对应顶点的UV坐标Vector2(1,1),Vector2(1,0),Vector2(0,0),]var mesh = ArrayMesh.new()var arr = []arr.resize(Mesh.ARRAY_MAX)arr[Mesh.ARRAY_VERTEX] = trianglesarr[Mesh.ARRAY_TEX_UV] = uvsmesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,arr)mesh_instance_3d.mesh = meshmesh_instance_2d.mesh = mesh

可以看到,Mesh的核心数据,是一个固定结构的二维数组,其每个子数组分别对应不同的数据信息。ArrayMesh通过这样的数组,可以直接创建Mesh也就不足为奇。

var arr = [[],  # 0,ARRAY_VERTEX[],  # 1,ARRAY_NORMAL[],  # 2,ARRAY_TANGENT[],  # 3,ARRAY_COLOR[],  # 4,ARRAY_TEX_UV[],  # 5,ARRAY_TEX_UV2[],  # 6,ARRAY_CUSTOM0[],  # 7,ARRAY_CUSTOM1[],  # 8,ARRAY_CUSTOM2[],  # 9,ARRAY_CUSTOM3[],  # 10,ARRAY_BONES[],  # 11,ARRAY_WEIGHTS[],  # 12,ARRAY_INDEX
] 

其中:顶点数组可以是PackedVector2Array也可以是PackedVector3Array

ImmediateMesh

在这里插入图片描述

示例:

@tool
extends Node3D@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var mesh_instance_2d: MeshInstance2D = $MeshInstance2Dfunc _enter_tree() -> void:await ready	# 多个三角形组成的三角扇var triangles:PackedVector3Array= [# 第1个三角形Vector3(0,0,0),Vector3(0,1,0),Vector3(1,1,0),# 第2个三角形Vector3(1,1,0),Vector3(1,0,0),Vector3(0,0,0),]var uvs:PackedVector2Array=[# 第1个三角形对应顶点的UV坐标Vector2(0,0),Vector2(0,1),Vector2(1,1),# 第2个三角形对应顶点的UV坐标Vector2(1,1),Vector2(1,0),Vector2(0,0),]var mesh = ImmediateMesh.new()mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)  # 开始创建表面for i in range(triangles.size()):mesh.surface_set_uv(uvs[i])               # 设定顶点UV坐标mesh.surface_add_vertex(triangles[i])     # 添加顶点mesh.surface_end()                            # 结束mesh_instance_3d.mesh = meshmesh_instance_2d.mesh = mesh

参考

  • 面剔除 - LearnOpenGL CN (learnopengl-cn.github.io)
  • Godot4.3官方文档和引擎内置文档
  • 基本3D图形:多边形网格浅谈 - 知乎 (zhihu.com)
  • 三角网格(Triangle Mesh)与四角mesh网格理解总结 - 知乎 (zhihu.com)

http://www.ppmy.cn/embedded/115029.html

相关文章

WPS生成目录

导航窗格:视图->导航窗格 可修改标题的样式,之后的标题直接套用即可 修改其他标题样式也是这样 添加编号:可以选上面的模版 也可自定义编号 生成目录:引用->目录->选用一个 但是我想把目录插到另一页 当我添加几个标题…

状态模式:将对象行为与状态解耦

状态模式(State Pattern)是一种行为设计模式,它允许对象在其内部状态改变时改变其行为,使对象看起来好像修改了其类。 状态模式的核心思想是将对象的行为封装在不同的状态对象中,每个状态对象都代表了对象在某一特定状…

linux部署Java项目时,阿里云OSS报错“超出了最大允许的时间偏差范围“

项目场景: linux部署Java项目时,阿里云OSS报错—RequestTimeTooSkewed 问题描述 linux部署Java项目时,阿里云OSS报错—RequestTimeTooSkewed 详细错误信息如下 [ErrorCode]: RequestTimeTooSkewed [RequestId]: 66ED6295352E0D3332BE4CC7 …

如何进行IP清洗

在数据抓取、网络爬虫或网络营销等活动中,IP地址的清洗是一个至关重要的环节。IP清洗旨在移除无效、受污染或可能引发问题的IP地址,从而提高网络活动的效率和安全性。本文将详细介绍如何进行IP清洗,包括识别问题IP、选择清洗工具、执行清洗过…

创客中国AIGC专题赛冠军天鹜科技:AI蛋白质设计引领者

“落霞与孤鹜齐飞,秋水共长天一色——这句出自《滕王阁序》的诗句,是我作为江西人熟记于心的佳句。它描绘的天地壮丽景色常浮现于我的脑海,正是这种豁达与壮观,启发我们将公司命名为‘天鹜科技’,我们希望将源自自然的蛋白质与现代科技的创新精神相结合,打造蛋白质设计与应用的…

PyCharm的使用

PyCharm的入门使用教程 下载和安装PyCharm: 首先,访问JetBrains官方网站(https://www.jetbrains.com/pycharm/)下载PyCharm的最新版本。根据您的操作系统选择合适的版本进行下载。 安装完成后,打开PyCharm。 创建新…

ftrace - 几种tracer的打印例子

ftrace - Function Tracer — The Linux Kernel documentation【原创】Ftrace使用及实现机制 - 沐多 - 博客园 (cnblogs.com) latency format nop tracer和function tracer下,latency format的时间戳是相对开始trace的时间,non-latency format的时间戳是…

ubuntu24安装vivado24(安装并解决若干错误)

目录 安装方法:问题1:解决办法: 问题2:解决方法: 安装完成: 安装方法: 注意:内存最好预留80G空闲的。 安装好大小: 安装依赖库: sudo apt-get update sud…