HomeArchiveFeedShelf

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

https://chat.openai.com

@2023-11-25 14:47