Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Generate LODs, Shadow Mesh and Lightmap UV2 options to OBJ mesh import #94108

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions doc/classes/ResourceImporterOBJ.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,26 @@
<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.
</member>
<member name="offset_mesh" type="Vector3" setter="" getter="" default="Vector3(0, 0, 0)">
Offsets the mesh's data by the specified value. This can be used to work around misaligned meshes without having to modify the source file.
</member>
<member name="optimize_mesh" type="bool" setter="" getter="" default="true">
Unused parameter. This currently has no effect.
</member>
<member name="scale_mesh" type="Vector3" setter="" getter="" default="Vector3(1, 1, 1)">
Scales the mesh's data by the specified value. This can be used to work around misscaled meshes without having to modify the source file.
</member>
Expand Down
82 changes: 76 additions & 6 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, bool p_optimize, 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();
}
Comment on lines +475 to +482
Copy link
Member Author

@Calinou Calinou Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: if you perform these steps before lightmap UV2 generation, the resulting mesh will be broken. It'll display with the wrong transform and no material (pure black color). I have no idea why this happens, but I figure I'd mention it either way. Putting up a MRP for this will likely require exposing additional ImporterMesh methods (such as create_shadow_mesh() and lightmap_unwrap_cached()).


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, false, 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,20 +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, "optimize_mesh"), true));
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["optimize_mesh"], 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
Loading