diff --git a/code/glTF2/glTF2Asset.h b/code/glTF2/glTF2Asset.h index 70f92df5b..23015c90a 100644 --- a/code/glTF2/glTF2Asset.h +++ b/code/glTF2/glTF2Asset.h @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * glTF Extensions Support: * KHR_materials_pbrSpecularGlossiness full * KHR_materials_unlit full + * KHR_lights_punctual full */ #ifndef GLTF2ASSET_H_INC #define GLTF2ASSET_H_INC @@ -668,6 +669,28 @@ namespace glTF2 void Read(Value& obj, Asset& r); }; + //! A light (from KHR_lights_punctual extension) + struct Light : public Object + { + enum Type + { + Directional, + Point, + Spot + }; + + Type type; + + vec3 color; + float intensity; + Nullable range; + + float innerConeAngle; + float outerConeAngle; + + Light() {} + void Read(Value& obj, Asset& r); + }; //! Image data used to create a texture. struct Image : public Object @@ -819,6 +842,7 @@ namespace glTF2 Nullable scale; Ref camera; + Ref light; std::vector< Ref > skeletons; //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy. Ref skin; //!< The ID of the skin referenced by this node. @@ -1050,6 +1074,7 @@ namespace glTF2 { bool KHR_materials_pbrSpecularGlossiness; bool KHR_materials_unlit; + bool KHR_lights_punctual; } extensionsUsed; @@ -1063,6 +1088,7 @@ namespace glTF2 LazyDict buffers; LazyDict bufferViews; LazyDict cameras; + LazyDict lights; LazyDict images; LazyDict materials; LazyDict meshes; @@ -1083,6 +1109,7 @@ namespace glTF2 , buffers (*this, "buffers") , bufferViews (*this, "bufferViews") , cameras (*this, "cameras") + , lights (*this, "lights", "KHR_lights_punctual") , images (*this, "images") , materials (*this, "materials") , meshes (*this, "meshes") diff --git a/code/glTF2/glTF2Asset.inl b/code/glTF2/glTF2Asset.inl index b51975c77..6f9eba50b 100644 --- a/code/glTF2/glTF2Asset.inl +++ b/code/glTF2/glTF2Asset.inl @@ -1067,6 +1067,39 @@ inline void Camera::Read(Value& obj, Asset& /*r*/) } } +inline void Light::Read(Value& obj, Asset& /*r*/) +{ +#ifndef M_PI + const float M_PI = 3.14159265358979323846f; +#endif + + std::string type_string; + ReadMember(obj, "type", type_string); + if (type_string == "directional") + type = Light::Directional; + else if (type_string == "point") + type = Light::Point; + else + type = Light::Spot; + + name = MemberOrDefault(obj, "name", ""); + + SetVector(color, vec3{ 1.0f, 1.0f, 1.0f }); + ReadMember(obj, "color", color); + + intensity = MemberOrDefault(obj, "intensity", 1.0f); + + ReadMember(obj, "range", range); + + if (type == Light::Spot) + { + Value* spot = FindObject(obj, "spot"); + if (!spot) throw DeadlyImportError("GLTF: Light missing its spot parameters"); + innerConeAngle = MemberOrDefault(*spot, "innerConeAngle", 0.0f); + outerConeAngle = MemberOrDefault(*spot, "outerConeAngle", M_PI / 4.0f); + } +} + inline void Node::Read(Value& obj, Asset& r) { @@ -1110,6 +1143,19 @@ inline void Node::Read(Value& obj, Asset& r) if (this->camera) this->camera->id = this->id; } + + if (Value* extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_lights_punctual) { + + if (Value* ext = FindObject(*extensions, "KHR_lights_punctual")) { + if (Value* light = FindUInt(*ext, "light")) { + this->light = r.lights.Retrieve(light->GetUint()); + if (this->light) + this->light->id = this->id; + } + } + } + } } inline void Scene::Read(Value& obj, Asset& r) @@ -1421,6 +1467,7 @@ inline void Asset::ReadExtensionsUsed(Document& doc) CHECK_EXT(KHR_materials_pbrSpecularGlossiness); CHECK_EXT(KHR_materials_unlit); + CHECK_EXT(KHR_lights_punctual); #undef CHECK_EXT } diff --git a/code/glTF2/glTF2AssetWriter.inl b/code/glTF2/glTF2AssetWriter.inl index bec88ceb8..92168fa61 100644 --- a/code/glTF2/glTF2AssetWriter.inl +++ b/code/glTF2/glTF2AssetWriter.inl @@ -202,6 +202,11 @@ namespace glTF2 { } + inline void Write(Value& /*obj*/, Light& /*c*/, AssetWriter& /*w*/) + { + + } + inline void Write(Value& obj, Image& img, AssetWriter& w) { if (img.bufferView) { diff --git a/code/glTF2/glTF2Importer.cpp b/code/glTF2/glTF2Importer.cpp index a2b18fc49..c6e998b3a 100644 --- a/code/glTF2/glTF2Importer.cpp +++ b/code/glTF2/glTF2Importer.cpp @@ -140,10 +140,10 @@ static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) } } -//static void CopyValue(const glTF2::vec3& v, aiColor3D& out) -//{ -// out.r = v[0]; out.g = v[1]; out.b = v[2]; -//} +static void CopyValue(const glTF2::vec3& v, aiColor3D& out) +{ + out.r = v[0]; out.g = v[1]; out.b = v[2]; +} static void CopyValue(const glTF2::vec4& v, aiColor4D& out) { @@ -710,6 +710,69 @@ void glTF2Importer::ImportCameras(glTF2::Asset& r) } } +void glTF2Importer::ImportLights(glTF2::Asset& r) +{ + if (!r.lights.Size()) + return; + + mScene->mNumLights = r.lights.Size(); + mScene->mLights = new aiLight*[r.lights.Size()]; + + for (size_t i = 0; i < r.lights.Size(); ++i) { + Light& light = r.lights[i]; + + aiLight* ail = mScene->mLights[i] = new aiLight(); + + switch (light.type) + { + case Light::Directional: + ail->mType = aiLightSource_DIRECTIONAL; break; + case Light::Point: + ail->mType = aiLightSource_POINT; break; + case Light::Spot: + ail->mType = aiLightSource_SPOT; break; + } + + if (ail->mType != aiLightSource_POINT) + { + ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f); + ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f); + } + + vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity }; + CopyValue(colorWithIntensity, ail->mColorAmbient); + CopyValue(colorWithIntensity, ail->mColorDiffuse); + CopyValue(colorWithIntensity, ail->mColorSpecular); + + if (ail->mType == aiLightSource_DIRECTIONAL) + { + ail->mAttenuationConstant = 1.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 0.0; + } + else + { + //in PBR attenuation is calculated using inverse square law which can be expressed + //using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters + //this is correct equation for the case when range (see + //https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual) + //is not present. When range is not present it is assumed that it is infinite and so numerator is 1. + //When range is present then numerator might be any value in range [0,1] and then assimps equation + //will not suffice. In this case range is added into metadata in ImportNode function + //and its up to implementation to read it when it wants to + ail->mAttenuationConstant = 0.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 1.0; + } + + if (ail->mType == aiLightSource_SPOT) + { + ail->mAngleInnerCone = light.innerConeAngle; + ail->mAngleOuterCone = light.outerConeAngle; + } + } +} + static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) { if (node.matrix.isPresent) { CopyValue(node.matrix.value, matrix); @@ -881,6 +944,18 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; } + if (node.light) { + pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; + + //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + //it is added to meta data of parent node, because there is no other place to put it + if (node.light->range.isPresent) + { + ainode->mMetaData = aiMetadata::Alloc(1); + ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value); + } + } + return ainode; } @@ -1150,6 +1225,7 @@ void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IO ImportMeshes(asset); ImportCameras(asset); + ImportLights(asset); ImportNodes(asset);