Skip to content

Commit

Permalink
[WIP] Infer material from texture name
Browse files Browse the repository at this point in the history
TODO:

- Convert to static method.
  • Loading branch information
Calinou committed Nov 27, 2024
1 parent bbc5469 commit 495310b
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 0 deletions.
165 changes: 165 additions & 0 deletions scene/resources/material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2836,6 +2836,148 @@ Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, Transparency p_
return materials_for_2d[key];
}

Ref<Material> BaseMaterial3D::get_material_from_texture_path(const String &p_file_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_file_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 : texture_types_from_components.keys()) {

Check failure on line 2849 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2849 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Template w/ Mono (target=template_release, tests=yes)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2849 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with ThreadSanitizer (target=editor, tests=yes, dev_build=yes, use_tsan=yes, use_llvm=yes, linker=lld)

loop variable 'type' of type 'const String &' binds to a temporary constructed from type 'Variant &' [-Werror,-Wrange-loop-construct]

Check failure on line 2849 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with clang sanitizers (target=editor, tests=yes, dev_build=yes, use_asan=yes, use_ubsan=yes, use_llvm=yes, linker=lld)

loop variable 'type' of type 'const String &' binds to a temporary constructed from type 'Variant &' [-Werror,-Wrange-loop-construct]

Check failure on line 2849 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor w/ Mono (target=editor)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2849 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template w/ GCC (target=template_release, tests=yes, use_mingw=yes)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2849 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]
for (const String &suffix : PackedStringArray(texture_types_from_components[type])) {
// Check PascalCase, lowercase and camelCase.
for (const String &suffix_casing : PackedStringArray({ suffix, suffix.to_lower(), suffix.to_camel_case() })) {
if (component == suffix_casing) {
path_type = type;
found_suffix = suffix_casing;
break;
}
}
}
}
} else {
break;
}
}

if (!found_suffix.is_empty()) {
print_line_rich(vformat("\n[b]%s[/b] is a [color=green][b]%s[/b][/color] texture with suffix [b]%s[/b].", p_file_path, path_type, found_suffix));
} else {
print_line(vformat("Could not detect material: %s", p_file_path));
return nullptr;
}

Dictionary found_textures;
for (const String &type : texture_types_from_components.keys()) {

Check failure on line 2874 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2874 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Template w/ Mono (target=template_release, tests=yes)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2874 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with ThreadSanitizer (target=editor, tests=yes, dev_build=yes, use_tsan=yes, use_llvm=yes, linker=lld)

loop variable 'type' of type 'const String &' binds to a temporary constructed from type 'Variant &' [-Werror,-Wrange-loop-construct]

Check failure on line 2874 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with clang sanitizers (target=editor, tests=yes, dev_build=yes, use_asan=yes, use_ubsan=yes, use_llvm=yes, linker=lld)

loop variable 'type' of type 'const String &' binds to a temporary constructed from type 'Variant &' [-Werror,-Wrange-loop-construct]

Check failure on line 2874 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor w/ Mono (target=editor)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2874 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template w/ GCC (target=template_release, tests=yes, use_mingw=yes)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]

Check failure on line 2874 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold)

loop variable 'type' of type 'const String&' binds to a temporary constructed from type 'Variant' [-Werror=range-loop-construct]
if (type == path_type) {
// We already know this texture's type, since it was the texture originally specified.
found_textures[type] = p_file_path;
continue;
}

for (const String &suffix : PackedStringArray(texture_types_from_components[type])) {
for (const String &suffix_casing : PackedStringArray({ suffix, suffix.to_lower(), suffix.to_camel_case() })) {
const Ref<FileAccess> file = FileAccess::create_for_path(p_file_path.replace(found_suffix, suffix_casing));
if (file->file_exists()) {

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

no matching function for call to 'FileAccess::file_exists()'

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Template w/ Mono (target=template_release, tests=yes)

no matching function for call to 'FileAccess::file_exists()'

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with ThreadSanitizer (target=editor, tests=yes, dev_build=yes, use_tsan=yes, use_llvm=yes, linker=lld)

too few arguments to function call, single argument 'p_name' was not specified

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with clang sanitizers (target=editor, tests=yes, dev_build=yes, use_asan=yes, use_ubsan=yes, use_llvm=yes, linker=lld)

too few arguments to function call, single argument 'p_name' was not specified

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template (target=template_release, tests=yes)

'FileAccess::file_exists': function does not take 0 arguments

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor w/ Mono (target=editor)

no matching function for call to 'FileAccess::file_exists()'

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template w/ GCC (target=template_release, tests=yes, use_mingw=yes)

no matching function for call to 'FileAccess::file_exists()'

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold)

no matching function for call to 'FileAccess::file_exists()'

Check failure on line 2884 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor (target=editor, tests=yes)

'FileAccess::file_exists': function does not take 0 arguments
found_textures[type] = p_file_path.replace(found_suffix, suffix_casing);
}
}
}
}

