源码: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)
- 管理多层细节(LOD)网格生成和渲染(
-
物理和碰撞系统
- 生成和管理地形碰撞形状(
_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_warning和p_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_process 是 Terrain3D 类中的一个私有方法,用于在每一帧物理更新时执行地形相关的处理。其主要功能如下:
- 检查
Terrain3D是否已初始化,未初始化则直接返回。 - 检查相机对象
_camera是否有效,无效则尝试获取当前相机。 - 如果
_mesher存在,调用其snap()方法,通常用于网格对齐或更新。 - 如果
_collision存在,调用其update()方法,更新碰撞信息。
该方法通常由 _notification 的 NOTIFICATION_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-Instance或Mesh Material Instance的缩写,用于管理地形相关的实例化对象或网格实例。
1.3.5 Terrain3D::_setup_mouse_picking
Terrain3D::_setup_mouse_picking() 是一个私有成员函数,用于初始化地形的鼠标拾取相关功能。它会在节点进入场景树时被调用,主要作用如下:
- 检查节点是否在场景树中,如果不在则记录错误并返回。
- 创建一个名为
MouseViewport的SubViewport子节点,并设置其大小、缩放模式和更新模式。 - 该视口用于后续的鼠标拾取操作,比如通过 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,主要原因有:
- 某些显卡或渲染后端对极小尺寸(如
1x1)的帧缓冲支持不佳,可能导致渲染异常或采样不稳定。2x2能更好地兼容多平台,且采样时可以取中心像素,避免边缘采样带来的误差。- 对鼠标拾取来说,
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_ORTHOGONAL,Camera3D还支持以下投影模式:
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),并为其分配一个自定义的着色器材质。具体流程如下:
- 创建一个
MeshInstance3D作为_mouse_quad,并将其命名为MouseQuad,挂载到_mouse_cam(鼠标专用相机)下。 - 创建一个
QuadMesh,设置其大小为 0.1x0.1,并赋给_mouse_quad作为网格。 - 读取
shaders/gpu_depth.glsl文件内容作为着色器代码,创建Shader和ShaderMaterial,并将着色器分配给材质。 - 将材质设置为
_mouse_quad的表面材质。 - 设置
_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的主要原因是:
- GPU 深度拾取:通过在摄像机前放置一个 Quad,并用特殊的 Shader 渲染,可以将鼠标指向位置的深度信息编码到颜色中,便于后续读取和反解出三维坐标,实现高效的地形拾取。
- 与摄像机同步:作为摄像机的子节点,Quad 始终和摄像机对齐,保证拾取区域和视角一致。
- 只渲染需要的区域: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::_notification 是 Terrain3D 类的一个保护成员函数,用于处理 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_triangles 是 Terrain3D 类的私有成员函数,用于根据地形数据生成三角形顶点和可选的 UV 坐标。其主要功能如下:
-
参数说明:
p_vertices:用于存储生成的三角形顶点。p_uvs:可选参数,存储每个顶点的 UV 坐标。p_lod:细节层级(Level of Detail),决定采样步长。p_filter:高度采样方式(如最近邻或最小值)。p_require_nav:是否只生成可导航区域的三角形。p_global_aabb:限定生成三角形的空间范围。
-
主要流程:
- 检查地形数据对象
_data是否有效。 - 根据
p_lod计算采样步长。(LOD 越高步长越大,细节越少) - 如果
p_global_aabb没有体积,则遍历整个地形区域,按步长采样顶点和高度,生成三角形并填充到p_vertices和p_uvs。 - 如果有体积限制,则只在指定的 AABB 区域内生成三角形。
- 支持不同高度采样方式和导航区域过滤。
- 检查地形数据对象
-
作用: 该函数是地形网格生成的核心,供物理、渲染、导航等系统调用,生成地形的三角形数据。
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 指定。
具体流程如下:
p_direction.normalized()得到归一化的方向向量。- 计算
p_src_pos - direction,即在p_src_pos位置沿着direction反方向移动一单位。 - 用这个新位置设置
_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_mode 为 true 时,会通过 _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);- 让
_mouse_cam相机朝向指定方向(direction),用于捕捉地形的深度信息。 - 设置
_mouse_vp视口为只更新一次(UPDATE_ONCE),以便获取最新的渲染结果。 - 获取视口的纹理(
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_vpsize 时设置),对性能影响很小
- 读取图像中 (0, 0) 像素的颜色值(
screen_depth),该像素包含了地形的深度信息。
这些步骤是为了通过 GPU 渲染结果获取地形的深度数据,实现地形的鼠标拾取或射线检测。
总的来说,GPU 方案的原理如下:
- 通过 GPU 渲染,将场景投影到一个小的
SubViewport(如 2x2 像素),并用正交或透视相机渲染地形。 - 渲染时,地形的 shader 会输出深度信息到深度图(Depth Texture)。
- 通过读取
ViewportTexture的像素(如get_pixel(0, 0)),获得该像素的深度值。 - 利用相机的投影参数和深度值,可以反推出相机到地面的实际距离,从而得到地面交点的世界坐标。
这种方法利用 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) - 支持区域的动态创建、加载、卸载和删除操作
- 跟踪区域生命周期状态:修改状态、删除状态、活跃状态
- 实现 32×32 区域网格系统 (
-
CPU-GPU 渲染数据同步
- 将 CPU 端的
Image数据转换为 GPU 端的TextureArray纹理数组 - 通过
GeneratedTexture类管理渲染服务器的 RID 资源 - 支持增量更新和全量更新两种同步模式
- 自动处理纹理的 mipmap 生成
- 将 CPU 端的
-
全局坐标系统一访问
- 提供基于全局坐标的统一数据访问接口,屏蔽底层区域分割复杂性
- 自动完成全局坐标到区域坐标的转换计算
- 通过内联函数优化频繁访问操作的性能
- 支持越界访问的安全处理和边界检查
-
地形数据持久化
- 支持目录级的整体地形项目保存和加载
- 支持单个区域的独立保存和加载操作
- 提供多种图像格式的导出功能
- 处理 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是渲染资源。所以这里通过
RenderingServer把Image转成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):全局坐标直接获取区域 IDstatic 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_HEIGHT、TYPE_CONTROL、TYPE_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):获取基础纹理 IDvoid 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):获取覆盖纹理 IDvoid 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():获取高度纹理的渲染服务器 RIDRID get_control_maps_rid():获取控制纹理的渲染服务器 RIDRID get_color_maps_rid():获取颜色纹理的渲染服务器 RIDTypedArray<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_BLACK、COLOR_CONTROL、COLOR_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_map(Ref<Image>):地形高度信息,使用FORMAT_RF格式_control_map(Ref<Image>):材质混合控制信息,使用FORMAT_RF格式_color_map(Ref<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字典中 - 动态生成着色器代码,支持代码插入和注释处理
- 处理编辑器代码注入和着色器参数更新
- 管理活动着色器
-
材质特性控制
- 世界背景渲染模式:
NONE、FLAT、NOISE - 纹理过滤方式:
LINEAR、NEAREST - 双重缩放、自动着色器等高级特性控制
- 世界背景渲染模式:
-
调试与可视化系统
- 提供多种调试视图:棋盘格、灰度、高度图、锯齿度等
- 控制纹理显示:控制纹理、混合、角度、缩放等
- 支持颜色图、粗糙度图、纹理高度/法线/粗糙度等专项调试
-
覆盖层显示管理
- 区域网格、实例网格、顶点网格显示控制
- 等高线、导航信息的可视化开关
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 函数的作用是将编辑器相关的代码片段插入到着色器源码中。它会:
- 先去除传入着色器代码中的注释。
- 查找
render_mode语句后插入一些预处理代码(目前是占位)。 - 在
vertex()函数前插入与编辑器和调试相关的 shader 片段(如 DEBUG、EDITOR、OVERLAY 等)。 - 在
fragment()函数结尾插入更多调试和可视化相关的 shader 片段。 - 这些插入内容根据当前材质的状态(如是否显示轮廓、调试视图等)动态决定。
总之,这个函数让材质的着色器代码在编辑器和调试模式下能动态插入额外的可视化和调试功能。
4.3.2 Terrain3DMaterial::_update_shader
_update_shader() 函数负责更新材质所使用的着色器代码和相关参数。其主要流程如下:
- 初始化和日志记录,准备更新。
- 判断是否启用着色器覆盖(_shader_override),如果启用且有效,则优先使用覆盖的着色器代码,否则生成默认着色器代码。
- 将着色器代码注入编辑器相关代码后,设置到
_shader对象,并通过渲染服务器(RS)将着色器应用到材质。 - 更新自定义着色器参数,包括将纹理等对象参数转换为 RID 并设置到材质。
- 设置特定的着色器参数(如背景模式)。
- 如果没有噪声纹理,则自动生成一个默认的噪声纹理并设置。
- 最后通知属性列表发生变化。
该函数确保材质的着色器和参数始终与当前设置保持同步,支持编辑器和运行时的各种功能切换。
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() 函数用于更新着色器中与纹理数组相关的参数。它的主要流程如下:
- 检查资源列表(
asset_list)是否有效且已初始化,否则记录错误并返回。 - 将各种纹理数组(如反照率、法线、颜色、深度、AO 强度、粗糙度、UV 缩放、垂直投影、去平铺等)通过
RS->material_set_param设置到材质中。 - 根据纹理数量决定是否启用“棋盘格”调试视图:如果没有纹理,则启用棋盘格视图,否则关闭。
- 记录相关日志,便于调试。
该函数确保材质始终拥有最新的纹理数组数据,并根据资源情况自动切换调试显示模式。
5 Terrain3DAssets
Terrain3DAssets 是一个继承自 Godot Resource 的资源类,专门用于管理 Terrain3D 系统的所有纹理和网格资产。它负责将独立的纹理资源整合为渲染所需的纹理数组,并为网格资源生成预览缩略图,同时提供资产的序列化和生命周期管理。
5.1 主要职责
- 资产统一管理
- 管理两种类型的资产:纹理 (
TYPE_TEXTURE) 和网格 (TYPE_MESH)。 - 维护一个纹理资产列表 (
_texture_list) 和一个网格资产列表 (_mesh_list)。 - 提供资产的添加、移除、交换和更新机制。
- 管理两种类型的资产:纹理 (
- 纹理数组生成
- 从
_texture_list中的Terrain3DTextureAsset列表生成用于地形渲染的albedo和normal纹理数组。 - 将各个纹理的颜色、法线深度、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_create、camera_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 场景用于生成网格预览图。
主要组件:
- 场景 (Scenario): 创建一个 3D 渲染场景容器
- 视口 (Viewport): 创建 128x128 的透明背景渲染视口,设置为手动更新模式
- 相机 (Camera): 设置正交相机,位置在 (0,0,3),视野范围 1.0,近平面 0.01,远平面 1000.0
- 主光源 (Key Light): 创建方向光,从 (-1,-1,-1) 方向照射,模拟主要光源
- 补光 (Fill Light): 创建较暗的方向光 (30% 灰度),从上方 (0,1,0) 照射,用于补充阴影区域
- 网格实例: 创建用于渲染的网格实例对象
渲染流程: 这个设置创建了一个标准的 3D 预览环境,使用两点照明系统 (主光源 + 补光) 来确保网格的所有细节都能清晰可见,最终生成用于 UI 显示的缩略图纹理。
5.3.2 Terrain3DAssets::update_texture_list
update_texture_list 函数用于重新连接所有纹理相关的信号,并更新纹理数组的状态。其主要流程如下:
- 遍历
_texture_list,为每个有效的Terrain3DTextureAsset连接file_changed和setting_changed信号,确保纹理文件或设置变更时能触发对应的更新函数。 - 清空已生成的反照率和法线纹理数组。
- 调用
_update_texture_files()重新生成纹理文件相关的数据。 - 调用
_update_texture_settings()更新纹理的颜色、法线深度等设置。
该函数保证纹理资源的信号连接和数据同步,适用于纹理列表发生变化时的刷新操作。
5.3.3 Terrain3DAssets::_update_texture_files
Terrain3DAssets::_update_texture_files() 是一个用于更新地形纹理资源的私有成员函数。其主要功能如下:
- 初始化和清理:清空已生成的纹理数组,检查纹理列表是否为空,若为空则发射
textures_changed信号并返回。 - 检测纹理属性:遍历所有纹理,检测每个纹理的尺寸、格式和 mipmaps 设置,确保所有纹理属性一致,否则设置警告并输出错误日志。
- 生成默认纹理:如果某些纹理缺失,则自动生成默认填充纹理(如棋盘格或法线贴图),并创建新的
ImageTexture。 - 生成纹理数组:将所有有效的纹理图像收集到数组中,创建纹理数组资源。
- 发射信号:最后发射
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_thumbnails 是 Terrain3DAssets 类的一个成员函数,用于为指定的网格(或全部网格)生成缩略图。其主要流程如下:
- 根据传入的
p_id和p_size,确定要处理的网格范围和缩略图尺寸。 - 遍历网格列表,对每个网格:
- 获取网格和材质,设置渲染实例。
- 计算网格的包围盒,调整变换,使网格居中并缩放到合适大小。
- 设置渲染视口参数,触发一次渲染。
- 从视口获取渲染后的图像,生成缩略图纹理并赋值给网格资源。
- 该方法主要用于编辑器预览或资源管理,便于快速显示网格的可视化缩略图。
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)
- 支持最多 10 个 LOD 级别(
-
材质和渲染管理
- 支持材质覆盖
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
作用: 地形工具类,提供各种静态工具函数,辅助地形开发与调试。
实现细节:
- 文件、路径、字符串、图像等常用工具函数。
- 提供调试输出、数据转换、辅助计算等功能。
- 多为静态方法,便于全局调用。