скайбоксы, рендер воды, документация

This commit is contained in:
2026-06-27 18:33:55 +04:00
parent 579b9e10c9
commit b051012920
14 changed files with 1775 additions and 101 deletions
+25 -41
View File
@@ -2,6 +2,8 @@
extends RefCounted
class_name ADTBuilder
const WOW_LIQUID_MATERIAL := preload("res://addons/mpq_extractor/loaders/wow_liquid_material.gd")
const TILE_SIZE := 533.33333
const CHUNK_SIZE := 33.33333
const UNIT_SIZE := CHUNK_SIZE / 8.0
@@ -18,7 +20,6 @@ static var _single_texture_material_cache: Dictionary = {}
static var _baked_texture_material_cache: Dictionary = {}
static var _alpha_texture_cache: Dictionary = {}
static var _layered_material_cache: Dictionary = {}
static var _liquid_material_cache: Dictionary = {}
## Build a Node3D with 256 terrain chunk meshes.
## `extracted_dir` must be an absolute path to the extracted/ folder so BLPs can be loaded.
@@ -780,6 +781,7 @@ static func _build_tile_water_root(data: Dictionary, origin_offset: Vector3) ->
var mi := MeshInstance3D.new()
mi.mesh = mesh
mi.name = "Liquid_%d" % int(liquid_id)
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
root.add_child(mi)
return root if root.get_child_count() > 0 else null
@@ -817,6 +819,10 @@ static func _append_liquid_geometry(
var y10 := liquid_heights[z * 9 + x + 1] - origin_offset.y
var y01 := liquid_heights[(z + 1) * 9 + x] - origin_offset.y
var y11 := liquid_heights[(z + 1) * 9 + x + 1] - origin_offset.y
var u0 := x0 / TILE_SIZE
var u1 := x1 / TILE_SIZE
var v0 := z0 / TILE_SIZE
var v1 := z1 / TILE_SIZE
verts.append(Vector3(x0, y00, z0))
verts.append(Vector3(x0, y01, z1))
@@ -828,10 +834,10 @@ static func _append_liquid_geometry(
nrms.append(Vector3.UP)
nrms.append(Vector3.UP)
uvs_arr.append(Vector2(float(x) / 8.0, float(z) / 8.0))
uvs_arr.append(Vector2(float(x) / 8.0, float(z + 1) / 8.0))
uvs_arr.append(Vector2(float(x + 1) / 8.0, float(z + 1) / 8.0))
uvs_arr.append(Vector2(float(x + 1) / 8.0, float(z) / 8.0))
uvs_arr.append(Vector2(u0, v0))
uvs_arr.append(Vector2(u0, v1))
uvs_arr.append(Vector2(u1, v1))
uvs_arr.append(Vector2(u1, v0))
indices.append(base)
indices.append(base + 1)
@@ -912,6 +918,7 @@ static func _build_liquid_mesh(
var mi := MeshInstance3D.new()
mi.mesh = mesh
mi.position = chunk_origin - origin_offset
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
mi.name = "Liquid_%d_%d_%d" % [
chunk.get("index_x", 0),
chunk.get("index_y", 0),
@@ -938,33 +945,8 @@ static func _build_chunk_material(
return _build_fallback_material()
static func _build_liquid_material(liquid_id: int) -> StandardMaterial3D:
if _liquid_material_cache.has(liquid_id):
return _liquid_material_cache[liquid_id]
var color := _get_liquid_color(liquid_id)
var mat := StandardMaterial3D.new()
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.albedo_color = color
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
mat.roughness = 0.08
mat.metallic = 0.0
mat.emission_enabled = true
mat.emission = Color(color.r, color.g, color.b) * 0.08
_liquid_material_cache[liquid_id] = mat
return mat
static func _get_liquid_color(liquid_id: int) -> Color:
match liquid_id:
2, 14:
return Color(0.08, 0.20, 0.34, 0.62)
3, 15, 19:
return Color(0.75, 0.28, 0.03, 0.72)
4, 20:
return Color(0.24, 0.45, 0.14, 0.68)
_:
return Color(0.12, 0.33, 0.50, 0.58)
static func _build_liquid_material(liquid_id: int) -> Material:
return WOW_LIQUID_MATERIAL.build(liquid_id)
static func _build_terrain_material(
@@ -1287,18 +1269,20 @@ uniform float ambient = 0.62;
uniform float diffuse = 0.38;
uniform float hemi = 0.12;
vec3 sample_layer(float layer, vec2 local_uv) {
return texture(terrain_tex, vec3(local_uv * uv_scale, layer), mip_bias).rgb;
vec3 sample_layer(float layer, vec2 continuous_chunk_uv) {
return texture(terrain_tex, vec3(continuous_chunk_uv * uv_scale, layer), mip_bias).rgb;
}
void fragment() {
vec2 tile_uv = clamp(UV, vec2(0.0), vec2(0.999999));
vec2 chunk_f = floor(tile_uv * 16.0);
vec2 local_uv = fract(tile_uv * 16.0);
vec2 chunk_uv = tile_uv * 16.0;
vec2 chunk_f = floor(chunk_uv);
vec2 local_uv = chunk_uv - chunk_f;
vec4 packed_ids = COLOR;
vec4 layers = floor(packed_ids * 255.0 + vec4(0.5));
vec3 alpha = texture(alpha_atlas, (chunk_f * 64.0 + local_uv * 64.0 + vec2(0.5)) / 1024.0).rgb;
vec2 alpha_px = clamp(local_uv * 64.0, vec2(0.5), vec2(63.5));
vec3 alpha = texture(alpha_atlas, (chunk_f * 64.0 + alpha_px) / 1024.0).rgb;
float w1 = alpha.r;
float w2 = alpha.g;
@@ -1311,10 +1295,10 @@ void fragment() {
w3 /= sum;
vec3 albedo =
sample_layer(layers.x, local_uv) * w0 +
sample_layer(layers.y, local_uv) * w1 +
sample_layer(layers.z, local_uv) * w2 +
sample_layer(layers.w, local_uv) * w3;
sample_layer(layers.x, chunk_uv) * w0 +
sample_layer(layers.y, chunk_uv) * w1 +
sample_layer(layers.z, chunk_uv) * w2 +
sample_layer(layers.w, chunk_uv) * w3;
vec3 n = normalize(NORMAL);
if (!FRONT_FACING) {
+75 -7
View File
@@ -21,8 +21,11 @@ static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
return root
var textures: PackedStringArray = data.get("textures", PackedStringArray())
var texture_types: PackedInt32Array = data.get("texture_types", PackedInt32Array())
var texture_flags: PackedInt32Array = data.get("texture_flags", PackedInt32Array())
var materials: Array = data.get("materials", [])
var tex_combos: PackedInt32Array = data.get("texture_combos", PackedInt32Array())
var model_path: String = str(data.get("model_path", ""))
var mesh := ArrayMesh.new()
@@ -55,7 +58,17 @@ static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
var surf_idx := mesh.get_surface_count() - 1
var mat_def: Dictionary = materials[mat_id] if mat_id >= 0 and mat_id < materials.size() else {}
mesh.surface_set_material(surf_idx, _build_material(mat_def, tc_idx, textures, tex_combos, extracted_dir))
mesh.surface_set_material(
surf_idx,
_build_material(
mat_def,
tc_idx,
textures,
texture_types,
texture_flags,
tex_combos,
extracted_dir,
model_path))
if mesh.get_surface_count() == 0:
return root
@@ -71,36 +84,91 @@ static func _build_material(
mat_def: Dictionary,
texture_combo_index: int,
textures: PackedStringArray,
texture_types: PackedInt32Array,
texture_flags: PackedInt32Array,
tex_combos: PackedInt32Array,
extracted_dir: String) -> StandardMaterial3D:
extracted_dir: String,
model_path: String) -> StandardMaterial3D:
var mat := StandardMaterial3D.new()
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
var flags: int = int(mat_def.get("flags", 0))
mat.cull_mode = BaseMaterial3D.CULL_DISABLED if (flags & 0x04) != 0 else BaseMaterial3D.CULL_BACK
mat.roughness = 0.85
mat.metallic = 0.0
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED if (flags & 0x01) != 0 else BaseMaterial3D.SHADING_MODE_PER_PIXEL
mat.specular_mode = BaseMaterial3D.SPECULAR_DISABLED
mat.vertex_color_use_as_albedo = false
mat.set_flag(BaseMaterial3D.FLAG_USE_TEXTURE_REPEAT, false)
var blend_mode: int = mat_def.get("blend_mode", 0)
match blend_mode:
0: mat.transparency = BaseMaterial3D.TRANSPARENCY_DISABLED
1: mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
_: mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
0:
mat.transparency = BaseMaterial3D.TRANSPARENCY_DISABLED
1:
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
mat.alpha_scissor_threshold = 0.5
3, 4, 6:
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_OPAQUE_ONLY
_:
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_OPAQUE_ONLY
if texture_combo_index >= 0 and texture_combo_index < tex_combos.size():
var tex_idx: int = tex_combos[texture_combo_index]
if tex_idx >= 0 and tex_idx < textures.size():
var tex_path: String = str(textures[tex_idx]).replace("\\", "/")
var tex_type := int(texture_types[tex_idx]) if tex_idx < texture_types.size() else 0
var tex_flags := int(texture_flags[tex_idx]) if tex_idx < texture_flags.size() else 0
if tex_path.is_empty():
tex_path = _default_texture_path(model_path, tex_type)
if not tex_path.is_empty():
var tex := _load_texture(tex_path, extracted_dir)
if tex:
mat.albedo_texture = tex
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
mat.set_flag(BaseMaterial3D.FLAG_USE_TEXTURE_REPEAT, (tex_flags & 0x03) != 0)
return mat
static func _default_texture_path(model_path: String, texture_type: int) -> String:
var normalized := model_path.replace("\\", "/")
var rel_model := _relative_extracted_model_path(normalized)
var rel_lower := rel_model.to_lower()
if rel_lower.begins_with("item/objectcomponents/weapon/") and texture_type == 2:
var stem := rel_model.get_file().get_basename()
if stem.to_lower() == "axe_1h_horde_a_01":
return "Item/ObjectComponents/Weapon/Axe_1H_Horde_A_01Gray.blp"
if not stem.is_empty():
return "Item/ObjectComponents/Weapon/%s.blp" % stem
if rel_lower == "creature/wolf/wolf.m2" or rel_lower == "creature/wolf/wolf.mdx":
if texture_type == 11:
return "Creature/Wolf/WolfSkinCoyote.blp"
if texture_type == 12:
return "Creature/Wolf/WolfSkinCoyoteAlpha.blp"
if rel_lower == "creature/boar/boar.m2" or rel_lower == "creature/boar/boar.mdx":
if texture_type == 11:
return "Creature/Boar/BoarSkinIvory.blp"
if rel_lower == "creature/kobold/kobold.m2" or rel_lower == "creature/kobold/kobold.mdx":
if texture_type == 11:
return "Creature/Kobold/koboldskinAlbino.blp"
if rel_lower == "creature/murloc/murloc.m2" or rel_lower == "creature/murloc/murloc.mdx":
if texture_type == 11:
return "Creature/Murloc/SahauginskinBlue.blp"
return ""
static func _relative_extracted_model_path(path: String) -> String:
var marker := "/data/extracted/"
var lower := path.to_lower()
var marker_pos := lower.find(marker)
if marker_pos >= 0:
return path.substr(marker_pos + marker.length())
return path
static func _load_texture(rel_path: String, extracted_dir: String) -> Texture2D:
if rel_path.is_empty() or extracted_dir.is_empty():
return null
+18 -15
View File
@@ -6,6 +6,7 @@
class_name WMOBuilder
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
const WOW_LIQUID_MATERIAL := preload("res://addons/mpq_extractor/loaders/wow_liquid_material.gd")
const M2_RIGHT_YAW_OFFSET := PI * 0.5
const BUILD_OCCLUDERS := false
const OCCLUDER_MIN_TRIANGLES := 16
@@ -27,7 +28,7 @@ static func clear_caches() -> void:
_m2_prototype_cache.clear()
_m2_missing_cache.clear()
_texture_cache.clear()
_wmo_liquid_material_cache.clear()
WOW_LIQUID_MATERIAL.clear_cache()
# Returns a Node3D containing one MeshInstance3D per WMO group.
static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
@@ -339,6 +340,7 @@ static func _build_material(
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.specular_mode = BaseMaterial3D.SPECULAR_DISABLED
mat.vertex_color_use_as_albedo = false
mat.set_flag(BaseMaterial3D.FLAG_USE_TEXTURE_REPEAT, true)
var blend_mode: int = mat_def.get("blend_mode", 0)
match blend_mode:
@@ -356,6 +358,7 @@ static func _build_material(
if tex:
mat.albedo_texture = tex
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
mat.set_flag(BaseMaterial3D.FLAG_USE_TEXTURE_REPEAT, true)
mat.set_meta("wow_flags", mat_def.get("flags", 0))
mat.set_meta("wow_shader", mat_def.get("shader", 0))
@@ -363,7 +366,6 @@ static func _build_material(
return mat
static var _wmo_liquid_material_cache: Dictionary = {}
# WMO MLIQ → MeshInstance3D in WMO-local coordinates.
# Corner & vertex grid are in WMO-local (Z-up); convert to Godot Y-up using the
@@ -384,6 +386,8 @@ static func _build_group_liquid(liquid: Dictionary) -> MeshInstance3D:
return null
var verts := PackedVector3Array()
var nrms := PackedVector3Array()
var uvs := PackedVector2Array()
var indices := PackedInt32Array()
# Pre-compute Godot-space position for each grid vertex.
@@ -417,6 +421,14 @@ static func _build_group_liquid(liquid: Dictionary) -> MeshInstance3D:
verts.append(pos[i10])
verts.append(pos[i11])
verts.append(pos[i01])
nrms.append(Vector3.UP)
nrms.append(Vector3.UP)
nrms.append(Vector3.UP)
nrms.append(Vector3.UP)
uvs.append(Vector2(float(tx) / maxf(1.0, float(xtiles)), float(ty) / maxf(1.0, float(ytiles))))
uvs.append(Vector2(float(tx + 1) / maxf(1.0, float(xtiles)), float(ty) / maxf(1.0, float(ytiles))))
uvs.append(Vector2(float(tx + 1) / maxf(1.0, float(xtiles)), float(ty + 1) / maxf(1.0, float(ytiles))))
uvs.append(Vector2(float(tx) / maxf(1.0, float(xtiles)), float(ty + 1) / maxf(1.0, float(ytiles))))
# Two triangles, winding chosen to face up after the (-y, z, -x) flip.
indices.append(base + 0)
indices.append(base + 1)
@@ -431,6 +443,8 @@ static func _build_group_liquid(liquid: Dictionary) -> MeshInstance3D:
var arrays := []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = verts
arrays[Mesh.ARRAY_NORMAL] = nrms
arrays[Mesh.ARRAY_TEX_UV] = uvs
arrays[Mesh.ARRAY_INDEX] = indices
var mesh := ArrayMesh.new()
@@ -443,19 +457,8 @@ static func _build_group_liquid(liquid: Dictionary) -> MeshInstance3D:
return mi
static func _wmo_liquid_material(material_id: int) -> StandardMaterial3D:
if _wmo_liquid_material_cache.has(material_id):
return _wmo_liquid_material_cache[material_id]
var mat := StandardMaterial3D.new()
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
# Default: bluish water. Without DBC liquid_type lookup we can't pick magma/slime
# precisely yet — material_id from MLIQ is the WMO MOMT slot, not the DBC id.
mat.albedo_color = Color(0.2, 0.4, 0.7, 0.6)
_wmo_liquid_material_cache[material_id] = mat
return mat
static func _wmo_liquid_material(material_id: int) -> Material:
return WOW_LIQUID_MATERIAL.build_wmo(material_id)
static func _load_texture(rel_path: String, extracted_dir: String) -> Texture2D:
@@ -0,0 +1,177 @@
extends RefCounted
class_name WowLiquidMaterial
static var _shader: Shader
static var _cache: Dictionary = {}
static func clear_cache() -> void:
_cache.clear()
static func build(liquid_id: int) -> ShaderMaterial:
var key := "adt:%d" % liquid_id
if _cache.has(key):
return _cache[key]
var style := _style_for_liquid(liquid_id)
var mat := _create_material(style)
_cache[key] = mat
return mat
static func build_wmo(material_id: int) -> ShaderMaterial:
var key := "wmo:%d" % material_id
if _cache.has(key):
return _cache[key]
var style := {
"color": Color(0.10, 0.30, 0.48, 0.60),
"foam": Color(0.58, 0.80, 1.00, 0.22),
"wave_strength": 0.045,
"wave_scale": 0.095,
"wave_speed": 0.38,
"fresnel_power": 2.2,
"emission_strength": 0.025,
"magma_mode": 0.0,
}
var mat := _create_material(style)
_cache[key] = mat
return mat
static func _create_material(style: Dictionary) -> ShaderMaterial:
var mat := ShaderMaterial.new()
mat.shader = _get_shader()
mat.render_priority = 1
mat.set_shader_parameter("liquid_color", style["color"])
mat.set_shader_parameter("foam_color", style["foam"])
mat.set_shader_parameter("wave_strength", float(style["wave_strength"]))
mat.set_shader_parameter("wave_scale", float(style["wave_scale"]))
mat.set_shader_parameter("wave_speed", float(style["wave_speed"]))
mat.set_shader_parameter("alpha_base", (style["color"] as Color).a)
mat.set_shader_parameter("fresnel_power", float(style["fresnel_power"]))
mat.set_shader_parameter("emission_strength", float(style["emission_strength"]))
mat.set_shader_parameter("magma_mode", float(style["magma_mode"]))
return mat
static func _style_for_liquid(liquid_id: int) -> Dictionary:
match liquid_id:
3, 15, 19:
return {
"color": Color(0.78, 0.22, 0.03, 0.78),
"foam": Color(1.00, 0.66, 0.10, 0.38),
"wave_strength": 0.025,
"wave_scale": 0.12,
"wave_speed": 0.22,
"fresnel_power": 1.7,
"emission_strength": 0.42,
"magma_mode": 1.0,
}
4, 20:
return {
"color": Color(0.16, 0.38, 0.12, 0.66),
"foam": Color(0.50, 0.80, 0.32, 0.24),
"wave_strength": 0.035,
"wave_scale": 0.11,
"wave_speed": 0.28,
"fresnel_power": 2.0,
"emission_strength": 0.05,
"magma_mode": 0.0,
}
2, 14:
return {
"color": Color(0.05, 0.16, 0.30, 0.64),
"foam": Color(0.48, 0.72, 1.00, 0.20),
"wave_strength": 0.060,
"wave_scale": 0.075,
"wave_speed": 0.32,
"fresnel_power": 2.5,
"emission_strength": 0.025,
"magma_mode": 0.0,
}
_:
return {
"color": Color(0.08, 0.28, 0.46, 0.58),
"foam": Color(0.58, 0.82, 1.00, 0.24),
"wave_strength": 0.050,
"wave_scale": 0.085,
"wave_speed": 0.36,
"fresnel_power": 2.3,
"emission_strength": 0.025,
"magma_mode": 0.0,
}
static func _get_shader() -> Shader:
if _shader:
return _shader
_shader = Shader.new()
_shader.code = """
shader_type spatial;
render_mode blend_mix, cull_disabled, unshaded;
uniform vec4 liquid_color : source_color = vec4(0.08, 0.28, 0.46, 0.58);
uniform vec4 foam_color : source_color = vec4(0.58, 0.82, 1.00, 0.24);
uniform float wave_strength = 0.05;
uniform float wave_scale = 0.085;
uniform float wave_speed = 0.36;
uniform float alpha_base = 0.58;
uniform float fresnel_power = 2.3;
uniform float emission_strength = 0.025;
uniform float magma_mode = 0.0;
varying vec3 world_pos;
float hash21(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise21(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
float a = hash21(i);
float b = hash21(i + vec2(1.0, 0.0));
float c = hash21(i + vec2(0.0, 1.0));
float d = hash21(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
void vertex() {
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
float t = TIME * wave_speed;
float wave_a = sin(world_pos.x * wave_scale + t);
float wave_b = cos(world_pos.z * wave_scale * 1.37 - t * 1.21);
VERTEX.y += (wave_a + wave_b) * wave_strength;
}
void fragment() {
vec3 n = normalize(NORMAL);
vec3 v = normalize(VIEW);
float facing = clamp(abs(dot(n, v)), 0.0, 1.0);
float fresnel = pow(1.0 - facing, fresnel_power);
vec2 p = world_pos.xz * wave_scale;
float n1 = noise21(p * 0.70 + vec2(TIME * wave_speed, -TIME * wave_speed * 0.60));
float n2 = noise21(p * 1.80 + vec2(-TIME * wave_speed * 0.35, TIME * wave_speed * 0.45));
float ripple = smoothstep(0.52, 0.86, n1 * 0.65 + n2 * 0.35);
vec3 color = mix(liquid_color.rgb, foam_color.rgb, ripple * foam_color.a);
color += fresnel * foam_color.rgb * 0.35;
if (magma_mode > 0.5) {
float glow = smoothstep(0.45, 1.0, ripple + fresnel * 0.45);
color += vec3(1.0, 0.35, 0.05) * glow * 0.45;
EMISSION = color * max(emission_strength, 0.35);
} else {
EMISSION = color * emission_strength;
}
ALBEDO = color;
ALPHA = clamp(alpha_base + fresnel * 0.18 + ripple * 0.06, 0.0, 1.0);
}
"""
return _shader
@@ -0,0 +1 @@
uid://d10ykrmo5h0ih