337 lines
11 KiB
GDScript
337 lines
11 KiB
GDScript
extends RefCounted
|
|
class_name WowWMOMaterial
|
|
|
|
const WMOBLEND_OPAQUE := 0
|
|
const WMOBLEND_ALPHA_KEY := 1
|
|
const WMOBLEND_ALPHA := 2
|
|
const SHADER_VERSION := 3
|
|
|
|
static var _shader_cache: Dictionary = {}
|
|
|
|
|
|
static func clear_cache() -> void:
|
|
_shader_cache.clear()
|
|
|
|
|
|
static func _ensure_wow_shader_globals() -> void:
|
|
pass
|
|
|
|
|
|
static func build(
|
|
texture: Texture2D,
|
|
texture2: Texture2D,
|
|
material_flags: int,
|
|
shader_id: int,
|
|
blend_mode: int,
|
|
texture_path: String = "",
|
|
texture2_path: String = "",
|
|
diffuse_color: Color = Color.WHITE,
|
|
emissive_color: Color = Color.BLACK,
|
|
secondary_color: Color = Color.WHITE,
|
|
texture3: Texture2D = null,
|
|
texture3_path: String = "") -> ShaderMaterial:
|
|
|
|
var mode := _blend_shader_mode(blend_mode)
|
|
var effective_diffuse := diffuse_color
|
|
if maxf(maxf(effective_diffuse.r, effective_diffuse.g), effective_diffuse.b) <= 0.001:
|
|
effective_diffuse = Color.WHITE
|
|
|
|
_ensure_wow_shader_globals()
|
|
var mat := ShaderMaterial.new()
|
|
mat.resource_name = texture_path.get_file().get_basename()
|
|
var pixel_shader := _wmo_pixel_shader_id(shader_id)
|
|
mat.shader = _get_shader(mode, texture2 != null, texture3 != null)
|
|
mat.render_priority = 0 if blend_mode <= WMOBLEND_ALPHA_KEY else 1
|
|
mat.set_shader_parameter("albedo_tex", texture)
|
|
mat.set_shader_parameter("detail_tex", texture2)
|
|
mat.set_shader_parameter("extra_tex", texture3)
|
|
mat.set_shader_parameter("use_texture", texture != null)
|
|
mat.set_shader_parameter("use_detail_texture", texture2 != null)
|
|
mat.set_shader_parameter("use_extra_texture", texture3 != null)
|
|
mat.set_shader_parameter("pixel_shader", pixel_shader)
|
|
mat.set_shader_parameter("alpha_ref", _alpha_ref(blend_mode, pixel_shader))
|
|
mat.set_shader_parameter("alpha_output_strength", _alpha_output_strength(blend_mode, pixel_shader))
|
|
mat.set_shader_parameter("vertex_color_strength", 0.72)
|
|
mat.set_shader_parameter("ambient_floor", 0.22)
|
|
mat.set_shader_parameter("diffuse_color", effective_diffuse)
|
|
mat.set_shader_parameter("emissive_color", emissive_color)
|
|
mat.set_shader_parameter("material_emissive_strength", _material_emissive_strength(material_flags, emissive_color, texture_path))
|
|
mat.set_shader_parameter("secondary_color", _effective_secondary_color(secondary_color))
|
|
mat.set_shader_parameter("detail_strength", 1.0 if texture2 != null else 0.0)
|
|
mat.set_meta("texture0_path", texture_path)
|
|
mat.set_meta("texture1_path", texture2_path)
|
|
mat.set_meta("texture2_path", texture3_path)
|
|
mat.set_meta("wow_flags", material_flags)
|
|
mat.set_meta("wow_shader", shader_id)
|
|
mat.set_meta("wow_wmo_pixel_shader", pixel_shader)
|
|
mat.set_meta("wow_blend_mode", blend_mode)
|
|
return mat
|
|
|
|
|
|
static func _wmo_pixel_shader_id(shader_id: int) -> int:
|
|
match shader_id:
|
|
0:
|
|
return 0 # Diffuse
|
|
1:
|
|
return 1 # Specular
|
|
2:
|
|
return 2 # Metal
|
|
3:
|
|
return 3 # Env
|
|
4:
|
|
return 4 # Opaque
|
|
5:
|
|
return 5 # EnvMetal
|
|
6:
|
|
return 6 # TwoLayerDiffuse
|
|
7:
|
|
return 7 # TwoLayerEnvMetal
|
|
8:
|
|
return 8 # TwoLayerTerrain
|
|
9:
|
|
return 9 # DiffuseEmissive
|
|
11:
|
|
return 10 # MaskedEnvMetal
|
|
12:
|
|
return 11 # EnvMetalEmissive
|
|
13:
|
|
return 12 # TwoLayerDiffuseOpaque
|
|
15:
|
|
return 13 # TwoLayerDiffuseEmissive
|
|
16:
|
|
return 0 # DiffuseTerrain
|
|
17:
|
|
return 14 # AdditiveMaskedEnvMetal
|
|
18:
|
|
return 15 # TwoLayerDiffuseMod2x
|
|
19:
|
|
return 16 # TwoLayerDiffuseMod2xNA
|
|
20:
|
|
return 17 # TwoLayerDiffuseAlpha
|
|
21:
|
|
return 18 # Lod
|
|
22:
|
|
return 19 # Parallax
|
|
23:
|
|
return 20 # DF shader
|
|
_:
|
|
return 0
|
|
|
|
|
|
static func _shader_alpha_is_opacity(pixel_shader: int) -> bool:
|
|
match pixel_shader:
|
|
0, 1, 2, 4, 18, 19:
|
|
return true
|
|
_:
|
|
return false
|
|
|
|
|
|
static func _alpha_ref(blend_mode: int, pixel_shader: int) -> float:
|
|
if blend_mode > WMOBLEND_OPAQUE:
|
|
return 0.501960814
|
|
return 0.0
|
|
|
|
|
|
static func _alpha_output_strength(blend_mode: int, pixel_shader: int) -> float:
|
|
if blend_mode == WMOBLEND_ALPHA and _shader_alpha_is_opacity(pixel_shader):
|
|
return 1.0
|
|
return 0.0
|
|
|
|
|
|
static func _effective_secondary_color(color: Color) -> Color:
|
|
if maxf(maxf(color.r, color.g), color.b) <= 0.001:
|
|
return Color.WHITE
|
|
return color
|
|
|
|
|
|
static func _material_emissive_strength(material_flags: int, color: Color, texture_path: String) -> float:
|
|
var color_strength := maxf(maxf(color.r, color.g), color.b) * color.a
|
|
if color_strength <= 0.001:
|
|
return 0.0
|
|
var strength := 0.12
|
|
if (material_flags & 0x10) != 0:
|
|
strength = 0.26
|
|
var lower_path := texture_path.to_lower()
|
|
if lower_path.contains("window") or lower_path.contains("_wnd_") or lower_path.contains("wnd_"):
|
|
strength = minf(strength, 0.18)
|
|
return strength
|
|
|
|
|
|
static func _blend_shader_mode(blend_mode: int) -> String:
|
|
match blend_mode:
|
|
WMOBLEND_ALPHA_KEY:
|
|
return "cutout"
|
|
WMOBLEND_ALPHA:
|
|
return "alpha"
|
|
_:
|
|
return "opaque"
|
|
|
|
|
|
static func _get_shader(mode: String, has_detail: bool, has_extra: bool) -> Shader:
|
|
var key := "v=%d|%s|detail=%s|extra=%s" % [SHADER_VERSION, mode, str(has_detail), str(has_extra)]
|
|
if _shader_cache.has(key):
|
|
return _shader_cache[key]
|
|
|
|
var shader := Shader.new()
|
|
shader.code = _shader_code(mode, has_detail, has_extra)
|
|
_shader_cache[key] = shader
|
|
return shader
|
|
|
|
|
|
static func _shader_code(mode: String, has_detail: bool, has_extra: bool) -> String:
|
|
var render_modes: Array[String] = ["blend_mix", "cull_disabled", "unshaded"]
|
|
if mode == "opaque" or mode == "cutout":
|
|
render_modes.append("depth_draw_opaque")
|
|
else:
|
|
render_modes.append("depth_prepass_alpha")
|
|
|
|
return """
|
|
shader_type spatial;
|
|
render_mode %s;
|
|
|
|
uniform sampler2D albedo_tex : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
|
uniform sampler2D detail_tex : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
|
uniform sampler2D extra_tex : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
|
uniform bool use_texture = false;
|
|
uniform bool use_detail_texture = false;
|
|
uniform bool use_extra_texture = false;
|
|
uniform int pixel_shader = 0;
|
|
uniform float alpha_ref = 0.0;
|
|
uniform float alpha_output_strength = 0.0;
|
|
uniform float vertex_color_strength = 0.88;
|
|
uniform float ambient_floor = 0.20;
|
|
uniform vec4 diffuse_color : source_color = vec4(1.0);
|
|
uniform vec4 emissive_color : source_color = vec4(0.0);
|
|
uniform float material_emissive_strength = 0.0;
|
|
uniform vec4 secondary_color : source_color = vec4(1.0);
|
|
uniform float detail_strength = 0.0;
|
|
|
|
global uniform vec4 wow_ambient_color;
|
|
global uniform vec4 wow_light_color;
|
|
global uniform vec3 wow_light_dir;
|
|
global uniform vec4 wow_fog_color;
|
|
global uniform vec2 wow_fog_range;
|
|
global uniform float wow_fog_density;
|
|
global uniform float wow_sun_elevation;
|
|
|
|
varying vec3 world_normal;
|
|
varying vec3 world_pos;
|
|
varying vec3 view_pos;
|
|
|
|
void vertex() {
|
|
world_normal = normalize(MODEL_NORMAL_MATRIX * NORMAL);
|
|
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
view_pos = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
}
|
|
|
|
vec3 apply_wow_fog(vec3 color) {
|
|
float dist = length(view_pos);
|
|
float range_fog = smoothstep(wow_fog_range.x, wow_fog_range.y, dist);
|
|
float fog_amount = range_fog * clamp(wow_fog_density, 0.0, 0.55);
|
|
return mix(color, wow_fog_color.rgb, fog_amount);
|
|
}
|
|
|
|
float color_luma(vec3 color) {
|
|
return dot(color, vec3(0.2126, 0.7152, 0.0722));
|
|
}
|
|
|
|
vec3 textured_material_tint(vec3 color) {
|
|
float luma = max(color_luma(color), 0.001);
|
|
vec3 hue = clamp(color / luma, vec3(0.58), vec3(1.20));
|
|
return mix(vec3(1.0), hue, 0.28);
|
|
}
|
|
|
|
vec3 untextured_material_color(vec3 color) {
|
|
float luma = color_luma(color);
|
|
vec3 muted = mix(vec3(luma), color, 0.42);
|
|
return clamp(muted * 0.82, vec3(0.08), vec3(0.72));
|
|
}
|
|
|
|
void resolve_wmo_pixel(vec4 tex1, vec4 tex2, vec4 tex3, bool has_detail, bool has_extra, out vec3 diffuse, out vec3 emissive) {
|
|
float layer_weight = clamp(secondary_color.a, 0.0, 1.0);
|
|
diffuse = tex1.rgb;
|
|
emissive = vec3(0.0);
|
|
if (!has_detail) {
|
|
return;
|
|
}
|
|
|
|
if (pixel_shader == 3) {
|
|
emissive = tex2.rgb * tex1.a;
|
|
} else if (pixel_shader == 5) {
|
|
emissive = (tex1.rgb * tex1.a) * tex2.rgb;
|
|
} else if (pixel_shader == 6) {
|
|
vec3 layer2 = mix(tex1.rgb, tex2.rgb, tex2.a);
|
|
diffuse = mix(layer2, tex1.rgb, layer_weight);
|
|
} else if (pixel_shader == 8 || pixel_shader == 12) {
|
|
diffuse = mix(tex2.rgb, tex1.rgb, layer_weight);
|
|
} else if (pixel_shader == 9) {
|
|
emissive = tex2.rgb * tex2.a * layer_weight;
|
|
} else if (pixel_shader == 10 && has_extra) {
|
|
float mix_factor = clamp(tex3.a * layer_weight, 0.0, 1.0);
|
|
diffuse = mix(mix(tex1.rgb * tex2.rgb * 2.0, tex3.rgb, mix_factor), tex1.rgb, tex1.a);
|
|
} else if (pixel_shader == 11 && has_extra) {
|
|
emissive = ((tex1.rgb * tex1.a) * tex2.rgb) + ((tex3.rgb * tex3.a) * layer_weight);
|
|
} else if (pixel_shader == 13) {
|
|
vec3 layer = tex2.rgb * (1.0 - tex2.a);
|
|
diffuse = mix(layer, tex1.rgb, layer_weight);
|
|
emissive = (tex2.rgb * tex2.a) * (1.0 - layer_weight);
|
|
} else if (pixel_shader == 14 && has_extra) {
|
|
diffuse = mix(
|
|
(tex1.rgb * tex2.rgb * 2.0) + (tex3.rgb * clamp(tex3.a * layer_weight, 0.0, 1.0)),
|
|
tex1.rgb,
|
|
tex1.a
|
|
);
|
|
} else if (pixel_shader == 15) {
|
|
vec3 layer2 = mix(tex1.rgb, tex2.rgb, tex2.a);
|
|
vec3 layer3 = mix(layer2, tex1.rgb, layer_weight);
|
|
diffuse = has_extra ? layer3 * tex3.rgb * 2.0 : layer3;
|
|
} else if (pixel_shader == 16) {
|
|
vec3 mod_layer = tex1.rgb * tex2.rgb * 2.0;
|
|
diffuse = mix(tex1.rgb, mod_layer, layer_weight);
|
|
} else if (pixel_shader == 17) {
|
|
vec3 layer2 = mix(tex1.rgb, tex2.rgb, tex2.a);
|
|
vec3 layer3 = mix(layer2, tex1.rgb, has_extra ? tex3.a : layer_weight);
|
|
diffuse = has_extra ? layer3 * tex3.rgb * 2.0 : layer3;
|
|
}
|
|
}
|
|
|
|
void fragment() {
|
|
vec4 tex1 = use_texture ? texture(albedo_tex, UV) : vec4(1.0);
|
|
vec4 tex2 = use_detail_texture ? texture(detail_tex, UV) : vec4(1.0);
|
|
vec4 tex3 = use_extra_texture ? texture(extra_tex, UV) : vec4(1.0);
|
|
if (alpha_ref > 0.0 && tex1.a < alpha_ref) {
|
|
discard;
|
|
}
|
|
|
|
vec3 mat_diffuse;
|
|
vec3 shader_emissive;
|
|
resolve_wmo_pixel(tex1, tex2, tex3, use_detail_texture, use_extra_texture, mat_diffuse, shader_emissive);
|
|
|
|
vec3 baked = max(COLOR.rgb, vec3(ambient_floor));
|
|
vec3 baked_lit = mix(vec3(1.0), baked, clamp(vertex_color_strength, 0.0, 1.0));
|
|
vec3 n = normalize(world_normal);
|
|
if (!FRONT_FACING) {
|
|
n = -n;
|
|
}
|
|
float ndl = max(dot(n, normalize(wow_light_dir)), 0.0);
|
|
vec3 zone_ambient = mix(vec3(0.78), max(wow_ambient_color.rgb, vec3(0.08)), 0.16);
|
|
vec3 zone_light = mix(vec3(1.0), wow_light_color.rgb, 0.10);
|
|
float day_strength = mix(0.48, 0.92, clamp(wow_sun_elevation, 0.0, 1.0));
|
|
vec3 lit = baked_lit * (zone_ambient * 0.86 + zone_light * ndl * 0.24 * day_strength);
|
|
lit = clamp(lit, vec3(0.46), vec3(1.05));
|
|
vec3 material_color = untextured_material_color(diffuse_color.rgb);
|
|
if (use_texture) {
|
|
material_color = mat_diffuse * textured_material_tint(diffuse_color.rgb);
|
|
}
|
|
float emissive_mask = smoothstep(0.35, 0.92, color_luma(mat_diffuse));
|
|
vec3 material_emissive = mat_diffuse * emissive_color.rgb * emissive_color.a * material_emissive_strength * emissive_mask;
|
|
vec3 emissive = shader_emissive * 0.35 + material_emissive;
|
|
vec3 color = clamp(material_color * lit + emissive, vec3(0.0), vec3(1.0));
|
|
ALBEDO = apply_wow_fog(color);
|
|
%s
|
|
}
|
|
""" % [
|
|
", ".join(render_modes),
|
|
"" if mode == "opaque" or mode == "cutout" else "\tALPHA = mix(diffuse_color.a, tex1.a * diffuse_color.a, clamp(alpha_output_strength, 0.0, 1.0));",
|
|
]
|