Skip to content

Commit de6814d

Browse files
committed
Merge pull request #94108 from Calinou/obj-import-add-generate-lods-shadow-mesh-lightmap-uv2
Add Generate LODs, Shadow Mesh and Lightmap UV2 options to OBJ mesh import
2 parents 17f06f6 + b8ee61f commit de6814d

File tree

2 files changed

+91
-5
lines changed

2 files changed

+91
-5
lines changed

doc/classes/ResourceImporterOBJ.xml

+13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@
1414
<member name="force_disable_mesh_compression" type="bool" setter="" getter="" default="false">
1515
If [code]true[/code], mesh compression will not be used. Consider enabling if you notice blocky artifacts in your mesh normals or UVs, or if you have meshes that are larger than a few thousand meters in each direction.
1616
</member>
17+
<member name="generate_lightmap_uv2" type="bool" setter="" getter="" default="false">
18+
If [code]true[/code], generates UV2 on import for [LightmapGI] baking.
19+
</member>
20+
<member name="generate_lightmap_uv2_texel_size" type="float" setter="" getter="" default="0.2">
21+
Controls the size of each texel on the baked lightmap. A smaller value results in more precise lightmaps, at the cost of larger lightmap sizes and longer bake times.
22+
[b]Note:[/b] Only effective if [member generate_lightmap_uv2] is [code]true[/code].
23+
</member>
24+
<member name="generate_lods" type="bool" setter="" getter="" default="true">
25+
If [code]true[/code], generates lower detail variants of the mesh which will be displayed in the distance to improve rendering performance. Not all meshes benefit from LOD, especially if they are never rendered from far away. Disabling this can reduce output file size and speed up importing. See [url=$DOCS_URL/tutorials/3d/mesh_lod.html#doc-mesh-lod]Mesh level of detail (LOD)[/url] for more information.
26+
</member>
27+
<member name="generate_shadow_mesh" type="bool" setter="" getter="" default="true">
28+
If [code]true[/code], enables the generation of shadow meshes on import. This optimizes shadow rendering without reducing quality by welding vertices together when possible. This in turn reduces the memory bandwidth required to render shadows. Shadow mesh generation currently doesn't support using a lower detail level than the source mesh (but shadow rendering will make use of LODs when relevant).
29+
</member>
1730
<member name="generate_tangents" type="bool" setter="" getter="" default="true">
1831
If [code]true[/code], generate vertex tangents using [url=http://www.mikktspace.com/]Mikktspace[/url] if the source mesh doesn't have tangent data. When possible, it's recommended to let the 3D modeling software generate tangents on export instead on relying on this option. Tangents are required for correct display of normal and height maps, along with any material/shader features that require tangents.
1932
If you don't need material features that require tangents, disabling this can reduce output file size and speed up importing if the source 3D file doesn't contain tangents.

editor/import/3d/resource_importer_obj.cpp

+78-5
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,12 @@ static Error _parse_material_library(const String &p_path, HashMap<String, Ref<S
202202
return OK;
203203
}
204204

205-
static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, List<String> *r_missing_deps) {
205+
static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, bool p_generate_lods, bool p_generate_shadow_mesh, bool p_generate_lightmap_uv2, float p_generate_lightmap_uv2_texel_size, const PackedByteArray &p_src_lightmap_cache, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, Vector<Vector<uint8_t>> &r_lightmap_caches, List<String> *r_missing_deps) {
206206
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
207207
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open OBJ file '%s', it may not exist or not be readable.", p_path));
208208

209-
// Avoid trying to load/interpret potential build artifacts from Visual Studio (e.g. when compiling native plugins inside the project tree)
210-
// This should only match, if it's indeed a COFF file header
209+
// Avoid trying to load/interpret potential build artifacts from Visual Studio (e.g. when compiling native plugins inside the project tree).
210+
// This should only match if it's indeed a COFF file header.
211211
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
212212
const int first_bytes = f->get_16();
213213
static const Vector<int> coff_header_machines{
@@ -445,6 +445,7 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
445445
}
446446

447447
mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, array, TypedArray<Array>(), Dictionary(), material, name, mesh_flags);
448+
448449
print_verbose("OBJ: Added surface :" + mesh->get_surface_name(mesh->get_surface_count() - 1));
449450

450451
if (!current_material.is_empty()) {
@@ -508,6 +509,43 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
508509
}
509510
}
510511

