Godot中的动态3D物理绳索 - 第2部分
在上一篇文章中,我们解决了在飞行中生成PinJoint3D的问题,给定两个PhysicsBody3D和绳索的长度。
在本文中,我们尝试在全局空间中绘制一个3D绳索网格以及那些PinJoint3D。
ImmediateMesh和Curve3D
ImmediateMesh非常适合,因为我们的绳索在每个物理帧中都在不断更新。ImmediateMesh提供了OpenGL 1.x风格的即时模式API来绘制网格。
Godot还提供了一个名为Curve3D的类,正如其名称所示。结合这两个类,我们可以通过以下步骤绘制我们的绳索:
- 在绳索生成过程中,所有
PinJoint3D在正确的位置排列后,我们将它们的位置以及两个锚点位置保存到Curve3D中。 - 在每个物理帧中,我们根据pinjoints和锚点的位置更新曲线的点,这些位置已经由游戏引擎的物理引擎解决。
- 在每个渲染帧中,我们使用
ImmediateMesh和曲线中的点绘制3D绳索。
从Curve3D绘制3D绳索
对于曲线的每个点,我们将该点挤出成一个圆圈。它不是参数方程中的精确圆,而是由离散顶点构成的近似圆,因为我们正在进行实时渲染而不是离线光线追踪。
下面的函数返回曲线某一点的横截面圆上的点。随着section_circle_resolution的增加,圆会更加连续,但会以性能为代价。
func _generate_cross_section_circle_points(point: Vector3, next_point: Vector3) -> PackedVector3Array:
var points := PackedVector3Array()
var dir := next_point - point
var right := dir.cross(Vector3.UP).normalized()
var up := right.cross(dir).normalized()
for i in section_circle_resolution:
var phi = 2.0 * PI * float(i) / float(section_circle_resolution)
var local_point := Vector3(sin(phi) * radius, cos(phi) * radius, 0.0)
var global_point := point + right * local_point.x + up * local_point.y
points.push_back(global_point)
return points
在我们获得圆上的点后,我们在兄弟圆之间绘制四边形。一个四边形实际上是两个三角形。请注意,我们将顶点顺时针传递给ImmediateMesh。( a[i], b[i], b[j] ) 然后是( a[i], b[j], a[j] )。

func _draw_quad_between_circle_points(circle_points: PackedVector3Array, next_circle_points: PackedVector3Array):
for i in section_circle_resolution:
var j := (i + 1) % section_circle_resolution
# 第一个三角形
var i_normal := -1.0 * (circle_points[i] - circle_points[j]).cross(next_circle_points[i] - circle_points[i]).normalized()
mesh.surface_set_normal(i_normal)
mesh.surface_add_vertex(circle_points[i])
var next_i_normal := -1.0 * (circle_points[i] - next_circle_points[i]).cross(next_circle_points[i] - next_circle_points[j]).normalized()
mesh.surface_set_normal(next_i_normal)
mesh.surface_add_vertex(next_circle_points[i])
var next_j_normal := -1.0 * (circle_points[j] - next_circle_points[j]).cross(next_circle_points[i] - next_circle_points[j]).normalized()
mesh.surface_set_normal(next_j_normal)
mesh.surface_add_vertex(next_circle_points[j])
# 第二个三角形
mesh.surface_set_normal(i_normal)
mesh.surface_add_vertex(circle_points[i])
mesh.surface_set_normal(next_j_normal)
mesh.surface_add_vertex(next_circle_points[j])
var j_normal := -1.0 * (circle_points[i] - circle_points[j]).cross(next_circle_points[j] - circle_points[j]).normalized()
mesh.surface_set_normal(j_normal)
mesh.surface_add_vertex(circle_points[j])
有关更多详细信息,您可以查看github repo,特别是源文件rope_mesh.gd
进一步改进
该repo仍有很多工作要做,比如UV和用Rust等本地语言重写,以支持同时更多的pinjoints。我们拥有的pinjoints越多,绳索的感觉和行为就越真实。
参考
https://docs.godotengine.org/en/stable/tutorials/3d/procedural_geometry/immediatemesh.html
https://docs.godotengine.org/en/stable/classes/class_curve3d.html