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

news2024/11/17 0:30:28

概述

很早之前发布过一篇关于几何体程序生成的文章,当时对于三角面和网格的构造其实还没有特别深入的认识,直到自己脑海里想到用二维数组和点更新的方式构造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 Fan)

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

在这里插入图片描述

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

在这里插入图片描述

三角带(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:PackedVector3Array
			if 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 EditorScript

var 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 EditorScript

var 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 = $MeshInstance2D


func _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 = $MeshInstance2D


func _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 = $MeshInstance2D


func _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 = $MeshInstance2D


func _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] = triangles
	arr[Mesh.ARRAY_TEX_UV] = uvs
	mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,arr)
	
	mesh_instance_3d.mesh = mesh
	mesh_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 = $MeshInstance2D


func _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 = mesh
	mesh_instance_2d.mesh = mesh

参考

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2154145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode 每周算法 6(图论、回溯)

LeetCode 每周算法 6(图论、回溯) 图论算法: class Solution: def dfs(self, grid: List[List[str]], r: int, c: int) -> None: """ 深度优先搜索函数,用于遍历并标记与当前位置(r, c)相连的所有陆地&…

uni-data-select 使用 localdata 传入数据出现 不回显 | 下拉显示错误的 解决方法

目录 1. 问题所示2. 正确Demo3. 下拉显示错误(Bug复现)4. 下拉不回显(Bug复现)1. 问题所示 uni-app的下拉框uni-data-select 使用 localdata 传入数据 主要总结正确的Demo以及复现一些Bug 数据不回显数据不显示下拉选项2. 正确Demo 详细的基本知识推荐阅读:uni-app中的…

Codeforces Round 974 (Div. 3) A-F

封面原图 画师礼島れいあ 下午的ICPC网络赛的难受一晚上全都给我打没了 手速拉满再加上秒杀线段树 这场简直了啊 唯一可惜的是最后还是掉出了1000名 一把上蓝应该没啥希望了吧 A - Robin Helps 题意 侠盗罗宾因劫富济贫而闻名于世 罗宾遇到的 n n n 人,从 1 s …

springMvc的初始配置

基础文件结构(toWeb插件) 1.导入对应依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"ht…

BLE 设备丢包理解

前言 个人邮箱&#xff1a;zhangyixu02gmail.com在学习 BLE 过程中&#xff0c;总能听到 “丢包” 一词&#xff0c;但是我查阅资料又发现&#xff0c;有大佬说&#xff0c;ATT所有命令都是“必达”的&#xff0c;不存在所谓的“丢包”。而且我发现&#xff0c;在宣传 BLE 产品…

tcp、udp通信调试工具Socket Tool

tcp、udp通信调试工具Socket Tool ]

Rust 运算符快速了解

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 4.1 运 算 符 前面已经学习了变量和常量&#xff0c;本节开始对它们进行操作&am…

java之单链表的基本概念及创建

1.链表的概念: 链表是一种 物理存储结构上非连续 存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。 组成结构: 由一系列节点组成&#xff0c;每个节点包含数据域和指向下一个节点的指针。 优点: 动态大小&#xff0c;易于插入和删除操作。 缺点…

【网络安全 | 靶机搭建】修改镜像源、更新软件源、安装git、更改python版本等

文章目录 0x00、必要准备0x01、修改镜像源0x02、更新软件源并清除缓存0x03、安装git0x04、更改默认Python版本为python30x05、安装增强功能0x06、vmware虚拟机导出iso0x00、必要准备 安装虚拟机时必须保存用户名、密码,用于后续操作,可以截图保存: 以下内容按个人需要进行配…

如何将生物序列tokenization为token?

原理讲解 tokenization是自然语言处理领域非常成熟的一项技术&#xff0c;tokenization就是把我们研究的语言转换成计算机能够识别的数字——token。 在生物领域&#xff0c;如何把核苷酸或氨基酸序列tokenization成token呢&#xff1f; 我们可以使用k-mer技术&#xff1a; k-m…

GDPU Andriod移动应用 Activity

移动应用开发入门级必看&#xff0c;用活动打造属于你的页面。 重要的更新公告 &#xff01;&#xff01;&#xff01;GDPU的小伙伴&#xff0c;感谢大家的支持&#xff0c;希望到此一游的帅哥美女能有所帮助。本学期的前端框架及移动应用&#xff0c;采用专栏订阅量达到50才开…

医院伤员小程序点餐———未来之窗行业应用跨平台架构

一、读取服务器医院信息 var 未来之窗人工智-商家信息-医院职工 {//2024-09-22 cyber_getMerchant_CardUser_V20240922: function(appikkey,merchant_id,store_id,ecogen_sponsor_appid,openid,frommsg,wlzc_callback) {//2023-7-6 里程碑var wlzcapi"加入url";wx.re…

深度学习自编码器 - 去噪自编码器篇

序言 在深度学习的广阔天地中&#xff0c;自编码器作为一种强大的无监督学习工具&#xff0c;通过重构输入数据的方式&#xff0c;不仅实现了数据的有效压缩&#xff0c;还探索了数据的内在表示。而去噪自编码器&#xff08; Denoising Autoencoder, DAE \text{Denoising Auto…

ES6 -- 2015

学习视频 1. let和const 1.1 let 变量必须先声明再使用同一变量不能重复声明变量有块级作用域 1.2 const 声明常量&#xff0c;常量不能改变常量必须有初始值&#xff0c;不能先声明再赋值 2. 解构 1 数组解构 保持左右的结构一样&#xff0c;安装顺序一一对应 完全解构…

CVE-2024-46101

前言 自己挖的第一个CVE~ 喜提critical 这里简单说一下。 漏洞简介 GDidees CMS < 3.9.1 的版本&#xff0c;存在一个任意文件上传漏洞。允许登录后的攻击者上传webshell获得网站的权限。 影响版本&#xff1a; GDidees CMS < 3.9.1 &#xff08;其它的我没测。。&am…

日志系统扩展二:日志服务器的实现

日志系统扩展二&#xff1a;日志服务器的实现 一、设计1.为何要这么扩展&#xff1f;2.应用层协议的选择1.HTTP&#xff1f;2.自定义应用层协议 二、自定义应用层协议的实现1.声明日志器1.服务器和客户端这里日志器的关联2.枚举类型的定义3.sinks数组的定义 2.打印日志1.logMes…

MySQL record 06 part

事务、存储过程 事务&#xff1a; MySQL的同步&#xff0c;同步是指 together done&#xff0c;要么一起前进&#xff0c;要么一起后退的意思。 注意&#xff0c;回滚 rollback 对已经提交 commit 的数据是无效的&#xff0c;也就是说&#xff0c;只能对没有被提交 commit …

CSS 布局三大样式简单学习

目录 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 relative 2.3 fixed 3. css 盒子模型 3.1 效果1 3.2 效果2 3.3 效果3 3.4 效果4 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 …

thinkphp 做分布式服务+读写分离+分库分表(分区)(后续接着写)

thinkphp 做分布式服务读写分离分库分表&#xff08;分区&#xff09; 引言 thinkphp* 大道至简一、分库分表分表php 分库分表hash算法0、分表的方法&#xff08;thinkphp&#xff09;1、ThinkPHP6 业务分表之一&#xff1a;UID 发号器2、ThinkPHP6 业务分表之二&#xff1a;用…

希尔排序(C语言实现)

目录 1.希尔排序( 缩小增量排序 ) 2.动图 ​编辑 3.代码实现 预排序实现 子序列排列实现 单趟排序实现 对整组数进行子排序 希尔排序代码 代码测试 时间复杂度分析 希尔排序的特性总结&#xff1a; 1.希尔排序( 缩小增量排序 ) 基本思想&#xff1a; 1.先选定一个…