512+
if (p_generate_lightmap_uv2) {
513+
Vector<uint8_t> lightmap_cache;
514+
mesh->lightmap_unwrap_cached(Transform3D(), p_generate_lightmap_uv2_texel_size, p_src_lightmap_cache, lightmap_cache);
515+
516+
if (!lightmap_cache.is_empty()) {
517+
if (r_lightmap_caches.is_empty()) {
518+
r_lightmap_caches.push_back(lightmap_cache);
519+
} else {
520+
// MD5 is stored at the beginning of the cache data.
521+
const String new_md5 = String::md5(lightmap_cache.ptr());
522+
523+
for (int i = 0; i < r_lightmap_caches.size(); i++) {
524+
const String md5 = String::md5(r_lightmap_caches[i].ptr());
525+
if (new_md5 < md5) {
526+
r_lightmap_caches.insert(i, lightmap_cache);
527+
break;
528+
}
529+
530+
if (new_md5 == md5) {
531+
break;
532+
}
533+
}
534+
}
535+
}
536+
}
537+
538+
mesh->optimize_indices_for_cache();
539+
540+
if (p_generate_lods) {
541+
// Use normal merge/split angles that match the defaults used for 3D scene importing.
542+
mesh->generate_lods(60.0f, 25.0f, {});
543+
}
544+
545+
if (p_generate_shadow_mesh) {
546+
mesh->create_shadow_mesh();
547+
}
548+
511549
if (p_single_mesh && mesh->get_surface_count() > 0) {
512550
r_meshes.push_back(mesh);
513551
}
@@ -518,7 +556,10 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
518556
Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) {
519557
List<Ref<ImporterMesh>> meshes;
520558

521-
Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, r_missing_deps);
559+
// LOD, shadow mesh and lightmap UV2 generation are handled by ResourceImporterScene in this case,
560+
// so disable it within the OBJ mesh import.
561+
Vector<Vector<uint8_t>> mesh_lightmap_caches;
562+
Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, false, false, false, 0.2, PackedByteArray(), Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, mesh_lightmap_caches, r_missing_deps);
522563

523564
if (err != OK) {
524565
if (r_err) {
@@ -587,19 +628,51 @@ String ResourceImporterOBJ::get_preset_name(int p_idx) const {
587628

588629
void ResourceImporterOBJ::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
589630
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_tangents"), true));
631+
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_lods"), true));
632+
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_shadow_mesh"), true));
633+
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_lightmap_uv2", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
634+
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "generate_lightmap_uv2_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.2));
590635
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "scale_mesh"), Vector3(1, 1, 1)));
591636
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "offset_mesh"), Vector3(0, 0, 0)));
592637
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_disable_mesh_compression"), false));
593638
}
594639

595640
bool ResourceImporterOBJ::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
641+
if (p_option == "generate_lightmap_uv2_texel_size" && !p_options["generate_lightmap_uv2"]) {
642+
// Only display the lightmap texel size import option when lightmap UV2 generation is enabled.
643+
return false;
644+
}
645+
596646
return true;
597647
}
598648

599649
Error ResourceImporterOBJ::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
600650
List<Ref<ImporterMesh>> meshes;
601651

602-
Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], nullptr);
652+
Vector<uint8_t> src_lightmap_cache;
653+
Vector<Vector<uint8_t>> mesh_lightmap_caches;
654+
655+
Error err;
656+
{
657+
src_lightmap_cache = FileAccess::get_file_as_bytes(p_source_file + ".unwrap_cache", &err);
658+
if (err != OK) {
659+
src_lightmap_cache.clear();
660+
}
661+
}
662+
663+
err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["generate_lods"], p_options["generate_shadow_mesh"], p_options["generate_lightmap_uv2"], p_options["generate_lightmap_uv2_texel_size"], src_lightmap_cache, p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], mesh_lightmap_caches, nullptr);
664+
665+
if (mesh_lightmap_caches.size()) {
666+
Ref<FileAccess> f = FileAccess::open(p_source_file + ".unwrap_cache", FileAccess::WRITE);
667+
if (f.is_valid()) {
668+
f->store_32(mesh_lightmap_caches.size());
669+
for (int i = 0; i < mesh_lightmap_caches.size(); i++) {
670+
String md5 = String::md5(mesh_lightmap_caches[i].ptr());
671+
f->store_buffer(mesh_lightmap_caches[i].ptr(), mesh_lightmap_caches[i].size());
672+
}
673+
}
674+
}
675+
err = OK;
603676

604677
ERR_FAIL_COND_V(err != OK, err);
605678
ERR_FAIL_COND_V(meshes.size() != 1, ERR_BUG);

0 commit comments

Comments
 (0)