print_line(found_textures);

Ref<BaseMaterial3D> material;

Check failure on line 2893 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)

declaration of 'material' shadows a member of 'BaseMaterial3D' [-Werror=shadow]

Check failure on line 2893 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Template w/ Mono (target=template_release, tests=yes)

declaration of 'material' shadows a member of 'BaseMaterial3D' [-Werror=shadow]

Check warning on line 2893 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template (target=template_release, tests=yes)

declaration of 'material' hides class member

Check failure on line 2893 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor w/ Mono (target=editor)

declaration of 'material' shadows a member of 'BaseMaterial3D' [-Werror=shadow]

Check failure on line 2893 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template w/ GCC (target=template_release, tests=yes, use_mingw=yes)

declaration of 'material' shadows a member of 'BaseMaterial3D' [-Werror=shadow]

Check failure on line 2893 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold)

declaration of 'material' shadows a member of 'BaseMaterial3D' [-Werror=shadow]

Check warning on line 2893 in scene/resources/material.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor (target=editor, tests=yes)

declaration of 'material' hides class member

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);
Expand Down Expand Up @@ -3511,6 +3653,29 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) {
current_key.invalid_key = 1;

_mark_dirty();

// 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.
texture_types_from_components["orm"] = PackedStringArray({ "ORM", "ARM" });

// Common PBR material texture types, standardized across engines.
texture_types_from_components["albedo"] = PackedStringArray({ "Albedo", "BaseColor", "BaseColour", "Base", "Color", "Colour", "Diffuse", "Diff", "C", "D" });
texture_types_from_components["normal"] = PackedStringArray({ "Normal", "NormalGL", "NormalDX", "Local", "Norm", "Nor", "Nor_GL", "Nor_DX", "N" });
texture_types_from_components["roughness"] = PackedStringArray({ "Roughness", "Rough", "R" }); // Not used in ORMMaterial3D creation.
texture_types_from_components["metallic"] = PackedStringArray({ "Metallic", "Metalness", "Metal", "M" }); // Not used in ORMMaterial3D creation.
texture_types_from_components["ao"] = PackedStringArray({ "AO", "AmbientOcclusion", "Ambient", "Occlusion", "A", "O" }); // Not used in ORMMaterial3D creation.
texture_types_from_components["emission"] = PackedStringArray({ "Emission", "Emissive", "Glow", "Luma", "E", "G" });
texture_types_from_components["height"] = PackedStringArray({ "Height", "Displacement", "Disp", "H", "Z" });

// Less common and not as standardized across engines.
texture_types_from_components["rim"] = PackedStringArray({ "Rim" });
texture_types_from_components["clearcoat"] = PackedStringArray({ "Clearcoat" });
texture_types_from_components["anisotropy"] = PackedStringArray({ "Anisotropy", "Aniso", "Flowmap", "Flow" });
texture_types_from_components["subsurf_scatter"] = PackedStringArray({ "Subsurface", "Subsurf", "Scattering", "Scatter", "SSS" });
texture_types_from_components["subsurf_scatter_transmittance"] = PackedStringArray({ "Transmittance", "Transmission", "Transmissive", "Scatter", "SSS" });
texture_types_from_components["backlight"] = PackedStringArray({ "BackLighting", "Backlight" });
texture_types_from_components["refraction"] = PackedStringArray({ "Refraction", "Refract" });
texture_types_from_components["detail_albedo"] = PackedStringArray({ "Detail" });
}

BaseMaterial3D::~BaseMaterial3D() {
Expand Down
17 changes: 17 additions & 0 deletions scene/resources/material.h
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,22 @@ class BaseMaterial3D : public Material {

static HashMap<uint64_t, Ref<StandardMaterial3D>> materials_for_2d; //used by Sprite3D, Label3D and other stuff

// 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,
// lowercase and camelCase 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 texture_types_from_components;

protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
Expand Down Expand Up @@ -781,6 +797,7 @@ class BaseMaterial3D : public Material {
static void init_shaders();
static void finish_shaders();

Ref<Material> get_material_from_texture_path(const String &p_file_path) const;
static Ref<Material> get_material_for_2d(bool p_shaded, Transparency p_transparency, bool p_double_sided, bool p_billboard = false, bool p_billboard_y = false, bool p_msdf = false, bool p_no_depth = false, bool p_fixed_size = false, TextureFilter p_filter = TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, AlphaAntiAliasing p_alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF, RID *r_shader_rid = nullptr);

virtual RID get_rid() const override;
Expand Down

0 comments on commit 495310b

Please sign in to comment.