оптимизация рендера
This commit is contained in:
@@ -11,6 +11,7 @@ const USE_GEOMETRY_NORMALS := true
|
||||
static var _terrain_shader: Shader
|
||||
static var _single_texture_shader: Shader
|
||||
static var _empty_alpha_texture: Texture2D
|
||||
static var _native_adt_baker: Object
|
||||
static var _fallback_material: StandardMaterial3D
|
||||
static var _single_texture_material_cache: Dictionary = {}
|
||||
static var _baked_texture_material_cache: Dictionary = {}
|
||||
@@ -109,6 +110,24 @@ static func build_baked_tile_render_payload(
|
||||
return _build_baked_tile_payload(data, image_cache, extracted_dir, lod, texture_size)
|
||||
|
||||
|
||||
static func build_baked_tile_render_payload_pair(
|
||||
data: Dictionary,
|
||||
image_cache: Dictionary,
|
||||
extracted_dir: String = "",
|
||||
full_lod: int = 0,
|
||||
full_texture_size: int = 2048,
|
||||
coarse_lod: int = 3,
|
||||
coarse_texture_size: int = 512) -> Dictionary:
|
||||
return _build_baked_tile_payload_pair(
|
||||
data,
|
||||
image_cache,
|
||||
extracted_dir,
|
||||
full_lod,
|
||||
full_texture_size,
|
||||
coarse_lod,
|
||||
coarse_texture_size)
|
||||
|
||||
|
||||
static func get_tile_origin(data: Dictionary) -> Vector3:
|
||||
var found := false
|
||||
var min_x := 0.0
|
||||
@@ -441,6 +460,48 @@ static func _build_baked_tile_payload(
|
||||
lod: int,
|
||||
texture_size: int) -> Dictionary:
|
||||
|
||||
var payload := _build_baked_tile_geometry_payload(data, lod)
|
||||
if payload.is_empty():
|
||||
return {}
|
||||
|
||||
var baked_image := _build_tile_baked_albedo(data, image_cache, extracted_dir, texture_size)
|
||||
_apply_baked_albedo_material(payload.get("mesh", null), baked_image)
|
||||
return payload
|
||||
|
||||
|
||||
static func _build_baked_tile_payload_pair(
|
||||
data: Dictionary,
|
||||
image_cache: Dictionary,
|
||||
extracted_dir: String,
|
||||
full_lod: int,
|
||||
full_texture_size: int,
|
||||
coarse_lod: int,
|
||||
coarse_texture_size: int) -> Dictionary:
|
||||
|
||||
var full_payload := _build_baked_tile_geometry_payload(data, full_lod)
|
||||
if full_payload.is_empty():
|
||||
return {}
|
||||
|
||||
var coarse_payload := _build_baked_tile_geometry_payload(data, coarse_lod)
|
||||
var full_image := _build_tile_baked_albedo(data, image_cache, extracted_dir, full_texture_size)
|
||||
var coarse_image: Image = null
|
||||
if full_image != null:
|
||||
coarse_image = full_image.duplicate()
|
||||
var coarse_size := _get_baked_albedo_size(coarse_texture_size)
|
||||
if coarse_image.get_width() != coarse_size or coarse_image.get_height() != coarse_size:
|
||||
coarse_image.resize(coarse_size, coarse_size, Image.INTERPOLATE_BILINEAR)
|
||||
|
||||
_apply_baked_albedo_material(full_payload.get("mesh", null), full_image)
|
||||
if not coarse_payload.is_empty():
|
||||
_apply_baked_albedo_material(coarse_payload.get("mesh", null), coarse_image)
|
||||
|
||||
return {
|
||||
"full": full_payload,
|
||||
"coarse": coarse_payload,
|
||||
}
|
||||
|
||||
|
||||
static func _build_baked_tile_geometry_payload(data: Dictionary, lod: int) -> Dictionary:
|
||||
var tile_origin := get_tile_origin(data)
|
||||
var verts := PackedVector3Array()
|
||||
var nrms := PackedVector3Array()
|
||||
@@ -491,7 +552,16 @@ static func _build_baked_tile_payload(
|
||||
var mesh := ArrayMesh.new()
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
|
||||
var baked_image := _build_tile_baked_albedo(data, image_cache, extracted_dir, texture_size)
|
||||
return {
|
||||
"mesh": mesh,
|
||||
"position": Vector3.ZERO,
|
||||
"name": "TileLOD%d" % lod,
|
||||
}
|
||||
|
||||
|
||||
static func _apply_baked_albedo_material(mesh: Mesh, baked_image: Image) -> void:
|
||||
if mesh == null:
|
||||
return
|
||||
if baked_image:
|
||||
baked_image.generate_mipmaps()
|
||||
var baked_texture := ImageTexture.create_from_image(baked_image)
|
||||
@@ -499,12 +569,6 @@ static func _build_baked_tile_payload(
|
||||
else:
|
||||
mesh.surface_set_material(0, _build_fallback_material())
|
||||
|
||||
return {
|
||||
"mesh": mesh,
|
||||
"position": Vector3.ZERO,
|
||||
"name": "TileLOD%d" % lod,
|
||||
}
|
||||
|
||||
|
||||
static func _build_mesh_instance_from_payload(payload: Dictionary) -> MeshInstance3D:
|
||||
if payload.is_empty():
|
||||
@@ -881,7 +945,15 @@ static func _build_tile_baked_albedo(
|
||||
extracted_dir: String,
|
||||
texture_size: int) -> Image:
|
||||
|
||||
var chunk_pixels := maxi(8, int(ceil(float(maxi(texture_size, 16)) / 16.0)))
|
||||
var native_image := _build_tile_baked_albedo_native(
|
||||
data,
|
||||
image_cache,
|
||||
extracted_dir,
|
||||
texture_size)
|
||||
if native_image != null:
|
||||
return native_image
|
||||
|
||||
var chunk_pixels := int(_get_baked_albedo_size(texture_size) / 16)
|
||||
var size := chunk_pixels * 16
|
||||
var image := Image.create(size, size, false, Image.FORMAT_RGB8)
|
||||
image.fill(Color(0.4, 0.55, 0.3))
|
||||
@@ -914,6 +986,30 @@ static func _build_tile_baked_albedo(
|
||||
return image
|
||||
|
||||
|
||||
static func _build_tile_baked_albedo_native(
|
||||
data: Dictionary,
|
||||
image_cache: Dictionary,
|
||||
extracted_dir: String,
|
||||
texture_size: int) -> Image:
|
||||
|
||||
if not ClassDB.class_exists("ADTBaker"):
|
||||
return null
|
||||
if _native_adt_baker == null:
|
||||
_native_adt_baker = ClassDB.instantiate("ADTBaker")
|
||||
if _native_adt_baker == null:
|
||||
return null
|
||||
return _native_adt_baker.call(
|
||||
"bake_tile_albedo",
|
||||
data,
|
||||
image_cache,
|
||||
extracted_dir,
|
||||
texture_size) as Image
|
||||
|
||||
|
||||
static func _get_baked_albedo_size(texture_size: int) -> int:
|
||||
return maxi(8, int(ceil(float(maxi(texture_size, 16)) / 16.0))) * 16
|
||||
|
||||
|
||||
static func _sample_chunk_baked_albedo(
|
||||
chunk: Dictionary,
|
||||
tex_names: PackedStringArray,
|
||||
@@ -1110,6 +1206,7 @@ static func _load_texture(
|
||||
cache[blp_path] = null
|
||||
return null
|
||||
|
||||
img.generate_mipmaps()
|
||||
var tex := ImageTexture.create_from_image(img)
|
||||
cache[blp_path] = tex
|
||||
return tex
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user