Skip to content

Commit

Permalink
Add Generate LODs, Shadow Mesh and Lightmap UV2 options to OBJ mesh i…
Browse files Browse the repository at this point in the history
…mport

This puts OBJ mesh import on parity with 3D scene import. It's now
possible to have the same level of optimization as imported 3D scenes
while using the OBJ mesh workflow.
  • Loading branch information
Calinou committed Jul 8, 2024
1 parent 5ee4100 commit 60c20bf
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 5 deletions.
13 changes: 13 additions & 0 deletions doc/classes/ResourceImporterOBJ.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
<member name="force_disable_mesh_compression" type="bool" setter="" getter="" default="false">
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.
</member>
<member name="generate_lightmap_uv2" type="bool" setter="" getter="" default="false">
If [code]true[/code], generates UV2 on import for [LightmapGI] baking.
</member>
<member name="generate_lightmap_uv2_texel_size" type="float" setter="" getter="" default="0.2">
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.
[b]Note:[/b] Only effective if [member generate_lightmap_uv2] is [code]true[/code].
</member>
<member name="generate_lods" type="bool" setter="" getter="" default="true">
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.
</member>
<member name="generate_shadow_mesh" type="bool" setter="" getter="" default="true">
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).
</member>
<member name="generate_tangents" type="bool" setter="" getter="" default="true">
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.
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.
Expand Down
81 changes: 76 additions & 5 deletions editor/import/3d/resource_importer_obj.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,12 @@ static Error _parse_material_library(const String &p_path, HashMap<String, Ref<S
return OK;
}

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) {
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) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
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));

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

mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, array, TypedArray<Array>(), Dictionary(), material, name, mesh_flags);

if (p_generate_lightmap_uv2) {
Vector<uint8_t> lightmap_cache;
mesh->lightmap_unwrap_cached(Transform3D(), p_generate_lightmap_uv2_texel_size, p_src_lightmap_cache, lightmap_cache);

if (!lightmap_cache.is_empty()) {
if (r_lightmap_caches.is_empty()) {
r_lightmap_caches.push_back(lightmap_cache);
} else {
// MD5 is stored at the beginning of the cache data.
const String new_md5 = String::md5(lightmap_cache.ptr());

for (int i = 0; i < r_lightmap_caches.size(); i++) {
const String md5 = String::md5(r_lightmap_caches[i].ptr());
if (new_md5 < md5) {
r_lightmap_caches.insert(i, lightmap_cache);
break;
}

if (new_md5 == md5) {
break;
}
}
}
}
}

if (p_generate_lods) {
// Use normal merge/split angles that match the defaults used for 3D scene importing.
mesh->generate_lods(60.0f, 25.0f, {});
}

if (p_generate_shadow_mesh) {
mesh->create_shadow_mesh();
}

print_verbose("OBJ: Added surface :" + mesh->get_surface_name(mesh->get_surface_count() - 1));

surf_tool->clear();
Expand Down Expand Up @@ -507,7 +543,10 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
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) {
List<Ref<ImporterMesh>> meshes;

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);
// LOD, shadow mesh and lightmap UV2 generation are handled by ResourceImporterScene in this case,
// so disable it within the OBJ mesh import.
Vector<Vector<uint8_t>> mesh_lightmap_caches;
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);

if (err != OK) {
if (r_err) {
Expand Down Expand Up @@ -576,19 +615,51 @@ String ResourceImporterOBJ::get_preset_name(int p_idx) const {

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

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

return true;
}

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) {
List<Ref<ImporterMesh>> meshes;

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);
Vector<uint8_t> src_lightmap_cache;
Vector<Vector<uint8_t>> mesh_lightmap_caches;

Error err;
{
src_lightmap_cache = FileAccess::get_file_as_bytes(p_source_file + ".unwrap_cache", &err);
if (err != OK) {
src_lightmap_cache.clear();
}
}

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);

if (mesh_lightmap_caches.size()) {
Ref<FileAccess> f = FileAccess::open(p_source_file + ".unwrap_cache", FileAccess::WRITE);
if (f.is_valid()) {
f->store_32(mesh_lightmap_caches.size());
for (int i = 0; i < mesh_lightmap_caches.size(); i++) {
String md5 = String::md5(mesh_lightmap_caches[i].ptr());
f->store_buffer(mesh_lightmap_caches[i].ptr(), mesh_lightmap_caches[i].size());
}
}
}
err = OK;

ERR_FAIL_COND_V(err != OK, err);
ERR_FAIL_COND_V(meshes.size() != 1, ERR_BUG);
Expand Down

0 comments on commit 60c20bf

Please sign in to comment.