From 63ba5bd3ec0e3844aabc949fa31daf6900cf7bad Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Sat, 23 Nov 2024 22:58:52 +0100 Subject: [PATCH] [WIP] Infer material from texture name --- scene/resources/material.cpp | 175 +++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index ecc1982aa5ef..ea0d7a728f3b 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -2836,6 +2836,181 @@ Ref BaseMaterial3D::get_material_for_2d(bool p_shaded, Transparency p_ return materials_for_2d[key]; } +// Values must be written in PascalCase. +// The file name is stripped from its extension, then each component (each word separated by `-`, `_` or `.`) +// is checked individually for a match. Each component will check for the original casing, +// convert to lowercase, then convert the first character to lowercase. This covers the PascalCase, +// camelCase and snake_case file naming conventions. +# +// The order of the keys is important, since we want some material maps to be checked before others (e.g. albedo takes +// priority over metallic, so that `metal_grate_albedo.png` is correctly detected as an albedo map). +# +// Websites used to determine common file names: +# +// - https://polyhaven.com/ +// - http://cgbookcase.com/ +// - https://ambientcg.com/ +Dictionary types = { + // We check for the presence of an ORM texture first when creating the material. + // If one is found, we'll create an ORMMaterial3D. Otherwise, we'll create a StandardMaterial3D. + "orm": PackedStringArray({ "ORM", "ARM" }), + + // Common PBR material texture types, standardized across engines. + "albedo": PackedStringArray({ "Albedo", "BaseColor", "BaseColour", "Base", "Color", "Colour", "Diffuse", "Diff", "C", "D" }), + "normal": PackedStringArray({ "Normal", "NormalGL", "NormalDX", "Local", "Norm", "Nor", "Nor_GL", "Nor_DX", "N" }), + "roughness": PackedStringArray({ "Roughness", "Rough", "R" }), // Not used in ORMMaterial3D creation. + "metallic": PackedStringArray({ "Metallic", "Metalness", "Metal", "M" }), // Not used in ORMMaterial3D creation. + "ao": PackedStringArray({ "AO", "AmbientOcclusion", "Ambient", "Occlusion", "A", "O" }), // Not used in ORMMaterial3D creation. + "emission": PackedStringArray({ "Emission", "Emissive", "Glow", "Luma", "E", "G" }), + "height": PackedStringArray({ "Height", "Displacement", "Disp", "H", "Z" }), + + // Less common and not as standardized across engines. + "rim": PackedStringArray({ "Rim" }), + "clearcoat": PackedStringArray({ "Clearcoat" }), + "anisotropy": PackedStringArray({ "Anisotropy", "Aniso", "Flowmap", "Flow" }), + "subsurf_scatter": PackedStringArray({ "Subsurface", "Subsurf", "Scattering", "Scatter", "SSS" }), + "subsurf_scatter_transmittance": PackedStringArray({ "Transmittance", "Transmission", "Transmissive", "Scatter", "SSS" }), + "backlight": PackedStringArray({ "BackLighting", "Backlight" }), + "refraction": PackedStringArray({ "Refraction", "Refract" }), + "detail_albedo": PackedStringArray({ "Detail" }), +}; + +Ref BaseMaterial3D::get_material_from_texture(const String &p_texture_path) const { + String path_type; + String found_suffix; + // Read from the last component to the first, so that names like `blue_metal_diff` + // are seen as an albedo (diffuse) texture instead of a metallic map. + PackedStringArray components = p_texture_path.get_basename().get_file().replace("-", "_").replace(" ", "_").replace(".", "_").split("_", false); + components.reverse(); + + for (const String &component : components) { + if (found_suffix.is_empty()) { + for (const String &type : types) { + for (const String &suffix : types[type]) { + // Check PascalCase, snake_case and camelCase. + for (const String &suffix_casing : PackedStringArray({ suffix, suffix.to_lower(), suffix[0].to_lower() + suffix.substr(1) }) { + if (component == suffix_casing) { + path_type = type; + found_suffix = suffix_casing; + break; + } + } + } + } else { + break; + } + } + + if (found_suffix) { + print_line_rich(vformat("\n[b]%s[/b] is a [color=green][b]%s[/b][/color] texture with suffix [b]%s[/b].", path, path_type, found_suffix)); + } else { + print_line(vformat("Could not detect material: %s" % path)); + return nullptr; + } + + Dictionary found_textures; + for (const String &type : types) { + if (type == path_type) { + // We already know this texture's type, since it was the texture originally specified. + found_textures[type] = path; + continue; + } + + for (const String &suffix : types[type]) { + for (const String &suffix_casing : PackedStringArray({ suffix, suffix.to_lower(), suffix[0].to_lower() + suffix.substr(1) })) + if (FileAccess::file_exists(p_texture_path.replace(found_suffix, suffix_casing))) { + found_textures[type] = p_texture_path.replace(found_suffix, suffix_casing); + } + } + } + + print_line(found_textures); + + Ref material; + + if (found_textures.has("orm")) { + print_line("Creating ORMMaterial3D"); + material = memnew(ORMMaterial3D); + material.instantiate(); + material->set_texture(BaseMaterial3D::TEXTURE_ORM, ResourceLoader::load(found_textures["orm"])); + material->set_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION, true); + } else { + print_line("Creating StandardMaterial3D"); + material = memnew(StandardMaterial3D); + material.instantiate(); + if (found_textures.has("roughness")) { + material->set_roughness(1.0); + material->set_texture(BaseMaterial3D::TEXTURE_ROUGHNESS, ResourceLoader::load(found_textures["roughness"])); + } if (found_textures.has("metallic")) { + material->set_metallic(1.0); + material->set_texture(BaseMaterial3D::TEXTURE_METALLIC, ResourceLoader::load(found_textures["metallic"])); + } if (found_textures.has("ao")) { + material->set_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION, true); + material->set_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, ResourceLoader::load(found_textures["ao"])); + } + + if (found_textures.has("albedo")) { + material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, ResourceLoader::load(found_textures["albedo"])); + } + + if (found_textures.has("normal")) { + material->set_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING, true); + material->set_texture(BaseMaterial3D::TEXTURE_NORMAL, ResourceLoader::load(found_textures["normal"])); + } + + if (found_textures.has("emission")) { + material->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); + material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, ResourceLoader::load(found_textures["emission"])); + } + + if (found_textures.has("height")) { + material->set_feature(BaseMaterial3D::FEATURE_HEIGHT_MAPPING, true); + material->set_texture(BaseMaterial3D::TEXTURE_HEIGHTMAP, ResourceLoader::load(found_textures["height"])); + } + + if (found_textures.has("rim")) { + material->set_feature(BaseMaterial3D::FEATURE_RIM, true); + material->set_texture(BaseMaterial3D::TEXTURE_RIM, ResourceLoader::load(found_textures["rim"])); + } + + if (found_textures.has("clearcoat")) { + material->set_feature(BaseMaterial3D::FEATURE_CLEARCOAT, true); + material->set_texture(BaseMaterial3D::TEXTURE_CLEARCOAT, ResourceLoader::load(found_textures["clearcoat"])); + } + + if (found_textures.has("anisotropy")) { + material->set_feature(BaseMaterial3D::FEATURE_ANISOTROPY, true); + material->set_texture(BaseMaterial3D::TEXTURE_FLOWMAP, ResourceLoader::load(found_textures["anisotropy"])); + } + + if (found_textures.has("subsurf_scatter")) { + material->set_feature(BaseMaterial3D::FEATURE_SUBSURFACE_SCATTERING, true); + material->set_texture(BaseMaterial3D::TEXTURE_SUBSURFACE_SCATTERING, ResourceLoader::load(found_textures["subsurf_scatter"])); + } + + if (found_textures.has("subsurf_scatter_transmittance")) { + material->set_feature(BaseMaterial3D::FEATURE_SUBSURFACE_TRANSMITTANCE, true); + material->set_texture(BaseMaterial3D::TEXTURE_SUBSURFACE_TRANSMITTANCE, ResourceLoader::load(found_textures["subsurf_scatter_transmittance"])); + } + + if (found_textures.has("backlight")) { + material->set_feature(BaseMaterial3D::FEATURE_BACKLIGHT, true); + material->set_texture(BaseMaterial3D::TEXTURE_BACKLIGHT, ResourceLoader::load(found_textures["backlight"])); + } + + if (found_textures.has("refraction")) { + material->set_feature(BaseMaterial3D::FEATURE_REFRACTION, true); + material->set_texture(BaseMaterial3D::TEXTURE_REFRACTION, ResourceLoader::load(found_textures["refraction"])); + } + + if (found_textures.has("detail_albedo")) { + material->set_feature(BaseMaterial3D::FEATURE_DETAIL, true); + material->set_texture(BaseMaterial3D::TEXTURE_DETAIL_ALBEDO, ResourceLoader::load(found_textures["detail_albedo"])); + } + + return material; +} + void BaseMaterial3D::set_on_top_of_alpha() { set_transparency(TRANSPARENCY_DISABLED); set_render_priority(RENDER_PRIORITY_MAX);