glTF 2.0 Lights import

Importing of lights according to glTF 2.0 extension KHR_lights_punctual https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual

Since glTF lights are based on PBR they use different attenuation model than conventional lights supported by assimp. It is possible to use attenuation factors in assimp to describe inverse square law fallof. But the light structure does not provide means to save range property. Therefore I resorted to use of metadata. When range parameter is present, I put it into 'PBR_LightRange' metadata of light's node. Please, see comment in glTF2Importer file.
pull/2578/head
petrmohelnik 2019-08-03 20:51:00 +02:00
parent 1c3e934ae9
commit 9330cca1cd
4 changed files with 159 additions and 4 deletions

View File

@ -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<float> 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<vec3> scale;
Ref<Camera> camera;
Ref<Light> light;
std::vector< Ref<Node> > skeletons; //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy.
Ref<Skin> 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<Buffer> buffers;
LazyDict<BufferView> bufferViews;
LazyDict<Camera> cameras;
LazyDict<Light> lights;
LazyDict<Image> images;
LazyDict<Material> materials;
LazyDict<Mesh> 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")

View File

@ -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
}

View File

@ -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) {

View File

@ -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<unsigned int>&
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);