源码:a477964

copilot prompt:

解释这个类,先是对这个类概述(不用标题),后续包含主要职责、核心成员、核心接口等,内容尽量细化,提及相关类或变量如有必要请说明(用引号包裹如:`Ref<Image>`, 不用全部都备注,要精确也要精简),格式要求“主要职责”等为3级标题,内容用多级列表形式而不是子标题分层

1 Terrain3D

这是一个基于 Godot 引擎的 3D 地形系统的核心类,继承自 Node3D。该类实现了一个功能完备的地形渲染和编辑系统,支持高度图管理、多层细节(LOD)网格生成、材质系统、物理碰撞、实例化对象管理以及完整的编辑器集成。该系统设计用于构建大型开放世界游戏场景,提供了区域化地形管理、实时编辑、调试可视化等高级功能。类中定义了两个重要枚举:调试级别(DebugLevel)用于控制日志输出,区域大小(RegionSize)用于定义地形分块尺寸。

1.1 主要职责

  • 地形数据管理

    • 管理地形高度图和控制贴图数据(_data
    • 处理地形区域(Region)的分割、加载和保存(_region_size, _save_16_bit
    • 支持 16 位精度的地形数据存储
    • 提供数据目录管理和版本控制(_data_directory, _version
  • 渲染系统控制

    • 管理多层细节(LOD)网格生成和渲染(_mesh_lods, _mesher
    • 控制地形材质、着色器和纹理系统(_material
    • 处理阴影投射、全局光照和渲染层设置(_cast_shadows, _gi_mode, _render_layers
    • 支持多种调试视图和可视化模式(debug_level
  • 物理和碰撞系统

    • 生成和管理地形碰撞形状(_collision
    • 提供射线检测和交互功能(get_intersection, get_raycast_result
    • 支持导航网格生成(generate_nav_mesh_source_geometry
    • 管理物理材质和碰撞参数
  • 编辑器集成

    • 提供完整的地形编辑功能接口(_editor, _plugin
    • 管理编辑器插件和工具集成(EditorPlugin
    • 支持实时地形修改和预览
    • 处理鼠标拾取和交互系统(_mouse_vp, _mouse_cam, _mouse_quad
  • 实例化对象管理

    • 管理地形上的植被、装饰物等实例化对象(_instancer
    • 控制实例的显示、隐藏和渲染
    • 提供实例网格的父容器管理(_mmi_parent
  • 目标跟踪和优化

    • 跟踪相机和目标节点位置(_clipmap_target, _collision_target, _camera
    • 基于距离的 LOD 优化
    • 动态裁剪和渲染优化(_cull_margin

1.2 核心成员

  • 版本和状态管理

    • _version:地形系统版本号(“1.1.0-dev”)
    • _data_directory:数据存储目录路径(String
    • _is_inside_world:是否在世界场景树中的状态标志(bool
    • _initialized:系统初始化完成标志(bool
    • _warnings:警告状态位掩码(uint8_t
  • 核心组件对象

    • _data:地形数据管理器指针,负责高度图和控制数据(Terrain3DData *
    • _material:地形材质资源引用,管理着色器和纹理(Ref<Terrain3DMaterial>
    • _assets:地形资源管理器,管理纹理和材质库(Ref<Terrain3DAssets>
    • _instancer:实例化管理器,处理植被和装饰物(Terrain3DInstancer *
    • _collision:碰撞系统管理器,处理物理交互(Terrain3DCollision *
    • _mesher:网格生成器,负责 LOD 网格创建(Terrain3DMesher *
    • _editor:编辑器管理器,提供编辑功能(Terrain3DEditor *
    • _plugin:编辑器插件引用(EditorPlugin *
  • 目标跟踪系统

    • _clipmap_target:裁剪图目标节点,用于 LOD 中心点(TargetNode3D
    • _collision_target:碰撞目标节点,用于碰撞更新中心(TargetNode3D
    • _camera:后备相机目标,当其他目标不可用时使用(TargetNode3D
  • 地形配置参数

    • _region_size:区域大小枚举(64-2048)(RegionSize
    • _save_16_bit:是否使用 16 位精度保存(bool
    • _label_distance:区域标签显示距离(real_t
    • _label_size:区域标签字体大小(int
  • 网格渲染参数

    • _mesh_lods:LOD 层数(默认 7 层)(int
    • _mesh_size:网格尺寸(默认 48)(int
    • _vertex_spacing:顶点间距(默认 1.0)(real_t
  • 渲染控制参数

    • _render_layers:渲染层位掩码(uint32_t
    • _cast_shadows:阴影投射设置(RenderingServer::ShadowCastingSetting
    • _gi_mode:全局光照模式(GeometryInstance3D::GIMode
    • _cull_margin:裁剪边距(real_t
    • _free_editor_textures:编辑器纹理释放控制(bool
  • 鼠标交互系统

    • _mouse_vp:鼠标拾取视口(SubViewport *
    • _mouse_cam:鼠标拾取相机(Camera3D *
    • _mouse_quad:鼠标光标网格(MeshInstance3D *
    • _mouse_layer:鼠标交互层(uint32_t
  • 父容器节点

    • _label_parent:标签节点的父容器(Node3D *
    • _mmi_parent:多网格实例的父容器(Node3D *

1.3 核心接口

  • 基础管理接口

    • get_version():获取系统版本号(String
    • set_debug_level() / get_debug_level():调试级别控制(DebugLevel
    • set_data_directory() / get_data_directory():数据目录管理(String
    • is_inside_world():检查是否在场景树中(bool
  • 组件对象访问接口

    • get_data():获取地形数据管理器(Terrain3DData *
    • set_material() / get_material():地形材质管理(Ref<Terrain3DMaterial>
    • set_assets() / get_assets():地形资源管理(Ref<Terrain3DAssets>
    • get_collision():获取碰撞管理器(Terrain3DCollision *
    • get_instancer():获取实例化管理器(Terrain3DInstancer *
    • get_mmi_parent():获取多网格实例父容器(Node *
    • set_editor() / get_editor():编辑器管理(Terrain3DEditor *
    • set_plugin() / get_plugin():插件管理(EditorPlugin *
  • 目标跟踪控制接口

    • set_camera() / get_camera():相机目标设置(Camera3D *
    • set_clipmap_target() / get_clipmap_target():裁剪图目标管理(Node3D *
    • get_clipmap_target_position():获取裁剪图目标位置(Vector3
    • set_collision_target() / get_collision_target():碰撞目标管理(Node3D *
    • get_collision_target_position():获取碰撞目标位置(Vector3
    • snap():执行目标位置对齐操作
  • 地形区域配置接口

    • set_region_size() / get_region_size():区域大小设置(RegionSize
    • change_region_size():动态改变区域大小(RegionSize
    • set_save_16_bit() / get_save_16_bit():16 位保存模式控制(bool
    • set_label_distance() / get_label_distance():标签显示距离(real_t
    • set_label_size() / get_label_size():标签字体大小(int
    • update_region_labels():更新区域标签显示
  • 网格和渲染配置接口

    • set_mesh_lods() / get_mesh_lods():LOD 层数控制(int
    • set_mesh_size() / get_mesh_size():网格尺寸设置(int
    • set_vertex_spacing() / get_vertex_spacing():顶点间距控制(real_t
    • set_render_layers() / get_render_layers():渲染层设置(uint32_t
    • set_mouse_layer() / get_mouse_layer():鼠标交互层设置(uint32_t
    • set_cast_shadows() / get_cast_shadows():阴影投射控制(RenderingServer::ShadowCastingSetting
    • set_gi_mode() / get_gi_mode():全局光照模式(GeometryInstance3D::GIMode
    • set_cull_margin() / get_cull_margin():裁剪边距设置(real_t
    • set_free_editor_textures() / get_free_editor_textures():编辑器纹理释放控制(bool
    • set_show_instances() / get_show_instances():实例显示控制(bool
  • 交互和工具接口

    • get_intersection():射线与地形求交计算(Vector3
    • get_raycast_result():射线检测结果获取(Dictionary
    • bake_mesh():烘焙地形网格为静态网格(Ref<Mesh>
    • generate_nav_mesh_source_geometry():生成导航网格源几何体(PackedVector3Array
  • 碰撞系统别名接口

    • set_collision_mode() / get_collision_mode():碰撞模式设置(CollisionMode
    • set_collision_shape_size() / get_collision_shape_size():碰撞形状尺寸(uint16_t
    • set_collision_radius() / get_collision_radius():碰撞半径控制(uint16_t
    • set_collision_layer() / get_collision_layer():碰撞层设置(uint32_t
    • set_collision_mask() / get_collision_mask():碰撞掩码设置(uint32_t
    • set_collision_priority() / get_collision_priority():碰撞优先级(real_t
    • set_physics_material() / get_physics_material():物理材质管理(Ref<PhysicsMaterial>
  • 调试可视化接口

    • 覆盖层显示set_show_region_grid(), set_show_instancer_grid(), set_show_vertex_grid(), set_show_contours(), set_show_navigation()
    • 调试视图显示set_show_checkered(), set_show_grey(), set_show_heightmap(), set_show_jaggedness(), set_show_autoshader()
    • 控制纹理显示set_show_control_texture(), set_show_control_blend(), set_show_control_angle(), set_show_control_scale()
    • 纹理调试显示set_show_colormap(), set_show_roughmap(), set_show_texture_height(), set_show_texture_normal(), set_show_texture_rough()
  • 警告和配置接口

    • set_warning():设置特定警告状态(uint8_t, bool
    • get_warnings():获取所有警告状态(uint8_t
    • _get_configuration_warnings():获取配置警告信息(覆盖基类方法)(PackedStringArray

1.3.1 Terrain3D::_initialize

  • 构建地形数据(Terrain3DData)、资源(Terrain3DMaterial、Terrain3DAssets)、功能句柄(Terrain3DInstancer、Terrain3DCollision、Terrain3DMesher)和编辑器(Terrain3DEditor、EditorPlugin)
  • 绑定各种信号函数,其中信号全在 Terrain3DData::_bind_methods 中通过 ADD_SIGNAL 宏注册
  • 初始化 Terrain3DData、Terrain3DMaterial 等
  • update_configuration_warnings() 更新配置警告

Note

Terrain3D 的配置警告通过 set_warning 方法设置(见第 833 行)。 该方法会根据传入的 p_warningp_enabled 参数,设置或清除 _warnings 位掩码,然后调用 update_configuration_warnings() 更新配置警告。 具体的警告内容在 _get_configuration_warnings() 方法(842 行)中定义,比如数据目录为空、纹理尺寸不匹配、格式不匹配等。

1.3.2 Terrain3D::__physics_process

Note

__physics_process 在这里实际上是 C++ 绑定 Godot 的 _physics_process。 由于 godot-cpp/issues/1022 的原因,C++ 里不能直接用 _physics_process,所以用 __physics_process 作为代理函数,然后在 _notification 里手动调用。 GDScript 里的 _physics_process 是 Godot 的物理帧回调,C++ 里通过这种方式实现同样的功能。

__physics_processTerrain3D 类中的一个私有方法,用于在每一帧物理更新时执行地形相关的处理。其主要功能如下:

  • 检查 Terrain3D 是否已初始化,未初始化则直接返回。
  • 检查相机对象 _camera 是否有效,无效则尝试获取当前相机。
  • 如果 _mesher 存在,调用其 snap() 方法,通常用于网格对齐或更新。
  • 如果 _collision 存在,调用其 update() 方法,更新碰撞信息。

该方法通常由 _notificationNOTIFICATION_PHYSICS_PROCESS 事件间接调用,实现地形的物理帧更新逻辑。

1.3.3 Terrain3D::_grab_camera

_grab_camera() 函数的作用是:

  • 在编辑器环境下(IS_EDITOR 为真时),获取第一个编辑器视口(viewport)的 3D 摄像机,并将其设置为当前地形对象的相机目标。这样可以确保在编辑器中操作地形时,地形系统能正确跟踪和使用编辑器视口的相机。
  • IS_EDITOR 为假时,_grab_camera() 会通过 get_viewport()->get_camera_3d() 获取游戏运行时的主摄像机,并将其设置为当前地形对象的相机目标。如果找不到有效的摄像机且没有设置 clipmap 目标,则会关闭物理处理并输出错误日志,提示 LOD 不会被更新,需要手动设置目标。 函数还会输出调试日志,记录获取到的相机对象。

1.3.4 Terrain3D::_build_containers

_build_containers() 函数的作用是为 Terrain3D 节点创建并添加两个子节点容器:一个用于存放地形标签(_label_parent),一个用于存放 MMI 相关节点(_mmi_parent)。它们都是 Node3D 类型,并分别命名为 "Labels""MMI",随后通过 add_child 方法添加到当前节点下。这为后续动态添加、管理地形标签和 MMI 实例提供了专用的父节点容器,便于统一管理和清理。

什么是 MMI?

MMI 是一个节点(Node3D)的名称。它通过 _mmi_parent->set_name("MMI"); 这行代码被设置。具体含义需要结合项目上下文来看,但通常 MMI 可能是 Mesh Multi-InstanceMesh Material Instance 的缩写,用于管理地形相关的实例化对象或网格实例。

1.3.5 Terrain3D::_setup_mouse_picking

Terrain3D::_setup_mouse_picking() 是一个私有成员函数,用于初始化地形的鼠标拾取相关功能。它会在节点进入场景树时被调用,主要作用如下:

  • 检查节点是否在场景树中,如果不在则记录错误并返回。
  • 创建一个名为 MouseViewportSubViewport 子节点,并设置其大小、缩放模式和更新模式。
  • 该视口用于后续的鼠标拾取操作,比如通过 GPU 深度纹理或射线检测获取地形上的交点。

此函数确保地形对象具备鼠标拾取和交互的基础设施。

什么是鼠标拾取?

鼠标拾取(Mouse Picking)是指在三维场景中,通过鼠标点击或移动,确定鼠标指针在场景中所指向的对象或位置的技术。常用于选中、交互、拖拽物体等功能。实现方式通常包括射线检测(Raycast)或利用 GPU 深度信息获取鼠标对应的三维坐标。


_mouse_vp = memnew(SubViewport);  
_mouse_vp->set_name("MouseViewport");  
add_child(_mouse_vp, true);  
_mouse_vp->set_size(Vector2i(2, 2));  
_mouse_vp->set_scaling_3d_mode(Viewport::SCALING_3D_MODE_BILINEAR);  
_mouse_vp->set_update_mode(SubViewport::UPDATE_ONCE);  
_mouse_vp->set_handle_input_locally(false);  
_mouse_vp->set_canvas_cull_mask(0);  
_mouse_vp->set_use_hdr_2d(true);  
_mouse_vp->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST);  
_mouse_vp->set_positional_shadow_atlas_size(0);  
_mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);  
_mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(1, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);  
_mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(2, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);  
_mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(3, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);

这段代码的作用是在 Godot 引擎中为鼠标拾取功能创建并配置一个 SubViewport(子视口)对象。具体来说:

  • 创建一个名为 MouseViewport 的 2x2 像素的子视口,用于渲染鼠标拾取相关的内容。
  • 设置视口的 3D 缩放模式为双线性(BILINEAR),只在需要时更新(UPDATE_ONCE),并关闭本地输入处理。
  • 关闭画布剔除遮罩(canvas cull mask),启用 2D HDR,设置画布项纹理过滤为最近点采样(NEAREST)。
  • 关闭所有位置阴影图集(shadow atlas),以节省资源。

Note

这里设置 2x2 像素的 SubViewport(vp)而不是 1x1,主要原因有:

  1. 某些显卡或渲染后端对极小尺寸(如 1x1)的帧缓冲支持不佳,可能导致渲染异常或采样不稳定。
  2. 2x2 能更好地兼容多平台,且采样时可以取中心像素,避免边缘采样带来的误差。
  3. 对鼠标拾取来说,2x2 已经足够小,精度和性能都能兼顾。

所以一般会用 2x2 作为最小安全尺寸,而不是 1x1

这些设置确保该子视口只用于鼠标拾取相关的 GPU 采样,不参与主渲染流程,也不会影响性能和主场景显示。


_mouse_cam = memnew(Camera3D);  
_mouse_cam->set_name("MouseCamera");  
_mouse_vp->add_child(_mouse_cam, true);  
Ref<Environment> env;  
env.instantiate();  
env->set_tonemapper(Environment::TONE_MAPPER_LINEAR);  
_mouse_cam->set_environment(env);  
Ref<Compositor> comp;  
comp.instantiate();  
_mouse_cam->set_compositor(comp);  
_mouse_cam->set_projection(Camera3D::PROJECTION_ORTHOGONAL);  
_mouse_cam->set_size(0.1f);  
_mouse_cam->set_far(100000.f);

这段代码的作用是在 Godot 引擎中为鼠标拾取功能创建并配置一个专用的正交相机(Camera3D),并将其添加到一个子视口(SubViewport)中。具体效果如下:

  • 创建一个新的 Camera3D 对象,命名为 MouseCamera,并作为子节点添加到 _mouse_vp(鼠标用的 SubViewport)。
  • 创建并设置一个线性色调映射的 Environment,赋给相机,实现特定的渲染环境。
  • 创建并设置一个 Compositor,用于后处理或自定义渲染流程。
  • 将相机投影模式设置为正交(PROJECTION_ORTHOGONAL),适合 2D/像素精确的拾取。
  • 设置相机的正交尺寸为 0.1f,远裁剪面为 100000.f,保证拾取时能覆盖足够大的空间范围。

Note

env->set_tonemapper(Environment::TONE_MAPPER_LINEAR);

的作用是设置 Environment(环境)对象的色调映射器(Tonemapper)为线性(LINEAR)模式。色调映射器用于控制场景渲染时的颜色和亮度映射方式,TONE_MAPPER_LINEAR 表示使用线性色调映射,不做额外的色彩压缩或增强,适合需要精确颜色还原的场景。

Note

Compositor 在 Godot 4.x 中用于自定义相机的后处理(Post-processing)渲染流程,比如自定义渲染通道、特效、合成等。通过 Camera3D::set_compositor() 可以为该相机指定一个自定义的渲染合成器。

这里为 _mouse_cam 设置了一个新的 Compositor 实例。如果不设置 comp,该相机将使用默认的渲染流程,不会有自定义的后处理或特殊的渲染合成效果。通常情况下,如果你不需要特殊的渲染处理,可以不设置 Compositor,但如果后续有自定义渲染需求,这一步是必须的。

Note

  • 正交投影:摄像机拍摄的物体不会因距离远近而产生透视缩放,远近物体大小一致,常用于 2D 或需要精确测量的 3D 场景(如编辑器、地图等)。
  • 实际用途:在本代码中,正交投影用于鼠标拾取相关的摄像机,方便精确地获取地形上的点,无视深度带来的视觉变形。

除了 PROJECTION_ORTHOGONALCamera3D 还支持以下投影模式:

  • PROJECTION_PERSPECTIVE(透视投影):常规 3D 游戏摄像机,远处物体看起来更小,符合人眼视觉。
  • PROJECTION_FRUSTUM(截头体投影):允许自定义视锥体的左右上下边界,适合特殊投影需求。

Note

set_size(0.1f) 设置了 _mouse_cam(一个正交相机)的视锥体正交尺寸为 0.1,表示正交投影下相机可见区域的宽度。

这里将 _mouse_cam 的正交尺寸设置为 0.1f,是因为这个相机只用于鼠标拾取(即在 _setup_mouse_picking 里创建的 2x2 像素的 SubViewport),并不用于实际场景渲染。 设置很小的宽度可以让鼠标拾取时的投影区域非常精确,避免拾取到相机视野外的地形。实际渲染和观察用的主相机不会受这个值影响。

set_far(100000.f) 设置了相机的远裁剪面距离为 100000,表示相机能看到的最远距离。 100000.f 与后面 gpu_depth.glsl 中的 100000 是相关的。 在 GPU 深度采样和解码时,通常需要用到相机的 far 值来将深度值还原为世界坐标。 如果 GLSL 里也写死了 100000,说明 C++ 和 GLSL 这两处的 far 值需要保持一致,否则会导致深度解码不准确,进而影响地形交点计算等功能。

为什么这里的 vp,camera 不用 RenderingServer 创建?

在这段代码中,_mouse_vp(SubViewport)和相关的相机对象不使用 RenderingServer 直接创建,主要有以下几个原因:

1. 抽象层级不同

  • SubViewport 是 Godot 的高级节点对象,封装了完整的渲染管道功能
  • RenderingServer 是底层的渲染 API,用于直接操作渲染资源(如网格、材质、实例等)

2. 功能需求

从代码可以看出,这个 SubViewport 用于鼠标拾取功能:

void Terrain3D::_setup_mouse_picking() {
    // ...
    _mouse_vp = memnew(SubViewport);
    _mouse_vp->set_name("MouseViewport");
    add_child(_mouse_vp, true);  // 需要添加到场景树
    _mouse_vp->set_size(Vector2i(2, 2));
    _mouse_vp->set_update_mode(SubViewport::UPDATE_ONCE);
    // ...
}

3. 为什么需要高级对象

  • 场景树集成:SubViewport 需要作为节点添加到场景树中
  • 自动管理:自动处理相机、渲染管道、纹理生成等复杂操作
  • 便捷接口:提供 get_texture()get_image() 等方便的接口
  • 事件处理:可以处理输入事件和更新逻辑

4. 对比其他对象

在同一个类中,地形的网格确实使用 RenderingServer:

// 在 Terrain3DMesher 中可能会看到:
RenderingServer::get_singleton()->mesh_create()
RenderingServer::get_singleton()->instance_create()

这是因为地形网格需要:

  • 高性能的批量渲染
  • 直接控制渲染属性
  • 避免节点开销

总结

SubViewport 用于创建一个完整的渲染上下文来捕获鼠标拾取所需的深度信息,这需要完整的场景管理功能,而不仅仅是底层的渲染资源。使用高级 API 更符合这个用例的需求。

整体目的是为后续通过 GPU 读取深度或颜色信息实现鼠标点地拾取提供一个独立、可控的渲染环境和相机。


    _mouse_quad = memnew(MeshInstance3D);  
    _mouse_quad->set_name("MouseQuad");  
    _mouse_cam->add_child(_mouse_quad, true);  
    Ref<QuadMesh> quad;  
    quad.instantiate();  
    quad->set_size(Vector2(0.1f, 0.1f));  
    _mouse_quad->set_mesh(quad);  
    String shader_code = String(  
#include "shaders/gpu_depth.glsl"  
    );  
    Ref<Shader> shader;  
    shader.instantiate();  
    shader->set_code(shader_code);  
    Ref<ShaderMaterial> shader_material;  
    shader_material.instantiate();  
    shader_material->set_shader(shader);  
    _mouse_quad->set_surface_override_material(0, shader_material);  
    _mouse_quad->set_position(Vector3(0.f, 0.f, -0.5f));

这段代码的作用是在 Godot 场景中为鼠标拾取功能创建一个屏幕空间的正交相机和一个小的四边形(MeshInstance3D),并为其分配一个自定义的着色器材质。具体流程如下:

  1. 创建一个 MeshInstance3D 作为 _mouse_quad,并将其命名为 MouseQuad,挂载到 _mouse_cam(鼠标专用相机)下。
  2. 创建一个 QuadMesh,设置其大小为 0.1x0.1,并赋给 _mouse_quad 作为网格。
  3. 读取 shaders/gpu_depth.glsl 文件内容作为着色器代码,创建 ShaderShaderMaterial,并将着色器分配给材质。
  4. 将材质设置为 _mouse_quad 的表面材质。
  5. 设置 _mouse_quad 的位置为 (0, 0, -0.5)。

效果: 这块代码的目的是在一个极小的正交视口中渲染一个带有自定义深度着色器的四边形,用于 GPU 方式的地形拾取(如获取鼠标指向地形的深度信息),通常配合后续的像素读取实现高效的地形点击检测。

Note

QuadMesh 是 Godot 引擎中的一个网格类,用于生成一个二维矩形(四边形)网格,常用于显示平面、屏幕贴图等。

quad->set_size(Vector2(0.1f, 0.1f)); 设置了这个四边形的宽和高为 0.1 单位。

在 Godot 中,正交相机的 size 通常表示视口的宽度(或高度,取决于方向),所以这里的 QuadMesh 大小和相机正交宽度一致,确保四边形正好填满相机视野。这样做通常用于屏幕空间采样或特殊渲染用途。

Note

设置 shader_material 的作用是为 _mouse_quad 这个 MeshInstance3D 对象指定一个自定义的着色器材质(ShaderMaterial),这样它在渲染时会使用你自定义的 shader 代码(这里是 shaders/gpu_depth.glsl)来处理像素和顶点。

如果不设置 shader_material_mouse_quad 会使用默认材质或没有材质,无法实现你在 shader 里定义的特殊渲染效果(比如深度采样、特殊颜色、GPU 计算等),相关功能(如鼠标拾取、深度检测)就无法正常工作或效果不正确。

Note

_mouse_quad->set_position(Vector3(0.f, 0.f, -0.5f)); 这行代码的作用是将 MouseQuad 网格实例放置在鼠标相机的本地坐标系下的 (z = -0.5) 处。这样做的原因通常有:

  • MouseQuad 作为一个屏幕空间采样用的平面,需要位于相机前方一定距离,避免与相机原点重叠,确保渲染和采样深度时不会出错。
  • 选择 (-0.5) 作为 z 坐标,通常是因为正交相机的近平面在 0,远平面在正数方向,放在 (-0.5) 可以保证平面在相机前方且不会被裁剪掉。(见 Godot 坐标系
  • x、y 设为 0,表示平面正好在相机视锥中心。

简言之,这样设置是为了让采样用的平面正好位于鼠标相机前方,便于后续 GPU 深度采样和射线检测。

Note

正交相机(Orthographic Camera)的近平面远平面是指:

  • 近平面(Near Plane):相机能“看到”的最近的距离。小于这个距离的物体不会被渲染。
  • 远平面(Far Plane):相机能“看到”的最远的距离。超过这个距离的物体也不会被渲染。

在正交投影下,这两个参数定义了相机的可见空间(视锥体的深度范围),但正交相机的视锥体是一个长方体(盒子),而不是透视相机的金字塔形。

在 Godot 或其他 3D 引擎中,通常可以通过设置相机的 near 和 far 属性来调整这两个平面。例如:

  • set_near(0.1f); // 近平面距离
  • set_far(1000.f); // 远平面距离

这样,只有在这两个距离之间的物体才会被渲染。

为什么这里的相机要在前面加一个平面?

_mouse_cam 是一个 Camera3D,用于专门渲染鼠标拾取相关的视图。 _mouse_quad 是一个 MeshInstance3D,通常是一个小的 Quad(平面),被作为 _mouse_cam 的子节点。

加上 _mouse_quad 的主要原因是:

  1. GPU 深度拾取:通过在摄像机前放置一个 Quad,并用特殊的 Shader 渲染,可以将鼠标指向位置的深度信息编码到颜色中,便于后续读取和反解出三维坐标,实现高效的地形拾取。
  2. 与摄像机同步:作为摄像机的子节点,Quad 始终和摄像机对齐,保证拾取区域和视角一致。
  3. 只渲染需要的区域:Quad 很小,只覆盖鼠标点,提升性能,避免全屏渲染。

总结:_mouse_quad_mouse_cam 的子节点,主要用于 GPU 拾取和深度检测,辅助获取鼠标在地形上的精确三维位置。


1.3.6 Terrain3D::set_mouse_layer

set_mouse_layer 用于设置地形与鼠标拾取相关的渲染层。它的参数 p_layer 是一个无符号 32 位整数,表示要设置的鼠标层编号(通常在 21~32 之间)。函数内部会对输入层号进行限制,然后更新 _mouse_layer,并计算对应的掩码。接着,它会调用 set_render_layers 方法,将地形网格设置到新的渲染层,并确保地形材质、鼠标相机、鼠标拾取用的四边形等对象也应用到该层。这样可以实现鼠标拾取时只影响指定的渲染层,常用于编辑器或特殊交互场景。

Note

这行代码的作用是设置地形渲染层(render layers),并确保鼠标相关的渲染层被激活:

set_render_layers((_render_layers & 0xFFFFF) | mouse_mask);

含义如下:

  • _render_layers & 0xFFFFF:保留原有的前 20 位渲染层(用户层),去掉高位(21-32 层)。
  • mouse_mask:表示当前鼠标层的掩码(如第 N 层则为 1 << (N-1))。
  • | mouse_mask:将鼠标层激活(设置为 1)。
  • set_render_layers(...):应用新的渲染层设置。

这样做可以让地形对象既保留原有的用户渲染层,又确保鼠标相关的渲染层被正确设置。

Note

这一行代码 _mouse_quad->set_layer_mask(mouse_mask); 的作用是设置 _mouse_quad(一个 MeshInstance3D 对象)所属的渲染层遮罩(layer mask)。 mouse_mask 是一个位掩码,用于指定该对象应该被分配到哪个渲染层。这样可以控制该对象在不同的摄像机或渲染通道中是否可见,实现分层渲染和选择性显示。

1.3.7 Terrain3D::_notification

Terrain3D::_notificationTerrain3D 类的一个保护成员函数,用于处理 Godot 引擎的通知事件。它的参数 p_what 表示不同的通知类型(如节点初始化、进入场景树、退出场景树等)。在函数内部通常会用 switch 语句,根据 p_what 的值执行相应的初始化、清理或状态更新操作。该函数是 Godot 节点生命周期管理的关键部分,确保 Terrain3D 在不同阶段能正确响应引擎事件。

以下为几个重要的通知处理分支:

1.3.7.1 NOTIFICATION_ENTER_TREE

处理 Godot 节点进入场景树(SceneTree)时的通知。当节点被添加到场景树时,会触发此分支,执行以下操作:

  • 调用 set_as_top_level(true),让节点不继承父节点的变换,使用全局坐标。

Note

在 Godot 中,set_as_top_level(true) 的作用是让该节点不继承父节点的变换(如位置、旋转、缩放),而是直接使用全局坐标系。 这意味着:

  • 节点的位置、旋转、缩放不会受到父节点影响。
  • 节点的变换是相对于世界坐标(global transform),而不是父节点的本地坐标(local transform)。

这样可以确保节点始终在世界空间中独立定位,适用于需要全局控制的对象。

  • 启用变换通知(set_notify_transform(true))。

Note

在 Godot 中,set_notify_transform(true); 的作用是启用节点的变换(Transform)变化通知。 这意味着当节点的位置、旋转或缩放发生变化时,Godot 会自动调用 _notification 方法并发送 NOTIFICATION_TRANSFORM_CHANGED 通知。这样可以让你的代码在节点变换时执行特定逻辑,比如更新相关数据或触发其他操作。

  • 设置元数据 _edit_lock_ 为 true,通常用于编辑器锁定。
  • 调用 _setup_mouse_picking(),初始化鼠标拾取相关的子节点和视图。

Note

这行代码调用了 Terrain3D::_setup_mouse_picking() 方法,其作用是在节点进入场景树(SceneTree)时初始化鼠标拾取相关的子系统,包括创建用于鼠标拾取的视口、摄像机和屏幕四边形等对象,并设置它们的参数。这样可以支持地形的鼠标交互和拾取功能。

  • 如果启用了 free_editor_textures,且不是在编辑器环境,并且资产路径有效且不包含 Terrain3DAssets,则重新加载资产资源。
  • 调用 _initialize() ,在节点进入场景树(NOTIFICATION_ENTER_TREE)时,重新初始化 Terrain3D 的主要子系统,包括网格、碰撞、实例化器等。如果之前有资源被释放,这里会重新创建和连接相关对象,确保地形系统处于可用状态。

这些操作确保地形节点在进入场景树时能正确初始化其渲染、拾取和资源管理功能。

1.3.7.2 NOTIFICATION_PHYSICS_PROCESS

case NOTIFICATION_PHYSICS_PROCESS: 这个分支用于处理物理帧更新通知。当节点每经过一个物理帧时,Godot 会发送该通知。

  • 调用了 __physics_process(get_process_delta_time());,用于执行地形相关的物理处理逻辑,比如相机抓取、网格快照和碰撞更新。这样可以确保地形在每个物理帧都能正确响应和更新。

1.3.8 Terrain3D::_bind_methods

Terrain3D::_bind_methods 的作用是将 C++ 类 Terrain3D 的方法、属性和信号绑定到 Godot 引擎,使其能在 Godot 编辑器和脚本中被访问和调用。具体包括:

  • 绑定枚举常量(如 ERROR, INFO 等)。
  • 绑定成员方法,使其可被 Godot 脚本调用。
  • 注册属性,支持在编辑器中显示和编辑。
  • 注册信号,支持 Godot 的信号机制。

这样做可以让 Terrain3D 作为 Godot 的自定义节点,拥有完整的编辑器和脚本交互能力。

1.3.9 Terrain3D::_generate_triangles

_generate_trianglesTerrain3D 类的私有成员函数,用于根据地形数据生成三角形顶点和可选的 UV 坐标。其主要功能如下:

  • 参数说明:

    • p_vertices:用于存储生成的三角形顶点。
    • p_uvs:可选参数,存储每个顶点的 UV 坐标。
    • p_lod:细节层级(Level of Detail),决定采样步长。
    • p_filter:高度采样方式(如最近邻或最小值)。
    • p_require_nav:是否只生成可导航区域的三角形。
    • p_global_aabb:限定生成三角形的空间范围。
  • 主要流程:

    1. 检查地形数据对象 _data 是否有效。
    2. 根据 p_lod 计算采样步长。(LOD 越高步长越大,细节越少)
    3. 如果 p_global_aabb 没有体积,则遍历整个地形区域,按步长采样顶点和高度,生成三角形并填充到 p_verticesp_uvs
    4. 如果有体积限制,则只在指定的 AABB 区域内生成三角形。
    5. 支持不同高度采样方式和导航区域过滤。
  • 作用: 该函数是地形网格生成的核心,供物理、渲染、导航等系统调用,生成地形的三角形数据。


int32_t z_start = (int32_t)Math::ceil(p_global_aabb.position.z / _vertex_spacing);  
int32_t z_end = (int32_t)Math::floor(p_global_aabb.get_end().z / _vertex_spacing) + 1;  
int32_t x_start = (int32_t)Math::ceil(p_global_aabb.position.x / _vertex_spacing);  
int32_t x_end = (int32_t)Math::floor(p_global_aabb.get_end().x / _vertex_spacing) + 1;  
  
for (int32_t z = z_start; z < z_end; ++z) {  
    for (int32_t x = x_start; x < x_end; ++x) {  
       real_t height = _data->get_height(Vector3(x, 0.f, z));  
       if (height >= p_global_aabb.position.y && height <= p_global_aabb.get_end().y) {  
          _generate_triangle_pair(p_vertices, p_uvs, p_lod, p_filter, p_require_nav, x, z);  
       }  
    }  
}

_vertex_spacing 表示地形网格顶点之间的间距(单位通常为世界坐标单位),用于控制地形的精度和大小。

它的设置位置在 set_vertex_spacing 方法中(第 613 行开始),可以通过该方法修改 _vertex_spacing 的值。此方法会在属性变更时同步相关子系统,并更新地形标签、实例、网格和碰撞等。

此外,_vertex_spacing 也可以通过 Godot 编辑器属性 "vertex_spacing" 进行设置,绑定在 _bind_methods 方法的第 1199 行:

ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vertex_spacing", PROPERTY_HINT_RANGE, "0.25,10.0,0.05,or_greater"), "set_vertex_spacing", "get_vertex_spacing");

前面的分支 xz 循环步长是 step,而这里是 1?

前面的分支(全地形烘焙)xz 循环步长是 step,因为它是按 LOD(细节级别)步长跳跃采样,step 可能大于 1,可以跳过一些点以加速处理。这里是 AABB 区域烘焙,循环步长 1 是逐点遍历,因为这里需要更精细地检查每个点是否在 AABB 内并且高度符合要求,不能跳步(这种情况可能会生成冗余的三角形)。两者遍历粒度不同,目的是分别优化性能和精度。

1.3.10 Terrain3D::_generate_triangle_pair

该函数 Terrain3D::_generate_triangle_pair 用于在地形网格的指定位置(x, z)生成一对三角形顶点(通常用于渲染地形或导航网格)。它的主要作用是:

  • 根据给定的 LOD(细节层级)和高度过滤方式,从地形数据中采样顶点高度。
  • 计算四个顶点的位置(以 step 为间隔),并将它们转换为世界坐标。
  • 生成两组三角形顶点(上三角和下三角),并将结果添加到 p_vertices 数组中。
  • 如果 p_uvs 不为空,还会计算并添加对应的 UV 坐标。
  • p_require_nav 参数用于决定是否只生成可导航区域的三角形。

该函数是地形网格生成的核心步骤之一,负责将高度图数据转换为可渲染或可用于物理/导航的三角形面片。

1.3.11 Terrain3D::update_region_labels

1.3.12 Terrain3D::get_intersection

用于计算一条射线与地形的交点。它接收三个参数:

  • p_src_pos:射线的起点(通常是摄像机位置)。
  • p_direction:射线的方向(通常是摄像机朝向)。
  • p_gpu_mode:布尔值,决定是否使用 GPU 深度纹理进行交点计算(true 为 GPU 模式,false 为光线步进模式)。

该函数会返回一个 Vector3 类型的交点坐标。如果发生错误则返回 Vector3(NAN, NAN, NAN),如果没有交点则返回一个极大值的向量(如 vec3(3.402823466e+38F))。此方法常用于地形编辑器或游戏中判断鼠标点击位置对应的地形点。

Vector3 direction = p_direction.normalized();  
Vector3 point;  
  
// Position mouse cam one unit behind the requested position  
_mouse_cam->set_global_position(p_src_pos - direction);

这段代码的作用是将鼠标摄像机(_mouse_cam)的位置设置到请求位置 p_src_pos 的后方一单位处,方向由 p_direction 指定。 具体流程如下:

  1. p_direction.normalized() 得到归一化的方向向量。
  2. 计算 p_src_pos - direction,即在 p_src_pos 位置沿着 direction 反方向移动一单位。
  3. 用这个新位置设置 _mouse_cam 的全局位置。

这样做通常是为了在射线检测或地形交互时,确保摄像机不会与目标点重合,避免数值误差或遮挡问题。

// If looking straight down (eg orthogonal camera), just return height. look_at won't work  
if ((direction - Vector3(0.f, -1.f, 0.f)).length_squared() < 0.00001f) {  
    _mouse_cam->set_rotation_degrees(Vector3(-90.f, 0.f, 0.f));  
    point = p_src_pos;  
    point.y = _data->get_height(p_src_pos);  
    if (std::isnan(point.y)) {  
       point.y = 0;  
    }  
  
}

该分支是判断射线方向是否接近垂直向下(即与 (0, -1, 0) 的距离非常小),用于特殊处理正下方的射线。 如果是正下方,直接返回该点的地形高度,否则进入后续的射线检测或 GPU 模式。这样可以避免 look_at 旋转在垂直方向时出错,提高效率和稳定性。

为什么没有处理正上方的 case?

因为在地形射线检测中,正下方 (0, -1, 0) 通常用于从摄像机垂直向下投射到地面,直接返回高度即可。而正上方 (0, 1, 0) 则不会与地形发生交点,属于“离开地面”方向,没有实际意义,所以没有专门处理正上方的情况。


else if (!p_gpu_mode) {  
    // Else if not gpu mode, use raymarching mode  
    point = p_src_pos;  
    for (int i = 0; i < 4000; i++) {  
       real_t height = _data->get_height(point);  
       if (point.y - height <= 0) {  
          return point;  
       }  
       point += direction;  
    }  
    return V3_MAX;  
  
}

这个分支判断 p_gpu_mode 是否为 false,如果是,则采用“光线步进(raymarching)”方式检测地形高度。它会从起点 p_src_pos 沿着方向 p_direction 逐步前进,每次检查当前位置的高度,如果当前位置低于地形表面,则返回该点,表示射线与地形相交。如果循环结束还没相交,则返回一个极大值 V3_MAX,表示没有交点。

简单来说,这段代码是用 CPU 方式检测射线与地形的交点。


else {  
    // Else use GPU mode, which requires multiple calls  
    // Get depth from perspective camera snapshot    _mouse_cam->look_at(_mouse_cam->get_global_position() + direction, Vector3(0.f, 1.f, 0.f));  
    _mouse_vp->set_update_mode(SubViewport::UPDATE_ONCE);  
    Ref<ViewportTexture> vp_tex = _mouse_vp->get_texture();  
    Ref<Image> vp_img = vp_tex->get_image();  
  
    // Read the depth pixel from the camera viewport  
    Color screen_depth = vp_img->get_pixel(0, 0);  
  
    // Get position from depth packed in RGB - unpack back to float.  
    // Forward+ is 16bit, mobile and compatibility is 10bit.    // Compatibility also has precision loss for values below 0.5, so    // we use only the top half of the range, for 21bit depth encoded.    real_t r = floor((screen_depth.r * 256.0) - 128.0);  
    real_t g = floor((screen_depth.g * 256.0) - 128.0);  
    real_t b = floor((screen_depth.b * 256.0) - 128.0);  
  
    // Decode the full depth value  
    real_t decoded_depth = (r + g / 127.0 + b / (127.0 * 127.0)) / 127.0;  
  
    if (decoded_depth < 0.00001f) {  
       return V3_MAX;  
    }  
    // Necessary for a correct value depth = 1  
    if (decoded_depth > 0.99999f) {  
       decoded_depth = 1.0f;  
    }  
  
    // Denormalize distance to get real depth and terrain position.  
    decoded_depth *= _mouse_cam->get_far();  
  
    // Project the camera position by the depth value to get the intersection point.  
    point = _mouse_cam->get_global_position() + direction * decoded_depth;  
}
 

p_gpu_modetrue 时,会通过 _mouse_cam_mouse_vp 获取视口的深度贴图,然后解码出深度值,最后用相机位置和方向计算出地形的交点坐标。这个分支主要用于通过 GPU 渲染结果快速获取地形与射线的交点,适合高效的地形拾取和交互。

_mouse_cam->look_at(_mouse_cam->get_global_position() + direction, Vector3(0.f, 1.f, 0.f));  
_mouse_vp->set_update_mode(SubViewport::UPDATE_ONCE);  
Ref<ViewportTexture> vp_tex = _mouse_vp->get_texture();  
Ref<Image> vp_img = vp_tex->get_image();  
  
// Read the depth pixel from the camera viewport  
Color screen_depth = vp_img->get_pixel(0, 0);
  1. _mouse_cam 相机朝向指定方向(direction),用于捕捉地形的深度信息。
  2. 设置 _mouse_vp 视口为只更新一次(UPDATE_ONCE),以便获取最新的渲染结果。
  3. 获取视口的纹理(ViewportTexture),再从纹理中提取图像(Image)。

_mouse_vp->get_texture()_mouse_quad->set_surface_override_material(0, shader_material); 有什么联系?

有直接联系。 _mouse_quad->set_surface_override_material(0, shader_material); 是将自定义的着色器材质应用到屏幕四边形(MouseQuad)上。 而 vp_tex = _mouse_vp->get_texture(); 获取的是 SubViewport 渲染后的纹理,这个纹理内容就是由 MouseQuad 使用你设置的着色器渲染出来的。 所以你设置的 shader_material 决定了 get_texture() 得到的像素数据内容,两者是渲染链路的上下游关系。

_mouse_quad 不是放在相机前 0.5 距离单位吗,为什么渲染出来的点是相机射线与地形的交点的深度?

这是因为 _mouse_quad 使用的是自定义着色器(gpu_depth.glsl),它会采样地形的深度信息,并把交点的深度编码到渲染结果的颜色(RGB)里。

虽然 _mouse_quad 只是一个很小的四边形,放在相机前方,但它的着色器会用屏幕空间射线(由相机位置和方向决定)去“投射”到地形表面,计算出射线与地形的交点,然后把这个交点的深度值写入像素。 所以你读取 _mouse_vp->get_texture() 得到的其实是着色器计算出来的地形交点深度,而不是四边形本身的深度。这样就能实现 GPU 加速的拾取和交互。

这里取一张视图图片是否会有性能风险?

这里取得图片大小是 2x2(由前面设置 _mouse_vp size 时设置),对性能影响很小

  1. 读取图像中 (0, 0) 像素的颜色值(screen_depth),该像素包含了地形的深度信息。

这些步骤是为了通过 GPU 渲染结果获取地形的深度数据,实现地形的鼠标拾取或射线检测。

总的来说,GPU 方案的原理如下:

  1. 通过 GPU 渲染,将场景投影到一个小的 SubViewport(如 2x2 像素),并用正交或透视相机渲染地形。
  2. 渲染时,地形的 shader 会输出深度信息到深度图(Depth Texture)。
  3. 通过读取 ViewportTexture 的像素(如 get_pixel(0, 0)),获得该像素的深度值。
  4. 利用相机的投影参数和深度值,可以反推出相机到地面的实际距离,从而得到地面交点的世界坐标。

这种方法利用 GPU 高效地计算视线与地形的交点,避免 CPU 端复杂的射线检测或迭代。

1.3.13 Terrain3D::get_raycast_result

get_raycast_result 函数用于在三维空间中进行物理射线检测(raycast),返回射线与场景物体的碰撞信息。它接受起点 p_src_pos、终点 p_destination 和一个布尔值 p_exclude_self(是否排除自身碰撞体)。如果排除自身,则会把地形的碰撞体加入排除列表。最终通过 Godot 的物理空间查询,返回一个包含碰撞结果的 Dictionary(字典),如碰撞点、碰撞物体等信息。

1.3.14 Terrain3D::bake_mesh

bake_mesh 函数的作用是根据指定的 LOD(细节层级)和高度过滤方式,从地形数据生成一个静态的三角网格(ArrayMesh),用于渲染地形。它会遍历地形区域,采样高度和 UV,并通过 SurfaceTool 构建三角形,最终返回一个可用于渲染的网格对象。该函数常用于地形的可视化和导航网格生成。

Note

SurfaceTool 是 Godot 引擎中的一个工具类,用于在运行时程序化地创建和编辑网格(Mesh)。它允许你逐步添加顶点、法线、UV、颜色等信息,然后生成一个可用于渲染的网格对象。常用于自定义几何体、地形、模型等的生成。

在你的代码中,SurfaceTool 被用来收集顶点和 UV 数据,最后通过 st->commit() 生成 Mesh。

1.3.15 Terrain3D::generate_nav_mesh_source_geometry

generate_nav_mesh_source_geometry 函数用于从地形生成可用于导航网格烘焙的三角面数据。它会根据传入的 AABB 区域和 p_require_nav 标志,筛选出没有洞且被标记为可导航的地形区域,生成三角形顶点数组,供导航网格系统使用。

2 Terrain3DData

Terrain3DData 类是 Terrain3D 系统的核心数据管理类。它继承自 Godot 的 Object 类。

2.1 主要职责

  • 地形数据统一存储管理

    • 管理三种核心地图数据:高度图 (_height_maps)、控制图 (_control_maps)、颜色图 (_color_maps)
    • 维护数据格式版本控制 (CURRENT_VERSION = 0.93f),支持向后兼容和数据升级
    • 确保所有地形数据的完整性和一致性,防止数据损坏
  • 大规模地形区域化管理

    • 实现 32×32 区域网格系统 (REGION_MAP_SIZE = 32),将无限大地形世界划分为有限区域
    • 维护双重索引体系:稳定的位置索引 (region_location) 和高效的 ID 索引 (region_id)
    • 支持区域的动态创建、加载、卸载和删除操作
    • 跟踪区域生命周期状态:修改状态、删除状态、活跃状态
  • CPU-GPU 渲染数据同步

    • 将 CPU 端的 Image 数据转换为 GPU 端的 TextureArray 纹理数组
    • 通过 GeneratedTexture 类管理渲染服务器的 RID 资源
    • 支持增量更新和全量更新两种同步模式
    • 自动处理纹理的 mipmap 生成
  • 全局坐标系统一访问

    • 提供基于全局坐标的统一数据访问接口,屏蔽底层区域分割复杂性
    • 自动完成全局坐标到区域坐标的转换计算
    • 通过内联函数优化频繁访问操作的性能
    • 支持越界访问的安全处理和边界检查
  • 地形数据持久化

    • 支持目录级的整体地形项目保存和加载
    • 支持单个区域的独立保存和加载操作
    • 提供多种图像格式的导出功能
    • 处理 16 位和 32 位精度的数据存储

2.2 核心成员

  • 基础配置成员

    • Terrain3D *_terrain:指向父级地形对象的指针,提供回调和配置访问
    • int _region_size:单个区域的像素尺寸,影响内存占用和细节级别
    • Vector2i _region_sizev:区域尺寸的 Vector2i 表示,用于向量计算优化
    • real_t _vertex_spacing:地形顶点间的实际世界距离,决定地形缩放
  • 编辑状态跟踪成员

    • AABB _edited_area:当前编辑会话中被修改区域的 3D 包围盒
    • Vector2 _master_height_range:整个地形的最小和最大高度值范围
    • 用于优化渲染剔除和碰撞检测
  • 区域管理核心数据结构

    • Dictionary _regions:主要区域存储容器,以 region_location 为键,存储 Terrain3DRegion 对象
    • TypedArray<Vector2i> _region_locations:活跃区域位置数组,定义 region_id 的分配顺序
    • PackedInt32Array _region_map:32×32 快速索引网格,存储 region_id 值用于 O(1) 查找
    • bool _region_map_dirty:区域映射表的脏标记,用于延迟重建优化
  • 地形数据存储数组

    • TypedArray<Image> _height_maps:高度图数组,每个区域对应一张单通道浮点图像
    • TypedArray<Image> _control_maps:控制图数组,存储纹理 ID、混合权重、UV 变换等信息
    • TypedArray<Image> _color_maps:颜色图数组,存储漫反射颜色和表面粗糙度
    • 所有数组按 region_id 顺序排列,确保索引一致性
  • GPU 渲染资源

    • GeneratedTexture _generated_height_maps:GPU 端高度纹理数组,供着色器采样使用
    • GeneratedTexture _generated_control_maps:GPU 端控制纹理数组,包含材质混合信息
    • GeneratedTexture _generated_color_maps:GPU 端颜色纹理数组,提供表面颜色和粗糙度
    • 自动管理纹理的创建、更新和销毁

Note

查看 GeneratedTexture 的实现可知,这些 _generated_xxx_maps 管理的纹理数组实际是和所有激活区域的对应 _xxx_maps 一一对应的, 因此后面调用 GeneratedTexture::update 更新纹理时,用的 p_layer 参数就是区域的 region_id

Image,Texture 的区别是是什么,这里为什么要通过 RenderingServer 将 Image 转成 Texture?

Image 通常表示原始的像素数据(如内存中的位图),可以直接读写、修改像素内容,适合做图像处理、生成、加载等操作。

Texture 则是渲染引擎用于绘制的纹理资源,通常是 GPU 端的对象,不能直接修改像素,适合高效渲染。

在 Godot(或类似引擎)中,RenderingServer 负责与底层渲染 API 交互。将 Image 转成 Texture,是因为只有 Texture 能被 GPU 用于渲染。Image 只是数据,Texture 是渲染资源。

所以这里通过 RenderingServerImage 转成 Texture,是为了让图像数据能被渲染到屏幕上。

control map 里的 pixel 数据是用 32 位无符号整数(uint32_t)编码的,每一部分的位含义如下:

  • 最高 5 位(27~31):base,基础材质索引
  • 22~26 位:overlay,叠加材质索引
  • 14~21 位:blend,混合权重
  • 10~13 位:uv_rotation,UV 旋转
  • 7~9 位:uv_scale,UV 缩放
  • 第 2 位(bit 2):hole,洞(是否有洞)
  • 第 1 位(bit 1):nav,导航(是否可导航)
  • 第 0 位(bit 0):auto,自动(自动标记)

每个字段都可以通过相应的 get_xxx/enc_xxx 函数进行读取和写入。

参考: https://terrain3d.readthedocs.io/en/stable/docs/controlmap_format.html

2.3 核心接口

  • 区域管理接口

    • 坐标转换与查询
      • Vector2i get_region_location(const Vector3 &p_global_position):全局坐标到区域位置转换
      • int get_region_id(const Vector2i &p_region_loc):区域位置到区域 ID 转换
      • int get_region_idp(const Vector3 &p_global_position):全局坐标直接获取区域 ID
      • static int get_region_map_index(const Vector2i &p_region_loc):计算区域映射表索引
    • 区域存在性检查
      • bool has_region(const Vector2i &p_region_loc):检查指定位置是否有区域
      • bool has_regionp(const Vector3 &p_global_position):检查全局坐标位置是否有区域
    • 区域对象获取
      • Ref<Terrain3DRegion> get_region(const Vector2i &p_region_loc):安全的引用计数获取方式
      • Terrain3DRegion *get_region_ptr(const Vector2i &p_region_loc):高性能原始指针获取方式
      • Ref<Terrain3DRegion> get_regionp(const Vector3 &p_global_position):基于全局坐标的区域获取
      • 模板删除函数防止类型错误传参
    • 区域生命周期管理
      • Ref<Terrain3DRegion> add_region_blank(const Vector2i &p_region_loc, bool p_update = true):创建空白区域
      • Error add_region(const Ref<Terrain3DRegion> &p_region, bool p_update = true):添加已存在的区域
      • void remove_region(const Ref<Terrain3DRegion> &p_region, bool p_update = true):删除指定区域
      • void remove_regionl/p(参数, bool p_update = true):按位置或坐标删除区域
    • 区域状态管理
      • void set_region_modified(const Vector2i &p_region_loc, bool p_modified = true):标记区域修改状态
      • bool is_region_modified(const Vector2i &p_region_loc):查询区域修改状态
      • void set_region_deleted(const Vector2i &p_region_loc, bool p_deleted = true):标记区域删除状态
      • bool is_region_deleted(const Vector2i &p_region_loc):查询区域删除状态
  • 地形数据访问接口

    • 通用像素操作
      • void set_pixel(MapType p_map_type, const Vector3 &p_global_position, const Color &p_pixel):统一像素写入接口
      • Color get_pixel(MapType p_map_type, const Vector3 &p_global_position):统一像素读取接口
      • 支持所有地图类型 (TYPE_HEIGHTTYPE_CONTROLTYPE_COLOR) 的统一访问
    • 高度数据操作
      • void set_height(const Vector3 &p_global_position, real_t p_height):设置地形高度值
      • real_t get_height(const Vector3 &p_global_position):获取地形高度值
      • Vector3 get_normal(const Vector3 &p_global_position):计算地形法线向量
      • Vector3 get_mesh_vertex(int32_t p_lod, HeightFilter p_filter, const Vector3 &p_global_position):获取 LOD 网格顶点
    • 颜色数据操作
      • void set_color(const Vector3 &p_global_position, const Color &p_color):设置地形颜色 (自动处理粗糙度)
      • Color get_color(const Vector3 &p_global_position):获取地形颜色 (Alpha 通道置为 1.0)
      • void set_roughness(const Vector3 &p_global_position, real_t p_roughness):单独设置表面粗糙度
      • real_t get_roughness(const Vector3 &p_global_position):获取表面粗糙度值
  • 控制数据接口

    • 纹理控制操作
      • void set_control_base_id(const Vector3 &p_global_position, uint8_t p_base):设置基础纹理 ID(0-31)
      • uint32_t get_control_base_id(const Vector3 &p_global_position):获取基础纹理 ID
      • void set_control_overlay_id(const Vector3 &p_global_position, uint8_t p_overlay):设置覆盖纹理 ID(0-31)
      • uint32_t get_control_overlay_id(const Vector3 &p_global_position):获取覆盖纹理 ID
      • void set_control_blend(const Vector3 &p_global_position, real_t p_blend):设置纹理混合权重 (0.0-1.0)
      • real_t get_control_blend(const Vector3 &p_global_position):获取纹理混合权重
    • UV 变换控制
      • void set_control_angle(const Vector3 &p_global_position, real_t p_angle):设置 UV 旋转角度 (度,22.5 度步进)
      • real_t get_control_angle(const Vector3 &p_global_position):获取 UV 旋转角度
      • void set_control_scale(const Vector3 &p_global_position, real_t p_scale):设置 UV 缩放百分比修饰符
      • real_t get_control_scale(const Vector3 &p_global_position):获取 UV 缩放值
    • 特殊标记控制
      • void set_control_hole(const Vector3 &p_global_position, bool p_hole):设置地形洞穴标记
      • bool get_control_hole(const Vector3 &p_global_position):获取地形洞穴状态
      • void set_control_navigation(const Vector3 &p_global_position, bool p_navigation):设置导航网格可通行标记
      • bool get_control_navigation(const Vector3 &p_global_position):获取导航网格状态
      • void set_control_auto(const Vector3 &p_global_position, bool p_auto):设置自动纹理标记
      • bool get_control_auto(const Vector3 &p_global_position):获取自动纹理状态
    • 原始控制数据
      • void set_control(const Vector3 &p_global_position, uint32_t p_control):直接设置 32 位控制数据
      • uint32_t get_control(const Vector3 &p_global_position):获取原始 32 位控制数据
  • 渲染与文件接口

    • 渲染资源管理
      • void update_maps(MapType p_map_type = TYPE_MAX, bool p_all_regions = true, bool p_generate_mipmaps = false):更新 GPU 纹理数据
      • RID get_height_maps_rid():获取高度纹理的渲染服务器 RID
      • RID get_control_maps_rid():获取控制纹理的渲染服务器 RID
      • RID get_color_maps_rid():获取颜色纹理的渲染服务器 RID
      • TypedArray<Image> get_height/control/color_maps():获取 CPU 端图像数据
    • 文件 I/O 操作
      • void save_directory(const String &p_dir):保存整个地形项目到目录
      • void load_directory(const String &p_dir):从目录加载地形项目
      • void save_region(const Vector2i &p_region_loc, const String &p_dir, bool p_16_bit = false):保存单个区域
      • void load_region(const Vector2i &p_region_loc, const String &p_dir, bool p_update = true):加载单个区域
      • Error export_image(const String &p_file_name, MapType p_map_type = TYPE_HEIGHT):导出为图像文件
      • Ref<Image> layered_to_image(MapType p_map_type):将分层纹理合并为单张图像
  • 高级查询与工具接口

    • 几何计算
      • bool is_in_slope(const Vector3 &p_global_position, const Vector2 &p_slope_range, bool p_invert = false):检查是否在指定坡度范围内
      • Vector3 get_texture_id(const Vector3 &p_global_position):获取位置的纹理 ID 信息
    • 高度范围管理
      • Vector2 get_height_range():获取全局高度范围
      • void update_master_height(real_t p_height):更新单个高度到全局范围
      • void update_master_heights(const Vector2 &p_low_high):批量更新高度范围
      • void calc_height_range(bool p_recursive = false):重新计算整个地形的高度范围
    • 编辑区域跟踪
      • void add_edited_area(const AABB &p_area):添加编辑区域到跟踪范围
      • void clear_edited_area():清空编辑区域记录
      • AABB get_edited_area():获取当前编辑区域包围盒
    • 批量操作与工具
      • void import_images(const TypedArray<Image> &p_images, const Vector3 &p_global_position, real_t p_offset, real_t p_scale):批量导入图像数据
      • void do_for_regions(const Rect2i &p_area, const Callable &p_callback):对指定区域执行回调函数
      • void print_audit_data():打印调试和审计信息

2.3.1 Terrain3DData::_copy_paste_dfr

该函数 _copy_paste_dfr 主要用于在两个 Terrain3DRegion 区域之间复制贴图数据。它会将源区域 p_src_region 的指定矩形 p_src_rect 的内容,复制到目标区域 p_dst_region 的指定矩形 p_dst_rect 位置。具体做法是遍历目标区域的所有贴图(如高度图、控制图、颜色图),并用 blit_rect 方法将源贴图的对应部分复制到目标贴图。最后还会调用 _terrain->get_instancer()->copy_paste_dfr,可能用于同步或进一步处理区域数据。这个函数通常用于区域数据的批量复制或粘贴操作。

2.3.2 Terrain3DData::load_directory

load_directory 函数用于从指定的目录(p_dir)加载所有符合 terrain3d*.res 模式的区域文件,并将它们导入到当前的地形数据结构中。如果目录为空或没有找到文件,会记录错误或提示信息。该函数主要实现地形区域数据的批量加载和初始化。

2.3.3 Terrain3DData::add_region

add_region 函数用于将一个 Terrain3DRegion 区域对象添加到地形数据中。它会检查传入的区域对象是否有效、位置是否在允许范围内,然后将其插入到内部区域映射结构。如果 p_update 参数为真,还会触发地图数据的重建。函数返回操作结果(成功或失败),并在过程中记录相关日志。

2.3.4 Terrain3DData::update_maps

update_maps 函数用于更新地形的各种地图(如高度图、控制图、颜色图)。它根据传入的参数决定是否对所有区域进行更新,以及是否生成 mipmap(多级纹理)。主要流程是遍历所有区域,根据类型刷新对应的地图数据,并在需要时生成 mipmap,最后通过信号通知地图已更改。

Note

MapType 通常用于区分不同类型的地形贴图数据。主要有以下几种:

  • TYPE_HEIGHT:高度图,用于表示地形的高低起伏。
  • TYPE_CONTROL:控制图,通常用于混合材质、决定地形某处使用哪种纹理(如草地、岩石等),也可包含洞、导航等信息。
  • TYPE_COLOR:颜色图,直接决定地形表面的颜色,可用于细节着色或顶点色。

每种类型对应不同的数据和用途,处理方式也不同。例如,高度图用于地形生成和物理计算,控制图用于材质混合和特殊区域标记,颜色图用于视觉效果。

2.3.5 Terrain3DData::get_texture_id

get_texture_id 函数的作用是: 根据传入的全局坐标 p_global_position,返回该位置对应的纹理混合信息。 返回值是一个 Vector3,其中:

  • X:基础纹理(base texture)的索引
  • Y:叠加纹理(overlay texture)的索引
  • Z:基础与叠加之间的混合百分比(blend),用于决定两种纹理的混合程度

这个信息通常用于地形材质混合,比如决定地表某点是草地、岩石还是两者的过渡区域。

3 Terrain3DRegion

Terrain3DRegion 类是一个继承自 Godot Resource 类的 3D 地形区域管理器,通过 GDCLASS 宏集成到 Godot 的对象系统中。该类负责管理单个地形区域的完整数据生命周期,包括三种核心贴图(高度图、控制图、颜色图)的存储与操作、高度范围的动态跟踪、实例化对象的放置管理,以及数据的持久化与版本控制。该类使用 MapType 枚举定义贴图类型,并提供相应的格式配置和默认值设置。

3.1 主要职责

  • 地形贴图类型系统管理

    • 定义 MapType 枚举:TYPE_HEIGHT(高度图)、TYPE_CONTROL(控制图)、TYPE_COLOR(颜色图)和 TYPE_MAX(边界标记)
    • 维护静态格式配置数组 FORMAT[]:高度图和控制图使用 Image::FORMAT_RF,颜色图使用 Image::FORMAT_RGBA8
    • 提供类型字符串映射 TYPESTR[] 和默认颜色配置 COLOR[],使用 COLOR_BLACKCOLOR_CONTROLCOLOR_ROUGHNESS 等常量
  • 地形数据存储与管理

    • 管理三个核心 Ref<Image> 贴图:_height_map_control_map_color_map
    • 维护 _region_size(int)区域尺寸和 _height_range(Vector2)高度范围
    • 提供贴图验证、清理和尺寸检查功能
    • 支持 TypedArray<Image> 批量操作和单个贴图的精细控制
  • 高度范围动态跟踪

    • 通过内联函数 update_height()update_heights() 实现实时范围更新
    • 自动标记 _modified 状态当高度数据发生变化
    • 提供 calc_height_range() 方法重新扫描整个高度图
  • 实例化对象系统

    • 管理 _instances(Dictionary)三层嵌套结构:Meshes{int} → Cells{Vector2i} → [Transform3D, Color, Modified]
    • 控制 _vertex_spacing(real_t)顶点间距,范围限制在 0.25f-100.0f 之间
    • 支持实例化对象的变换、颜色和修改状态跟踪
  • 版本控制与数据持久化

    • 维护 _version(real_t)数据版本,初始值 0.8f,确保向后兼容性
    • 提供文件保存功能,支持可选的 16 位格式导出
    • 实现数据字典的序列化与反序列化

3.2 核心成员

  • 版本与配置数据

    • _version(real_t):数据格式版本号,默认 0.8f
    • _region_size(int):区域像素尺寸,默认 0
    • _vertex_spacing(real_t):实例化对象的顶点间距,默认 1.0f
  • 地形贴图数据

    • _height_mapRef<Image>):地形高度信息,使用 FORMAT_RF 格式
    • _control_mapRef<Image>):材质混合控制信息,使用 FORMAT_RF 格式
    • _color_mapRef<Image>):地形颜色信息,使用 FORMAT_RGBA8 格式
    • _height_range(Vector2):高度最小值(x)和最大值(y),默认 V2_ZERO
  • 实例化系统数据

    • _instances(Dictionary):三层嵌套的实例化对象存储结构
  • 工作状态标志

    • _deleted(bool):删除标记,默认 false
    • _edited(bool):编辑状态标记,用于撤销/重做系统
    • _modified(bool):修改标记,指示需要保存
    • _location(Vector2i):区域世界坐标位置,默认 V2I_MAX
  • 静态配置数组

    • FORMAT[]:各贴图类型的 Image::Format 配置
    • TYPESTR[]:贴图类型的字符串表示
    • COLOR[]:各类型的默认 Color

Note

_instances 是一个 Dictionary(字典),用于存储实例化对象的数据结构。根据注释:

// Meshes{int} -> Cells{v2i} -> [ Transform3D, Color, Modified ]

它的结构是:

  • 外层 key 是网格(Mesh)的整数 ID
  • value 是另一个字典,key 是单元格的二维整型坐标(Vector2i),value 是一个数组,包含:
    • Transform3D(变换矩阵,表示实例的位置/旋转/缩放)
    • Color(颜色)
    • Modified(是否被修改的标记)

这个成员用于管理地形区域中所有实例化对象(如植被、石头等)的分布、状态和属性。

3.3 核心接口

  • 版本与尺寸管理

    • set_version() / get_version():版本号管理,getter 为内联函数
    • set_region_size() / get_region_size():区域尺寸管理,getter 为内联函数
  • 贴图操作接口

    • set_map(MapType, Ref<Image>) / get_map(MapType):通用贴图操作
    • get_map_ptr(MapType):获取 Image* 原始指针用于直接访问
    • set_maps(TypedArray<Image>) / get_maps():批量贴图数组操作
    • 专用贴图接口:set_height_map()set_control_map()set_color_map() 及对应的 getter(内联函数)
  • 贴图验证与清理

    • sanitize_maps():批量清理所有贴图
    • sanitize_map(MapType, Ref<Image>):清理指定类型贴图并返回有效的 Ref<Image>
    • validate_map_size(Ref<Image>):验证贴图尺寸是否符合区域要求
  • 高度管理接口

    • set_height_range(Vector2) / get_height_range():高度范围管理,getter 为内联函数
    • update_height(real_t):内联函数,单个高度值的范围更新
    • update_heights(Vector2):内联函数,批量高度范围更新
    • calc_height_range():重新计算完整的高度范围
  • 实例化管理接口

    • set_instances(Dictionary) / get_instances():完整实例数据管理
    • set_vertex_spacing(real_t) / get_vertex_spacing():顶点间距管理,setter 包含 CLAMP 范围限制
  • 文件 I/O 与数据操作

    • save(String, bool):保存到文件,支持路径和 16 位格式选项,返回 Error 类型
    • set_data(Dictionary) / get_data():数据字典的序列化操作
    • duplicate(bool):区域复制,支持深度复制选项,返回 Ref<Terrain3DRegion>
  • 状态管理接口

    • set_deleted(bool) / is_deleted():删除状态管理,均为内联函数
    • set_edited(bool) / is_edited():编辑状态管理,均为内联函数
    • set_modified(bool) / is_modified():修改状态管理,均为内联函数
    • set_location(Vector2i) / get_location():位置坐标管理,getter 为内联函数

4 Terrain3DMaterial

这是一个用于管理 Terrain3D 地形系统材质的核心类,继承自 Godot 的 Resource 类。它负责处理地形的视觉渲染、着色器管理、调试视图和各种显示选项。该类作为地形渲染系统的材质控制中心,提供了完整的着色器代码生成、参数管理和实时更新功能。

4.1 主要职责

  • 着色器管理与生成

    • 管理活动着色器 _shader 和用户自定义着色器覆盖 _shader_override
    • 预加载和解析着色器代码,存储在 _shader_code 字典中
    • 动态生成着色器代码,支持代码插入和注释处理
    • 处理编辑器代码注入和着色器参数更新
  • 材质特性控制

    • 世界背景渲染模式:NONEFLATNOISE
    • 纹理过滤方式:LINEARNEAREST
    • 双重缩放、自动着色器等高级特性控制
  • 调试与可视化系统

    • 提供多种调试视图:棋盘格、灰度、高度图、锯齿度等
    • 控制纹理显示:控制纹理、混合、角度、缩放等
    • 支持颜色图、粗糙度图、纹理高度/法线/粗糙度等专项调试
  • 覆盖层显示管理

    • 区域网格、实例网格、顶点网格显示控制
    • 等高线、导航信息的可视化开关

4.2 核心成员

  • 着色器相关

    • _material: 材质的 RID 标识符
    • _shader: 当前活动的着色器引用
    • _shader_code: 存储所有加载的着色器和插入代码
    • _shader_override: 用户自定义着色器覆盖
    • _shader_params: 保存到磁盘的着色器参数字典
  • 特性控制变量

    • _world_background: 世界背景模式枚举
    • _texture_filtering: 纹理过滤方式枚举
    • _dual_scaling: 双重缩放开关
    • _auto_shader: 自动着色器开关
  • 调试状态变量

    • _debug_view_*: 一系列调试视图开关变量
    • _show_*: 覆盖层显示控制变量

4.3 核心接口

  • 初始化与生命周期

    • initialize(): 使用 Terrain3D 指针初始化材质
    • uninitialize()destroy(): 清理和销毁资源
    • update(): 更新材质状态
  • 着色器管理接口

    • enable_shader_override()set_shader_override(): 管理着色器覆盖
    • set_shader_param()get_shader_param(): 着色器参数访问
    • get_material_rid()get_shader_rid(): 获取底层资源标识
  • 材质特性设置

    • set_world_background()get_world_background(): 世界背景控制
    • set_texture_filtering()get_texture_filtering(): 纹理过滤控制
    • set_auto_shader()set_dual_scaling(): 高级特性开关
  • 调试视图控制

    • set_show_checkered()set_show_heightmap() 等: 各种调试视图开关
    • set_show_control_texture()set_show_colormap() 等: 纹理调试控制
    • set_show_region_grid()set_show_navigation() 等: 覆盖层显示控制

4.3.1 Terrain3DMaterial::_inject_editor_code

_inject_editor_code 函数的作用是将编辑器相关的代码片段插入到着色器源码中。它会:

  1. 先去除传入着色器代码中的注释。
  2. 查找 render_mode 语句后插入一些预处理代码(目前是占位)。
  3. vertex() 函数前插入与编辑器和调试相关的 shader 片段(如 DEBUG、EDITOR、OVERLAY 等)。
  4. fragment() 函数结尾插入更多调试和可视化相关的 shader 片段。
  5. 这些插入内容根据当前材质的状态(如是否显示轮廓、调试视图等)动态决定。

总之,这个函数让材质的着色器代码在编辑器和调试模式下能动态插入额外的可视化和调试功能。

4.3.2 Terrain3DMaterial::_update_shader

_update_shader() 函数负责更新材质所使用的着色器代码和相关参数。其主要流程如下:

  1. 初始化和日志记录,准备更新。
  2. 判断是否启用着色器覆盖(_shader_override),如果启用且有效,则优先使用覆盖的着色器代码,否则生成默认着色器代码。
  3. 将着色器代码注入编辑器相关代码后,设置到 _shader 对象,并通过渲染服务器(RS)将着色器应用到材质。
  4. 更新自定义着色器参数,包括将纹理等对象参数转换为 RID 并设置到材质。
  5. 设置特定的着色器参数(如背景模式)。
  6. 如果没有噪声纹理,则自动生成一个默认的噪声纹理并设置。
  7. 最后通知属性列表发生变化。

该函数确保材质的着色器和参数始终与当前设置保持同步,支持编辑器和运行时的各种功能切换。

4.3.3 Terrain3DMaterial::_update_maps

_update_maps 函数用于将地形相关的数据(如区域地图、区域位置、高度图、控制图、颜色图等)同步到材质参数。它从 _terrain 获取地形数据,校验区域地图大小,然后通过 RS->material_set_param 把这些数据设置到 _material,以便着色器能正确渲染地形。还会设置区域尺寸、顶点间距、网格尺寸等参数,并输出调试信息辅助开发和排查问题。

RS->material_set_param(_material, "_height_maps", data->get_height_maps_rid()); 这行代码是将高度图的资源 ID(RID)设置为材质参数 _height_maps。类似地,_control_maps_color_maps 也是通过 RS->material_set_param 设置到材质上。

这些参数会在 Godot 渲染管线中被着色器(Shader)访问,用于地形渲染。具体来说:

  • 在着色器代码(如 shaders/main.glsl)中,会通过 uniform 变量(如 uniform sampler2D _height_maps;)来读取这些参数,进行地形高度、控制、颜色等相关计算和渲染。
  • 这些参数的设置确保了 GPU 能够正确获取地形数据,实现地形的可视化和编辑功能。

简而言之,这些参数是传递给 GPU 着色器用的,主要用于地形渲染相关的计算。

4.3.4 Terrain3DMaterial::_update_texture_arrays

_update_texture_arrays() 函数用于更新着色器中与纹理数组相关的参数。它的主要流程如下:

  1. 检查资源列表(asset_list)是否有效且已初始化,否则记录错误并返回。
  2. 将各种纹理数组(如反照率、法线、颜色、深度、AO 强度、粗糙度、UV 缩放、垂直投影、去平铺等)通过 RS->material_set_param 设置到材质中。
  3. 根据纹理数量决定是否启用“棋盘格”调试视图:如果没有纹理,则启用棋盘格视图,否则关闭。
  4. 记录相关日志,便于调试。

该函数确保材质始终拥有最新的纹理数组数据,并根据资源情况自动切换调试显示模式。

5 Terrain3DAssets

Terrain3DAssets 是一个继承自 Godot Resource 的资源类,专门用于管理 Terrain3D 系统的所有纹理和网格资产。它负责将独立的纹理资源整合为渲染所需的纹理数组,并为网格资源生成预览缩略图,同时提供资产的序列化和生命周期管理。

5.1 主要职责

  • 资产统一管理
    • 管理两种类型的资产:纹理 (TYPE_TEXTURE) 和网格 (TYPE_MESH)。
    • 维护一个纹理资产列表 (_texture_list) 和一个网格资产列表 (_mesh_list)。
    • 提供资产的添加、移除、交换和更新机制。
  • 纹理数组生成
    • _texture_list 中的 Terrain3DTextureAsset 列表生成用于地形渲染的 albedonormal 纹理数组。
    • 将各个纹理的颜色、法线深度、AO、粗糙度等参数打包成数组,供着色器使用。
  • 网格缩略图创建
    • 内部搭建一个独立的渲染场景 (_scenario, _viewport, _camera 等),用于为 _mesh_list 中的每个网格资产自动生成预览缩略图。
  • 生命周期与持久化
    • 通过 initialize()destroy() 方法与 Terrain3D 主节点绑定和解绑。
    • 通过 save() 方法将资产配置保存到文件。

Godot 中 Texture 和 Mesh 有何区别,以及与 Material 的关系

在 Godot 中:

  • Texture(纹理):是一张图片,用于给 3D 或 2D 物体表面添加颜色、细节等视觉效果。常见类型有颜色贴图、法线贴图等。
  • Mesh(网格):是由顶点、边和面组成的 3D 形状,定义了物体的几何结构,比如立方体、球体等。
  • Material(材质):决定了物体表面的渲染方式,包括如何使用 Texture、光照、反射等。Material 通常会引用一个或多个 Texture,并应用到 Mesh 上。

关系如下: Mesh 决定形状,Material 决定表面表现,Texture 作为 Material 的属性被用来丰富表面细节。最终,Mesh 通过 Material 使用 Texture 来渲染出完整的视觉效果。

5.2 核心成员

  • 基础管理
    • _terrain: 指向其所属的 Terrain3D 实例的指针。
    • _texture_list: TypedArray<Terrain3DTextureAsset>,存储所有的纹理资产。
    • _mesh_list: TypedArray<Terrain3DMeshAsset>,存储所有的网格资产。
  • 纹理数据
    • _generated_albedo_textures / _generated_normal_textures: GeneratedTexture 类型的对象,用于创建和管理最终的纹理数组资源。
    • 存储所有纹理相关参数的打包数组。
      • _texture_colors:每个纹理的颜色信息数组。
      • _texture_normal_depths:每个纹理法线深度值数组,影响法线贴图的强度。
      • _texture_ao_strengths:每个纹理的环境光遮蔽强度数组。
      • _texture_roughness_mods:每个纹理的粗糙度修正数组,影响表面反射。
      • _texture_uv_scales:每个纹理的 UV 缩放数组,控制纹理在地形上的缩放比例。
      • _texture_vertical_projections:垂直投影相关的标志或参数,通常用于控制纹理投影方式。
      • _texture_detiles:每个纹理的细节贴图参数数组,通常包含 UV 偏移或缩放等信息。
  • 网格缩略图渲染系统
    • _scenario / _viewport / _camera / _key_light / _fill_light: 用于渲染缩略图的 Godot 底层渲染服务器资源 ID (RID)。
    • _mesh_instance: 用于在缩略图场景中实例化网格的 RID

5.3 核心接口

  • 生命周期
    • initialize(): 使用 Terrain3D 实例进行初始化。
    • destroy(): 清理所有资源和引用。
    • save(): 将当前资产配置保存到资源文件。
  • 纹理操作
    • set_texture() / get_texture(): 设置或获取指定 ID 的 Ref<Terrain3DTextureAsset>
    • set_texture_list(): 批量设置纹理列表。
    • update_texture_list(): 根据 _texture_list 的变化,触发纹理数组和相关参数的更新。
    • get_albedo_array_rid() / get_normal_array_rid(): 获取生成的纹理数组的 RID,供渲染使用。
  • 网格操作
    • set_mesh_asset() / get_mesh_asset(): 设置或获取指定 ID 的 Ref<Terrain3DMeshAsset>
    • set_mesh_list(): 批量设置网格列表。
    • create_mesh_thumbnails(): 为指定或全部网格资产生成缩略图。
    • update_mesh_list(): 更新网格列表,通常在列表变动后调用以刷新状态。

5.3.1 Terrain3DAssets::_setup_thumbnail_creation

_setup_thumbnail_creation 函数用于初始化用于生成网格缩略图的渲染环境。它会创建一个场景(scenario)、视口(viewport)、摄像机(camera)和两个灯光(key light 和 fill light),并将它们正确地设置到渲染服务器(RenderingServer)中。这样可以在一个独立的小场景里渲染网格模型,并生成预览缩略图。该函数只在 _scenario 未初始化时执行,避免重复创建资源。

通过 memnew 和 通过 RenderingServer 创建的对象有何区别

memnew 用于在 Godot C++ 模块中分配和构造 C++ 类对象,通常用于自定义类或 Godot 的扩展类实例。它直接在内存中创建对象,由 C++ 负责生命周期管理。

通过 RenderingServer 创建的对象(如 viewport_createcamera_create 等),是 Godot 引擎底层渲染系统的资源。这些对象通常返回 RID(Resource ID),由引擎统一管理和释放,生命周期由引擎控制,适用于 GPU 资源、渲染相关对象。

区别总结:

  • memnew:C++ 层对象,手动管理,适合逻辑/数据类。
  • RenderingServer:引擎资源对象,自动管理,适合渲染/场景资源。

这里能否用 memnew 构建 Scenario

不能。RS->scenario_create() 是 Godot 的 RenderingServer 提供的 API,用于创建场景(Scenario),它会返回一个 RID(资源 ID),而不是一个 C++ 对象指针。memnew 用于在堆上分配 C++ 类对象,但 Scenario 不是一个普通的 C++ 类实例,而是由底层渲染服务器管理的资源。

因此,这里必须使用 RS->scenario_create(),不能用 memnew

这段代码是用于设置网格缩略图创建的渲染环境。它使用 Godot 的渲染服务器 (RenderingServer) API 来创建一个虚拟的 3D 场景用于生成网格预览图。

主要组件:

  1. 场景 (Scenario): 创建一个 3D 渲染场景容器
  2. 视口 (Viewport): 创建 128x128 的透明背景渲染视口,设置为手动更新模式
  3. 相机 (Camera): 设置正交相机,位置在 (0,0,3),视野范围 1.0,近平面 0.01,远平面 1000.0
  4. 主光源 (Key Light): 创建方向光,从 (-1,-1,-1) 方向照射,模拟主要光源
  5. 补光 (Fill Light): 创建较暗的方向光 (30% 灰度),从上方 (0,1,0) 照射,用于补充阴影区域
  6. 网格实例: 创建用于渲染的网格实例对象

渲染流程: 这个设置创建了一个标准的 3D 预览环境,使用两点照明系统 (主光源 + 补光) 来确保网格的所有细节都能清晰可见,最终生成用于 UI 显示的缩略图纹理。

5.3.2 Terrain3DAssets::update_texture_list

update_texture_list 函数用于重新连接所有纹理相关的信号,并更新纹理数组的状态。其主要流程如下:

  1. 遍历 _texture_list,为每个有效的 Terrain3DTextureAsset 连接 file_changedsetting_changed 信号,确保纹理文件或设置变更时能触发对应的更新函数。
  2. 清空已生成的反照率和法线纹理数组。
  3. 调用 _update_texture_files() 重新生成纹理文件相关的数据。
  4. 调用 _update_texture_settings() 更新纹理的颜色、法线深度等设置。

该函数保证纹理资源的信号连接和数据同步,适用于纹理列表发生变化时的刷新操作。

5.3.3 Terrain3DAssets::_update_texture_files

Terrain3DAssets::_update_texture_files() 是一个用于更新地形纹理资源的私有成员函数。其主要功能如下:

  1. 初始化和清理:清空已生成的纹理数组,检查纹理列表是否为空,若为空则发射 textures_changed 信号并返回。
  2. 检测纹理属性:遍历所有纹理,检测每个纹理的尺寸、格式和 mipmaps 设置,确保所有纹理属性一致,否则设置警告并输出错误日志。
  3. 生成默认纹理:如果某些纹理缺失,则自动生成默认填充纹理(如棋盘格或法线贴图),并创建新的 ImageTexture
  4. 生成纹理数组:将所有有效的纹理图像收集到数组中,创建纹理数组资源。
  5. 发射信号:最后发射 textures_changed 信号,通知外部纹理已更新。

这个函数作用实际上是将所有纹理(_texture_list 中的)整理成一个纹理数组,并通过 ImageTexture::create_from_image 等方法上传到显卡的显存(VRAM),以便后续渲染时高效访问。原始的纹理数据通常先存储在内存(RAM)中,只有经过这一步处理后,才会被 GPU 用于渲染。这样做可以提升渲染性能,并保证纹理格式、尺寸等一致性。

5.3.4 Terrain3DAssets::_update_texture_settings

Terrain3DAssets::_update_texture_settings() 是一个私有方法,用于更新地形纹理的相关设置数组。它会遍历 _texture_list,收集每个纹理的颜色、法线深度、AO 强度、粗糙度、UV 缩放、垂直投影标志和去平铺参数,并存入对应的成员变量数组。最后会发射 textures_changed 信号,通知外部纹理设置已更新。此方法通常在纹理属性变更时被调用。

5.3.5 Terrain3DAssets::create_mesh_thumbnails

create_mesh_thumbnailsTerrain3DAssets 类的一个成员函数,用于为指定的网格(或全部网格)生成缩略图。其主要流程如下:

  1. 根据传入的 p_idp_size,确定要处理的网格范围和缩略图尺寸。
  2. 遍历网格列表,对每个网格:
    • 获取网格和材质,设置渲染实例。
    • 计算网格的包围盒,调整变换,使网格居中并缩放到合适大小。
    • 设置渲染视口参数,触发一次渲染。
    • 从视口获取渲染后的图像,生成缩略图纹理并赋值给网格资源。
  3. 该方法主要用于编辑器预览或资源管理,便于快速显示网格的可视化缩略图。

6 Terrain3DTextureAsset

Terrain3DTextureAsset 是一个继承自 Terrain3DAssetResource 的 Godot 扩展类,用于管理和封装地形系统中单个纹理资源的属性和行为。该类通过 GDCLASS 宏注册到 Godot 的类型系统中,支持在编辑器中进行可视化编辑和序列化。

6.1 主要职责

  • 纹理资源管理

    • 管理反照率纹理 (_albedo_texture) 和法线纹理 (_normal_texture) 的引用
    • 提供纹理格式验证机制,确保纹理符合地形系统要求
    • 支持纹理的动态加载和替换
  • 材质属性控制

    • 控制反照率颜色 (_albedo_color) 作为基础着色
    • 管理法线强度 (_normal_depth)、环境光遮蔽强度 (_ao_strength)、粗糙度 (_roughness) 等表面属性
    • 提供 UV 缩放 (_uv_scale) 控制纹理平铺密度
  • 高级渲染特性

    • 支持垂直投影模式 (_vertical_projection) 用于陡峭表面的纹理投影
    • 实现去平铺算法参数控制,包括旋转偏移 (_detiling_rotation) 和位移偏移 (_detiling_shift)

6.2 核心成员

  • 纹理资源

    • _albedo_texture: Ref<Texture2D> 类型的反照率纹理引用
    • _normal_texture: Ref<Texture2D> 类型的法线纹理引用
  • 表面属性

    • _albedo_color: 基础颜色,默认为白色 (1,1,1,1)
    • _normal_depth: 法线强度,默认 0.5,控制法线贴图的凹凸效果强度
    • _ao_strength: 环境光遮蔽强度,默认 0.5
    • _roughness: 表面粗糙度,默认 0.0,影响反射和高光效果
  • UV 和投影控制

    • _uv_scale: UV 缩放系数,默认 0.1,控制纹理重复频率
    • _vertical_projection: 布尔值,控制是否使用垂直投影模式
  • 去平铺参数

    • _detiling_rotation: 去平铺旋转角度,用于破坏纹理重复的规律性
    • _detiling_shift: 去平铺位移量,进一步增加纹理变化

6.3 核心接口

  • 基础属性访问器

    • set_name()/get_name(): 继承的资源名称管理
    • set_id()/get_id(): 继承的唯一标识符管理
    • clear(): 重置所有属性到默认状态
  • 纹理操作接口

    • set_albedo_texture()/get_albedo_texture(): 反照率纹理的设置和获取
    • set_normal_texture()/get_normal_texture(): 法线纹理的设置和获取
    • _is_valid_format(): 私有方法,验证纹理格式的有效性
  • 材质属性接口

    • set_albedo_color()/get_albedo_color(): 基础颜色控制
    • set_normal_depth()/get_normal_depth(): 法线强度调节
    • set_ao_strength()/get_ao_strength(): 环境光遮蔽强度设置
    • set_roughness()/get_roughness(): 表面粗糙度控制
  • 渲染控制接口

    • set_uv_scale()/get_uv_scale(): UV 缩放控制
    • set_vertical_projection()/get_vertical_projection(): 投影模式切换
    • set_detiling_rotation()/get_detiling_rotation(): 去平铺旋转设置
    • set_detiling_shift()/get_detiling_shift(): 去平铺位移设置

7 Terrain3DMeshAsset

Terrain3DMeshAsset 是一个继承自 Terrain3DAssetResource 的网格资产类,专门用于管理地形系统中的 3D 网格对象。该类支持多级细节层次(LOD)系统、阴影投射控制、材质管理以及程序化网格生成功能,为地形上的植被、岩石等静态网格对象提供完整的资产管理解决方案。

主要职责

  • 网格资产管理

    • 管理 Ref<PackedScene> 场景文件的加载和存储
    • 支持程序化生成的网格类型(如 TYPE_TEXTURE_CARD
    • 提供网格的启用/禁用状态控制
    • 生成和管理网格缩略图 Ref<Texture2D>
  • LOD 系统控制

    • 支持最多 10 个 LOD 级别(MAX_LOD_COUNT = 10
    • 管理 TypedArray<Mesh> 存储不同 LOD 级别的网格
    • 控制 LOD 距离范围通过 PackedFloat32Array _lod_ranges
    • 支持独立的阴影 LOD 设置和阴影替身(impostor)
  • 材质和渲染管理

    • 支持材质覆盖 Ref<Material> _material_override
    • 支持材质叠加 Ref<Material> _material_overlay
    • 控制阴影投射行为 ShadowCasting _cast_shadows
    • 管理渲染相关的高度偏移 real_t _height_offset
  • 程序化网格生成

    • 支持纹理卡片等预定义生成类型
    • 控制生成的面数 int _generated_faces
    • 设置生成网格的尺寸 Vector2 _generated_size

核心成员

  • 资产配置

    • bool _enabled: 资产启用状态
    • Ref<PackedScene> _packed_scene: 主要的场景文件引用
    • GenType _generated_type: 程序化生成类型枚举
    • real_t _density: 资产密度设置
  • LOD 相关

    • TypedArray<Mesh> _meshes: 存储各 LOD 级别网格的数组
    • PackedFloat32Array _lod_ranges: LOD 切换距离数组
    • int _last_lod: 最后使用的 LOD 级别
    • int _last_shadow_lod: 阴影 LOD 的最后级别
    • int _shadow_impostor: 阴影替身 LOD 级别
    • real_t _fade_margin: LOD 切换时的淡化边距
  • 材质和渲染

    • ShadowCasting _cast_shadows: 阴影投射设置
    • Ref<Material> _material_override/_material_overlay: 材质覆盖和叠加
    • real_t _height_offset: 高度偏移量

核心接口

  • 基础资产管理

    • set_enabled(bool)/is_enabled(): 控制资产启用状态
    • set_scene_file()/get_scene_file(): 管理 PackedScene 文件
    • get_mesh(int lod): 获取指定 LOD 级别的网格
    • get_thumbnail(): 获取资产缩略图
  • LOD 控制接口

    • get_lod_count(): 获取可用 LOD 级别数量
    • set_lod_range(int, real_t)/get_lod_range(): 设置和获取 LOD 距离
    • set_lod0_range()set_lod9_range(): 各个 LOD 级别的快速设置接口
    • get_lod_range_begin()/get_lod_range_end(): 获取 LOD 范围边界
  • 材质和渲染接口

    • set_cast_shadows()/get_cast_shadows(): 阴影投射控制
    • get_lod_cast_shadows(int): 获取特定 LOD 的阴影设置
    • set_material_override()/set_material_overlay(): 材质管理
    • set_height_offset()/get_height_offset(): 高度偏移控制
  • 程序化生成接口

    • set_generated_type()/get_generated_type(): 生成类型控制
    • set_generated_faces()/get_generated_faces(): 面数控制
    • set_generated_size()/get_generated_size(): 生成尺寸设置

8 Terrain3DCollision

作用: 地形碰撞体管理类,为地形生成和维护物理碰撞体。

实现细节:

  • 根据地形高度图动态生成碰撞网格。
  • 支持碰撞体的分块、LOD、动态更新。
  • 与 Godot 物理系统集成,支持物理交互。

9 Terrain3DInstancer

作用: 地形实例化管理类,负责在地形上批量生成物体(如植被、石头等)。

实现细节:

  • 支持基于掩码或规则的批量实例化。
  • 管理实例的生命周期、可见性、LOD。
  • 支持与地形数据联动,实现自动分布和编辑。

10 Terrain3DEditor

作用: 地形编辑器辅助类,负责与 Godot 编辑器的交互,实现地形的可视化编辑。

实现细节:

  • 提供画刷、选择、区域编辑等工具。
  • 响应编辑器事件,实现实时预览和反馈。
  • 支持撤销/重做、批量操作等编辑器功能。

11 Terrain3DUtil

作用: 地形工具类,提供各种静态工具函数,辅助地形开发与调试。

实现细节:

  • 文件、路径、字符串、图像等常用工具函数。
  • 提供调试输出、数据转换、辅助计算等功能。
  • 多为静态方法,便于全局调用。