MultiMesh 森林
两个 MultiMeshInstance3D 节点绘制出四千棵树,树冠使用逐实例颜色。
上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。
用到的 Godot 类
MultiMeshInstance3DMultiMeshCylinderMesh
代码
scripts/main.gd
extends Node
const TREES: int = 4000
const FIELD: float = 48.0
var camera: Camera3D
var angle: float = 0.0
func _ready() -> void:
_add_environment()
_add_light()
_add_ground()
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
rng.seed = 20260814
var spots: Array[Transform3D] = []
var tint: Array[Color] = []
for i in TREES:
var turn: float = rng.randf() * TAU
var radius: float = sqrt(rng.randf()) * FIELD
var size: float = rng.randf_range(0.7, 1.6)
var basis: Basis = Basis(Vector3.UP, rng.randf() * TAU).scaled(Vector3(size, size, size))
spots.append(Transform3D(basis, Vector3(cos(turn), 0, sin(turn)) * radius))
tint.append(Color('#8eef97').darkened(rng.randf_range(0.15, 0.6)))
_add_layer(_trunk_mesh(), spots, 0.6, [])
_add_layer(_crown_mesh(), spots, 2.4, tint)
_add_counter()
camera = Camera3D.new()
camera.fov = 55.0
add_child(camera)
camera.current = true
_place_camera()
print('two MultiMeshInstance3D draw %d trees, and the camera keeps circling them' % TREES)
func _process(delta: float) -> void:
angle += delta * 0.1
_place_camera()
func _place_camera() -> void:
camera.position = Vector3(sin(angle), 0, cos(angle)) * 56.0 + Vector3(0, 24, 0)
camera.look_at(Vector3.ZERO, Vector3.UP)
func _add_layer(mesh: Mesh, spots: Array[Transform3D], lift: float, tint: Array[Color]) -> void:
# use_colors and transform_format only take effect while instance_count is still zero
var multimesh: MultiMesh = MultiMesh.new()
multimesh.transform_format = MultiMesh.TRANSFORM_3D
multimesh.use_colors = not tint.is_empty()
multimesh.mesh = mesh
multimesh.instance_count = spots.size()
for i in spots.size():
multimesh.set_instance_transform(i, spots[i].translated_local(Vector3(0, lift, 0)))
if not tint.is_empty():
multimesh.set_instance_color(i, tint[i])
var material: StandardMaterial3D = StandardMaterial3D.new()
material.albedo_color = Color('#8eef97') if not tint.is_empty() else Color('#4a3a2e')
material.vertex_color_use_as_albedo = not tint.is_empty()
material.roughness = 0.85
var instance: MultiMeshInstance3D = MultiMeshInstance3D.new()
instance.multimesh = multimesh
instance.material_override = material
add_child(instance)
func _trunk_mesh() -> CylinderMesh:
var mesh: CylinderMesh = CylinderMesh.new()
mesh.top_radius = 0.13
mesh.bottom_radius = 0.19
mesh.height = 1.2
mesh.radial_segments = 6
mesh.rings = 1
return mesh
func _crown_mesh() -> CylinderMesh:
var mesh: CylinderMesh = CylinderMesh.new()
mesh.top_radius = 0.0
mesh.bottom_radius = 1.0
mesh.height = 2.2
mesh.radial_segments = 7
mesh.rings = 1
return mesh
func _add_counter() -> void:
var layer: CanvasLayer = CanvasLayer.new()
add_child(layer)
var counter: Label = Label.new()
counter.text = '%d trees\n%d MultiMesh instances\n2 MeshInstance3D nodes' % [TREES, TREES * 2]
counter.position = Vector2(28, 24)
counter.add_theme_font_size_override('font_size', 24)
counter.add_theme_color_override('font_color', Color('#f3d48d'))
layer.add_child(counter)
func _add_ground() -> void:
var mesh: PlaneMesh = PlaneMesh.new()
mesh.size = Vector2(400, 400)
var material: StandardMaterial3D = StandardMaterial3D.new()
material.albedo_color = Color('#2a2f3d')
material.roughness = 0.95
var ground: MeshInstance3D = MeshInstance3D.new()
ground.mesh = mesh
ground.material_override = material
add_child(ground)
func _add_environment() -> void:
var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
sky_material.sky_top_color = Color(0.08, 0.11, 0.22)
sky_material.sky_horizon_color = Color(0.3, 0.34, 0.46)
sky_material.ground_bottom_color = Color(0.06, 0.07, 0.11)
sky_material.ground_horizon_color = Color(0.22, 0.25, 0.34)
var sky: Sky = Sky.new()
sky.sky_material = sky_material
var env: Environment = Environment.new()
env.background_mode = Environment.BG_SKY
env.sky = sky
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_energy = 0.8
var world: WorldEnvironment = WorldEnvironment.new()
world.environment = env
add_child(world)
func _add_light() -> void:
var light: DirectionalLight3D = DirectionalLight3D.new()
light.rotation_degrees = Vector3(-52, -35, 0)
light.light_energy = 1.3
add_child(light)