diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index f4db2563017..4221f4e51a0 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -294,7 +294,16 @@ Error EditorSceneImporterGLTF::_parse_nodes(GLTFState &state) { node->xform.basis.set_quat_scale(node->rotation, node->scale); node->xform.origin = node->translation; } - + if (n.has("extensions")) { + Dictionary extensions = n["extensions"]; + if (extensions.has("KHR_lights_punctual")) { + Dictionary lights_punctual = extensions["KHR_lights_punctual"]; + if (lights_punctual.has("light")) { + GLTFLightIndex light = lights_punctual["light"]; + node->light = light; + } + } + } if (n.has("children")) { const Array &children = n["children"]; for (int j = 0; j < children.size(); j++) { @@ -2234,7 +2243,6 @@ bool EditorSceneImporterGLTF::_skins_are_same(const Ref &skin_a, const Ref } for (int i = 0; i < skin_a->get_bind_count(); ++i) { - if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { return false; } @@ -2264,6 +2272,58 @@ void EditorSceneImporterGLTF::_remove_duplicate_skins(GLTFState &state) { } } +Error EditorSceneImporterGLTF::_parse_lights(GLTFState &state) { + if (!state.json.has("extensions")) { + return OK; + } + Dictionary extensions = state.json["extensions"]; + if (!extensions.has("KHR_lights_punctual")) { + return OK; + } + Dictionary lights_punctual = extensions["KHR_lights_punctual"]; + if (!lights_punctual.has("lights")) { + return OK; + } + + const Array &lights = lights_punctual["lights"]; + + for (GLTFLightIndex light_i = 0; light_i < lights.size(); light_i++) { + const Dictionary &d = lights[light_i]; + + GLTFLight light; + ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); + const String &type = d["type"]; + light.type = type; + + if (d.has("color")) { + const Array &arr = d["color"]; + ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); + const Color c = Color(arr[0], arr[1], arr[2]).to_srgb(); + light.color = c; + } + if (d.has("intensity")) { + light.intensity = d["intensity"]; + } + if (d.has("range")) { + light.range = d["range"]; + } + if (type == "spot") { + const Dictionary &spot = d["spot"]; + light.inner_cone_angle = spot["innerConeAngle"]; + light.outer_cone_angle = spot["outerConeAngle"]; + ERR_FAIL_COND_V_MSG(light.inner_cone_angle >= light.outer_cone_angle, ERR_PARSE_ERROR, "The inner angle must be smaller than the outer angle."); + } else if (type != "point" && type != "directional") { + ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Light type is unknown."); + } + + state.lights.push_back(light); + } + + print_verbose("glTF: Total lights: " + itos(state.lights.size())); + + return OK; +} + Error EditorSceneImporterGLTF::_parse_cameras(GLTFState &state) { if (!state.json.has("cameras")) @@ -2511,6 +2571,58 @@ MeshInstance *EditorSceneImporterGLTF::_generate_mesh_instance(GLTFState &state, return mi; } +Light *EditorSceneImporterGLTF::_generate_light(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { + const GLTFNode *gltf_node = state.nodes[node_index]; + + ERR_FAIL_INDEX_V(gltf_node->light, state.lights.size(), nullptr); + + print_verbose("glTF: Creating light for: " + gltf_node->name); + + const GLTFLight &l = state.lights[gltf_node->light]; + + float intensity = l.intensity; + if (intensity > 10) { + // GLTF spec has the default around 1, but Blender defaults lights to 100. + // The only sane way to handle this is to check where it came from and + // handle it accordingly. If it's over 10, it probably came from Blender. + intensity /= 100; + } + + if (l.type == "directional") { + DirectionalLight *light = memnew(DirectionalLight); + light->set_param(Light::PARAM_ENERGY, intensity); + light->set_color(l.color); + return light; + } + + const float range = CLAMP(l.range, 0, 4096); + // Doubling the range will double the effective brightness, so we need double attenuation (half brightness). + // We want to have double intensity give double brightness, so we need half the attenuation. + const float attenuation = range / intensity; + if (l.type == "point") { + OmniLight *light = memnew(OmniLight); + light->set_param(OmniLight::PARAM_ATTENUATION, attenuation); + light->set_param(OmniLight::PARAM_RANGE, range); + light->set_color(l.color); + return light; + } + if (l.type == "spot") { + SpotLight *light = memnew(SpotLight); + light->set_param(SpotLight::PARAM_ATTENUATION, attenuation); + light->set_param(SpotLight::PARAM_RANGE, range); + light->set_param(SpotLight::PARAM_SPOT_ANGLE, Math::rad2deg(l.outer_cone_angle)); + light->set_color(l.color); + + // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b + // The points in desmos are not exact, except for (1, infinity). + float angle_ratio = l.inner_cone_angle / l.outer_cone_angle; + float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; + light->set_param(SpotLight::PARAM_SPOT_ATTENUATION, angle_attenuation); + return light; + } + return nullptr; +} + Camera *EditorSceneImporterGLTF::_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) { const GLTFNode *gltf_node = state.nodes[node_index]; @@ -2585,6 +2697,8 @@ void EditorSceneImporterGLTF::_generate_scene_node(GLTFState &state, Node *scene current_node = _generate_mesh_instance(state, scene_parent, node_index); } else if (gltf_node->camera >= 0) { current_node = _generate_camera(state, scene_parent, node_index); + } else if (gltf_node->light >= 0) { + current_node = _generate_light(state, scene_parent, node_index); } else { current_node = _generate_spatial(state, scene_parent, node_index); } @@ -2973,13 +3087,15 @@ Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_fla //binary file //text file Error err = _parse_glb(p_path, state); - if (err) + if (err) { return NULL; + } } else { //text file Error err = _parse_json(p_path, state); - if (err) + if (err) { return NULL; + } } ERR_FAIL_COND_V(!state.json.has("asset"), NULL); @@ -2997,83 +3113,104 @@ Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_fla /* STEP 0 PARSE SCENE */ Error err = _parse_scenes(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 1 PARSE NODES */ err = _parse_nodes(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 2 PARSE BUFFERS */ err = _parse_buffers(state, p_path.get_base_dir()); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 3 PARSE BUFFER VIEWS */ err = _parse_buffer_views(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 4 PARSE ACCESSORS */ err = _parse_accessors(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 5 PARSE IMAGES */ err = _parse_images(state, p_path.get_base_dir()); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 6 PARSE TEXTURES */ err = _parse_textures(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 7 PARSE TEXTURES */ err = _parse_materials(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 9 PARSE SKINS */ err = _parse_skins(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 10 DETERMINE SKELETONS */ err = _determine_skeletons(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 11 CREATE SKELETONS */ err = _create_skeletons(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 12 CREATE SKINS */ err = _create_skins(state); - if (err != OK) + if (err != OK) { return NULL; + } /* STEP 13 PARSE MESHES (we have enough info now) */ err = _parse_meshes(state); - if (err != OK) + if (err != OK) { return NULL; + } - /* STEP 14 PARSE CAMERAS */ + /* STEP 14 PARSE LIGHTS */ + err = _parse_lights(state); + if (err != OK) { + return NULL; + } + + /* STEP 15 PARSE CAMERAS */ err = _parse_cameras(state); - if (err != OK) + if (err != OK) { return NULL; + } - /* STEP 15 PARSE ANIMATIONS */ + /* STEP 16 PARSE ANIMATIONS */ err = _parse_animations(state); - if (err != OK) + if (err != OK) { return NULL; + } - /* STEP 16 ASSIGN SCENE NAMES */ + /* STEP 17 ASSIGN SCENE NAMES */ _assign_scene_names(state); - /* STEP 17 MAKE SCENE! */ + /* STEP 18 MAKE SCENE! */ Spatial *scene = _generate_scene(state, p_bake_fps); return scene; diff --git a/editor/import/editor_scene_importer_gltf.h b/editor/import/editor_scene_importer_gltf.h index 1712e3f7951..b145ddc6ffb 100644 --- a/editor/import/editor_scene_importer_gltf.h +++ b/editor/import/editor_scene_importer_gltf.h @@ -32,6 +32,7 @@ #define EDITOR_SCENE_IMPORTER_GLTF_H #include "editor/import/resource_importer_scene.h" +#include "scene/3d/light.h" #include "scene/3d/skeleton.h" #include "scene/3d/spatial.h" @@ -51,6 +52,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { typedef int GLTFImageIndex; typedef int GLTFMaterialIndex; typedef int GLTFMeshIndex; + typedef int GLTFLightIndex; typedef int GLTFNodeIndex; typedef int GLTFSkeletonIndex; typedef int GLTFSkinIndex; @@ -115,6 +117,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { GLTFNodeIndex fake_joint_parent; + GLTFLightIndex light; + GLTFNode() : parent(-1), height(-1), @@ -125,7 +129,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { joint(false), translation(0, 0, 0), scale(Vector3(1, 1, 1)), - fake_joint_parent(-1) {} + fake_joint_parent(-1), + light(-1) {} }; struct GLTFBufferView { @@ -262,6 +267,23 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { } }; + struct GLTFLight { + Color color; + float intensity; + String type; + float range; + float inner_cone_angle; + float outer_cone_angle; + GLTFLight() { + color = Color(1.0f, 1.0f, 1.0f); + intensity = 1.0f; + type = ""; + range = Math_INF; + inner_cone_angle = 0.0f; + outer_cone_angle = Math_PI / 4.0; + } + }; + struct GLTFAnimation { bool loop = false; @@ -317,6 +339,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { Vector skins; Vector cameras; + Vector lights; Set unique_names; @@ -395,12 +418,13 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { void _remove_duplicate_skins(GLTFState &state); Error _parse_cameras(GLTFState &state); - + Error _parse_lights(GLTFState &state); Error _parse_animations(GLTFState &state); BoneAttachment *_generate_bone_attachment(GLTFState &state, Skeleton *skeleton, const GLTFNodeIndex node_index); MeshInstance *_generate_mesh_instance(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); Camera *_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); + Light *_generate_light(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); Spatial *_generate_spatial(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index); void _generate_scene_node(GLTFState &state, Node *scene_parent, Spatial *scene_root, const GLTFNodeIndex node_index);