From 2f3eaa965cd4e89145ff1a88fc3a3886dc129a0b Mon Sep 17 00:00:00 2001 From: Ricardo Buring Date: Fri, 26 Sep 2025 12:41:34 +0200 Subject: [PATCH] OBJ importer: Support bump multiplier (normal scale) This improves compatibility with OBJ/MTL files found in the wild. --- editor/import/3d/resource_importer_obj.cpp | 52 +++++++++++++++------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index d9b1c873095..5abd00d6942 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -49,18 +49,17 @@ static Error _parse_material_library(const String &p_path, HashMapget_line().strip_edges(); if (l.begins_with("newmtl ")) { - //vertex - + // Start of a new material. current_name = l.replace("newmtl", "").strip_edges(); current.instantiate(); current->set_name(current_name); material_map[current_name] = current; } else if (l.begins_with("Ka ")) { - //uv + // Ambient color. WARN_PRINT("OBJ: Ambient light for material '" + current_name + "' is ignored in PBR"); } else if (l.begins_with("Kd ")) { - //normal + // Diffuse color. ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT); Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA); @@ -70,7 +69,7 @@ static Error _parse_material_library(const String &p_path, HashMapset_albedo(c); } else if (l.begins_with("Ks ")) { - //normal + // Specular color. ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT); Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA); @@ -80,14 +79,14 @@ static Error _parse_material_library(const String &p_path, HashMapset_metallic(metalness); } else if (l.begins_with("Ns ")) { - //normal + // Specular exponent. ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT); Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA); float s = v[1].to_float(); current->set_metallic((1000.0 - s) / 1000.0); } else if (l.begins_with("d ")) { - //normal + // Dissolve (1.0 is fully opaque, 0.0 is completely transparent). ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT); Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA); @@ -99,7 +98,7 @@ static Error _parse_material_library(const String &p_path, HashMapset_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); } } else if (l.begins_with("Tr ")) { - //normal + // Transparency (1.0 is completely transparent, 0.0 is fully opaque). ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT); Vector v = l.split(" ", false); ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA); @@ -112,11 +111,11 @@ static Error _parse_material_library(const String &p_path, HashMappush_back(path); } - } else if (l.begins_with("map_bump ")) { - //normal + } else if (l.begins_with("map_bump ") || l.begins_with("map_Bump ")) { + // Bump texture map. ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT); - String p = l.replace("map_bump", "").replace_char('\\', '/').strip_edges(); - String path = base_path.path_join(p); + l = l.begins_with("map_bump ") ? l.trim_prefix("map_bump ") : l.trim_prefix("map_Bump "); + l = l.strip_edges(); + + // Read path and optional bump multiplier. + String p; + float bm = 1.0; + int bm_pos = l.find("-bm "); + if (bm_pos >= 0) { + int bm_start = bm_pos + 4; + int bm_end = l.find_char(' ', bm_start); + if (bm_end >= 0) { + bm = l.substr(bm_start, bm_end - bm_start).to_float(); + p = l.substr(bm_end + 1); + } else { // Bump multiplier ends at end of line. + bm = l.substr(bm_start).to_float(); + p = l.substr(0, bm_pos); + } + } else { + p = l; + } + + String path = base_path.path_join(p.replace_char('\\', '/').strip_edges()); Ref texture = ResourceLoader::load(path); if (texture.is_valid()) { current->set_feature(StandardMaterial3D::FEATURE_NORMAL_MAPPING, true); current->set_texture(StandardMaterial3D::TEXTURE_NORMAL, texture); + current->set_normal_scale(bm); } else if (r_missing_deps) { r_missing_deps->push_back(path); }