оптимизация рендера

This commit is contained in:
2026-06-25 17:10:07 +04:00
parent 8c6cd88e31
commit af5e1f4d6c
26 changed files with 2691 additions and 171 deletions
+114 -16
View File
@@ -7,6 +7,8 @@ class_name WMOBuilder
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
const M2_RIGHT_YAW_OFFSET := PI * 0.5
const BUILD_OCCLUDERS := false
const OCCLUDER_MIN_TRIANGLES := 16
static var _texture_cache: Dictionary = {}
# rel_path → Node3D prototype (built once, duplicated per usage)
@@ -45,6 +47,7 @@ static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
# Build one MeshInstance3D per group, plus optional liquid mesh as sibling
var groups: Array = data.get("groups", [])
var occluder_root: Node3D = null
for gi in groups.size():
var g: Dictionary = groups[gi]
if g.is_empty():
@@ -53,6 +56,16 @@ static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
mesh_inst.name = "Group_%d" % gi
root.add_child(mesh_inst)
if BUILD_OCCLUDERS:
var occluder_inst := _build_group_occluder(g, materials)
if occluder_inst != null:
if occluder_root == null:
occluder_root = Node3D.new()
occluder_root.name = "Occluders"
root.add_child(occluder_root)
occluder_inst.name = "Group_%d_Occluder" % gi
occluder_root.add_child(occluder_inst)
var liquid_dict: Variant = g.get("liquid", null)
if liquid_dict is Dictionary and not (liquid_dict as Dictionary).is_empty():
var liquid_inst := _build_group_liquid(liquid_dict)
@@ -67,8 +80,7 @@ static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
return root
# Instantiates M2 doodads for set 0. Each placement is a M2 prototype
# duplicated under the WMO root with a local Transform3D.
# Instantiates M2 doodads for set 0 as one MultiMesh per M2 path.
static func _build_default_doodads(root: Node3D, data: Dictionary,
extracted_dir: String) -> void:
@@ -83,10 +95,7 @@ static func _build_default_doodads(root: Node3D, data: Dictionary,
if count <= 0:
return
var doodad_root := Node3D.new()
doodad_root.name = "Doodads"
root.add_child(doodad_root)
var groups: Dictionary = {}
var end: int = mini(start + count, placements.size())
for i in range(start, end):
var p: Dictionary = placements[i]
@@ -97,22 +106,53 @@ static func _build_default_doodads(root: Node3D, data: Dictionary,
if rel_path.ends_with(".mdx") or rel_path.ends_with(".mdl"):
rel_path = rel_path.get_basename() + ".m2"
var prototype: Node3D = _get_or_load_m2_prototype(rel_path, extracted_dir)
if prototype == null:
continue
var inst := prototype.duplicate() as Node3D
if inst == null:
continue
inst.name = "%s_%d" % [rel_path.get_file().get_basename(), i]
var pos: Vector3 = p.get("pos", Vector3.ZERO)
var rot_q: Quaternion = p.get("rot", Quaternion.IDENTITY)
var scl: float = float(p.get("scale", 1.0))
var basis := (Basis(rot_q) * Basis(Vector3.UP, M2_RIGHT_YAW_OFFSET)).scaled(Vector3.ONE * maxf(scl, 0.001))
inst.transform = Transform3D(basis, pos)
var xform := Transform3D(basis, pos)
if not groups.has(rel_path):
groups[rel_path] = []
(groups[rel_path] as Array).append(xform)
if groups.is_empty():
return
var doodad_root := Node3D.new()
doodad_root.name = "Doodads"
root.add_child(doodad_root)
for rel_path in groups.keys():
var mesh := _get_or_load_m2_mesh(rel_path, extracted_dir)
if mesh == null:
continue
var transforms: Array = groups[rel_path]
var mm := MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.mesh = mesh
mm.instance_count = transforms.size()
for i in transforms.size():
mm.set_instance_transform(i, transforms[i])
var inst := MultiMeshInstance3D.new()
inst.name = rel_path.get_file().get_basename()
inst.multimesh = mm
inst.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
doodad_root.add_child(inst)
if doodad_root.get_child_count() == 0:
doodad_root.queue_free()
static func _get_or_load_m2_mesh(rel_path: String, extracted_dir: String) -> Mesh:
var prototype: Node3D = _get_or_load_m2_prototype(rel_path, extracted_dir)
if prototype == null:
return null
for child in prototype.get_children():
if child is MeshInstance3D:
return (child as MeshInstance3D).mesh
return null
static func _get_or_load_m2_prototype(rel_path: String,
extracted_dir: String) -> Node3D:
@@ -178,6 +218,7 @@ static func _build_group_mesh(
if verts.is_empty() or indices.is_empty():
var empty := MeshInstance3D.new()
empty.mesh = mesh
empty.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
return empty
if batches.is_empty():
@@ -227,9 +268,65 @@ static func _build_group_mesh(
var mi := MeshInstance3D.new()
mi.mesh = mesh
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
return mi
static func _build_group_occluder(g: Dictionary, material_defs: Array) -> OccluderInstance3D:
var verts: PackedVector3Array = g.get("vertices", PackedVector3Array())
var indices: PackedInt32Array = g.get("indices", PackedInt32Array())
if verts.is_empty() or indices.is_empty():
return null
var batches: Array = g.get("batches", [])
var occluder_indices := PackedInt32Array()
if batches.is_empty():
for i in range(0, indices.size(), 3):
if i + 2 >= indices.size():
break
occluder_indices.append(indices[i + 0])
occluder_indices.append(indices[i + 2])
occluder_indices.append(indices[i + 1])
else:
for batch_variant in batches:
if not (batch_variant is Dictionary):
continue
var batch: Dictionary = batch_variant
if not _material_can_occlude(int(batch.get("material_id", -1)), material_defs):
continue
var idx_start: int = int(batch.get("index_start", 0))
var idx_count: int = int(batch.get("index_count", 0))
if idx_count <= 0 or idx_start >= indices.size():
continue
for i in range(idx_start, mini(idx_start + idx_count, indices.size()), 3):
if i + 2 >= indices.size():
break
occluder_indices.append(indices[i + 0])
occluder_indices.append(indices[i + 2])
occluder_indices.append(indices[i + 1])
if occluder_indices.size() < OCCLUDER_MIN_TRIANGLES * 3:
return null
var occluder := ArrayOccluder3D.new()
occluder.set_arrays(verts, occluder_indices)
var inst := OccluderInstance3D.new()
inst.set_occluder(occluder)
return inst
static func _material_can_occlude(mat_id: int, material_defs: Array) -> bool:
if mat_id < 0:
return true
if mat_id >= material_defs.size():
return false
var mat_def: Variant = material_defs[mat_id]
if not (mat_def is Dictionary):
return false
return int((mat_def as Dictionary).get("blend_mode", 0)) == 0
static func _build_material(
mat_def: Dictionary,
textures: PackedStringArray,
@@ -342,6 +439,7 @@ static func _build_group_liquid(liquid: Dictionary) -> MeshInstance3D:
var mi := MeshInstance3D.new()
mi.mesh = mesh
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
return mi