/* * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION * SPDX-License-Identifier: Apache-2.0 */ /** \namespace nvh::gltf These utilities are for loading glTF models in a canonical scene representation. From this representation you would create the appropriate 3D API resources (buffers and textures). \code{.cpp} // Typical Usage // Load the GLTF Scene using TinyGLTF tinygltf::Model gltfModel; tinygltf::TinyGLTF gltfContext; fileLoaded = gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warn, m_filename); // Fill the data in the gltfScene gltfScene.getMaterials(tmodel); gltfScene.getDrawableNodes(tmodel, GltfAttributes::Normal | GltfAttributes::Texcoord_0); // Todo in App: // create buffers for vertices and indices, from gltfScene.m_position, gltfScene.m_index // create textures from images: using tinygltf directly // create descriptorSet for material using directly gltfScene.m_materials \endcode */ #pragma once #include "nvmath/nvmath.h" #include "tiny_gltf.h" #include #include #include #include #include #include #include #include #include #define KHR_LIGHTS_PUNCTUAL_EXTENSION_NAME "KHR_lights_punctual" namespace nvh { // https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/README.md #define KHR_MATERIALS_PBRSPECULARGLOSSINESS_EXTENSION_NAME "KHR_materials_pbrSpecularGlossiness" struct KHR_materials_pbrSpecularGlossiness { nvmath::vec4f diffuseFactor{1.f, 1.f, 1.f, 1.f}; int diffuseTexture{-1}; nvmath::vec3f specularFactor{1.f, 1.f, 1.f}; float glossinessFactor{1.f}; int specularGlossinessTexture{-1}; }; // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md #define KHR_MATERIALS_SPECULAR_EXTENSION_NAME "KHR_materials_specular" struct KHR_materials_specular { float specularFactor{1.f}; int specularTexture{-1}; nvmath::vec3f specularColorFactor{1.f, 1.f, 1.f}; int specularColorTexture{-1}; }; // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform #define KHR_TEXTURE_TRANSFORM_EXTENSION_NAME "KHR_texture_transform" struct KHR_texture_transform { nvmath::vec2f offset{0.f, 0.f}; float rotation{0.f}; nvmath::vec2f scale{1.f}; int texCoord{0}; nvmath::mat3f uvTransform{1}; // Computed transform of offset, rotation, scale }; // https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md #define KHR_MATERIALS_CLEARCOAT_EXTENSION_NAME "KHR_materials_clearcoat" struct KHR_materials_clearcoat { float factor{0.f}; int texture{-1}; float roughnessFactor{0.f}; int roughnessTexture{-1}; int normalTexture{-1}; }; // https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_sheen/README.md #define KHR_MATERIALS_SHEEN_EXTENSION_NAME "KHR_materials_sheen" struct KHR_materials_sheen { nvmath::vec3f colorFactor{0.f, 0.f, 0.f}; int colorTexture{-1}; float roughnessFactor{0.f}; int roughnessTexture{-1}; }; // https://github.com/DassaultSystemes-Technology/glTF/tree/KHR_materials_volume/extensions/2.0/Khronos/KHR_materials_transmission #define KHR_MATERIALS_TRANSMISSION_EXTENSION_NAME "KHR_materials_transmission" struct KHR_materials_transmission { float factor{0.f}; int texture{-1}; }; // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit #define KHR_MATERIALS_UNLIT_EXTENSION_NAME "KHR_materials_unlit" struct KHR_materials_unlit { int active{0}; }; // PBR Next : KHR_materials_anisotropy #define KHR_MATERIALS_ANISOTROPY_EXTENSION_NAME "KHR_materials_anisotropy" struct KHR_materials_anisotropy { float factor{0.f}; nvmath::vec3f direction{1.f, 0.f, 0.f}; int texture{-1}; }; // https://github.com/DassaultSystemes-Technology/glTF/tree/KHR_materials_ior/extensions/2.0/Khronos/KHR_materials_ior #define KHR_MATERIALS_IOR_EXTENSION_NAME "KHR_materials_ior" struct KHR_materials_ior { float ior{1.5f}; }; // https://github.com/DassaultSystemes-Technology/glTF/tree/KHR_materials_volume/extensions/2.0/Khronos/KHR_materials_volume #define KHR_MATERIALS_VOLUME_EXTENSION_NAME "KHR_materials_volume" struct KHR_materials_volume { float thicknessFactor{0}; int thicknessTexture{-1}; float attenuationDistance{std::numeric_limits::max()}; nvmath::vec3f attenuationColor{1.f, 1.f, 1.f}; }; // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_texture_basisu/README.md #define KHR_TEXTURE_BASISU_NAME "KHR_texture_basisu" struct KHR_texture_basisu { int source{-1}; }; // https://github.com/KhronosGroup/glTF/issues/948 #define KHR_MATERIALS_DISPLACEMENT_NAME "KHR_materials_displacement" struct KHR_materials_displacement { float displacementGeometryFactor{1.0f}; float displacementGeometryOffset{0.0f}; int displacementGeometryTexture{-1}; }; // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#reference-material struct GltfMaterial { int shadingModel{0}; // 0: metallic-roughness, 1: specular-glossiness // pbrMetallicRoughness nvmath::vec4f baseColorFactor{1.f, 1.f, 1.f, 1.f}; int baseColorTexture{-1}; float metallicFactor{1.f}; float roughnessFactor{1.f}; int metallicRoughnessTexture{-1}; int emissiveTexture{-1}; nvmath::vec3f emissiveFactor{0, 0, 0}; int alphaMode{0}; float alphaCutoff{0.5f}; int doubleSided{0}; int normalTexture{-1}; float normalTextureScale{1.f}; int occlusionTexture{-1}; float occlusionTextureStrength{1}; // Extensions KHR_materials_pbrSpecularGlossiness specularGlossiness; KHR_materials_specular specular; KHR_texture_transform textureTransform; KHR_materials_clearcoat clearcoat; KHR_materials_sheen sheen; KHR_materials_transmission transmission; KHR_materials_unlit unlit; KHR_materials_anisotropy anisotropy; KHR_materials_ior ior; KHR_materials_volume volume; KHR_materials_displacement displacement; // Tiny Reference const tinygltf::Material* tmaterial{nullptr}; }; struct GltfNode { nvmath::mat4f worldMatrix{1}; int primMesh{0}; const tinygltf::Node* tnode{nullptr}; }; struct GltfPrimMesh { uint32_t firstIndex{0}; uint32_t indexCount{0}; uint32_t vertexOffset{0}; uint32_t vertexCount{0}; int materialIndex{0}; nvmath::vec3f posMin{0, 0, 0}; nvmath::vec3f posMax{0, 0, 0}; std::string name; // Tiny Reference const tinygltf::Mesh* tmesh{nullptr}; const tinygltf::Primitive* tprim{nullptr}; }; struct GltfStats { uint32_t nbCameras{0}; uint32_t nbImages{0}; uint32_t nbTextures{0}; uint32_t nbMaterials{0}; uint32_t nbSamplers{0}; uint32_t nbNodes{0}; uint32_t nbMeshes{0}; uint32_t nbLights{0}; uint32_t imageMem{0}; uint32_t nbUniqueTriangles{0}; uint32_t nbTriangles{0}; }; struct GltfCamera { nvmath::mat4f worldMatrix{1}; nvmath::vec3f eye{0, 0, 0}; nvmath::vec3f center{0, 0, 0}; nvmath::vec3f up{0, 1, 0}; tinygltf::Camera cam; }; // See: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md struct GltfLight { nvmath::mat4f worldMatrix{1}; tinygltf::Light light; }; enum class GltfAttributes : uint8_t { NoAttribs = 0, Position = 1, Normal = 2, Texcoord_0 = 4, Texcoord_1 = 8, Tangent = 16, Color_0 = 32, All = 0xFF }; using GltfAttributes_t = std::underlying_type_t; inline GltfAttributes operator|(GltfAttributes lhs, GltfAttributes rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } inline GltfAttributes operator&(GltfAttributes lhs, GltfAttributes rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } //-------------------------------------------------------------------------------------------------- // Class to convert gltfScene in simple draw-able format // struct GltfScene { // Importing all materials in a vector of GltfMaterial structure void importMaterials(const tinygltf::Model& tmodel); // Import all Mesh and primitives in a vector of GltfPrimMesh, // - Reads all requested GltfAttributes and create them if `forceRequested` contains it. // - Create a vector of GltfNode, GltfLight and GltfCamera void importDrawableNodes(const tinygltf::Model& tmodel, GltfAttributes requestedAttributes, GltfAttributes forceRequested = GltfAttributes::All); void exportDrawableNodes(tinygltf::Model& tmodel, GltfAttributes requestedAttributes); // Compute the scene bounding box void computeSceneDimensions(); // Removes everything void destroy(); static GltfStats getStatistics(const tinygltf::Model& tinyModel); // Scene data std::vector m_materials; // Material for shading std::vector m_nodes; // Drawable nodes, flat hierarchy std::vector m_primMeshes; // Primitive promoted to meshes std::vector m_cameras; std::vector m_lights; // Attributes, all same length if valid std::vector m_positions; std::vector m_indices; std::vector m_normals; std::vector m_tangents; std::vector m_texcoords0; std::vector m_texcoords1; std::vector m_colors0; // #TODO - Adding support for Skinning //using vec4us = vector4; //std::vector m_joints0; //std::vector m_weights0; // Size of the scene struct Dimensions { nvmath::vec3f min = nvmath::vec3f(std::numeric_limits::max()); nvmath::vec3f max = nvmath::vec3f(std::numeric_limits::min()); nvmath::vec3f size{0.f}; nvmath::vec3f center{0.f}; float radius{0}; } m_dimensions; private: void processNode(const tinygltf::Model& tmodel, int& nodeIdx, const nvmath::mat4f& parentMatrix); void processMesh(const tinygltf::Model& tmodel, const tinygltf::Primitive& tmesh, GltfAttributes requestedAttributes, GltfAttributes forceRequested, const std::string& name); void createNormals(GltfPrimMesh& resultMesh); void createTexcoords(GltfPrimMesh& resultMesh); void createTangents(GltfPrimMesh& resultMesh); void createColors(GltfPrimMesh& resultMesh); // Temporary data std::unordered_map> m_meshToPrimMeshes; std::vector primitiveIndices32u; std::vector primitiveIndices16u; std::vector primitiveIndices8u; std::unordered_map m_cachePrimMesh; void computeCamera(); void checkRequiredExtensions(const tinygltf::Model& tmodel); void findUsedMeshes(const tinygltf::Model& tmodel, std::set& usedMeshes, int nodeIdx); }; nvmath::mat4f getLocalMatrix(const tinygltf::Node& tnode); // Return a vector of data for a tinygltf::Value template static inline std::vector getVector(const tinygltf::Value& value) { std::vector result{0}; if(!value.IsArray()) return result; result.resize(value.ArrayLen()); for(int i = 0; i < value.ArrayLen(); i++) { result[i] = static_cast(value.Get(i).IsNumber() ? value.Get(i).Get() : value.Get(i).Get()); } return result; } static inline void getFloat(const tinygltf::Value& value, const std::string& name, float& val) { if(value.Has(name)) { val = static_cast(value.Get(name).Get()); } } static inline void getInt(const tinygltf::Value& value, const std::string& name, int& val) { if(value.Has(name)) { val = value.Get(name).Get(); } } static inline void getVec2(const tinygltf::Value& value, const std::string& name, nvmath::vec2f& val) { if(value.Has(name)) { auto s = getVector(value.Get(name)); val = nvmath::vec2f{s[0], s[1]}; } } static inline void getVec3(const tinygltf::Value& value, const std::string& name, nvmath::vec3f& val) { if(value.Has(name)) { auto s = getVector(value.Get(name)); val = nvmath::vec3f{s[0], s[1], s[2]}; } } static inline void getVec4(const tinygltf::Value& value, const std::string& name, nvmath::vec4f& val) { if(value.Has(name)) { auto s = getVector(value.Get(name)); val = nvmath::vec4f{s[0], s[1], s[2], s[3]}; } } static inline void getTexId(const tinygltf::Value& value, const std::string& name, int& val) { if(value.Has(name)) { val = value.Get(name).Get("index").Get(); } } // Calls a function (such as a lambda function) for each (index, value) pair in // a sparse accessor. It's only potentially called for indices from // accessorFirstElement through accessorFirstElement + numElementsToProcess - 1. template void forEachSparseValue(const tinygltf::Model& tmodel, const tinygltf::Accessor& accessor, size_t accessorFirstElement, size_t numElementsToProcess, std::function fn) { if(!accessor.sparse.isSparse) { return; // Nothing to do } const auto& idxs = accessor.sparse.indices; if(!(idxs.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE // || idxs.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT // || idxs.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)) { assert(!"Unsupported sparse accessor index type."); return; } const tinygltf::BufferView& idxBufferView = tmodel.bufferViews[idxs.bufferView]; const unsigned char* idxBuffer = &tmodel.buffers[idxBufferView.buffer].data[idxBufferView.byteOffset]; const size_t idxBufferByteStride = idxBufferView.byteStride ? idxBufferView.byteStride : tinygltf::GetComponentSizeInBytes(idxs.componentType); if(idxBufferByteStride == size_t(-1)) return; // Invalid const auto& vals = accessor.sparse.values; const tinygltf::BufferView& valBufferView = tmodel.bufferViews[vals.bufferView]; const unsigned char* valBuffer = &tmodel.buffers[valBufferView.buffer].data[valBufferView.byteOffset]; const size_t valBufferByteStride = accessor.ByteStride(valBufferView); if(valBufferByteStride == size_t(-1)) return; // Invalid // Note that this could be faster for lots of small copies, since we could // binary search for the first sparse accessor index to use (since the // glTF specification requires the indices be sorted)! for(size_t pairIdx = 0; pairIdx < accessor.sparse.count; pairIdx++) { // Read the index from the index buffer, converting its type size_t index = 0; const unsigned char* pIdx = idxBuffer + idxBufferByteStride * pairIdx; switch(idxs.componentType) { case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: index = *reinterpret_cast(pIdx); break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: index = *reinterpret_cast(pIdx); break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: index = *reinterpret_cast(pIdx); break; } // If it's not in range, skip it if(index < accessorFirstElement || (index - accessorFirstElement) >= numElementsToProcess) { continue; } fn(index, reinterpret_cast(valBuffer + valBufferByteStride * pairIdx)); } } // Copies accessor elements accessorFirstElement through // accessorFirstElement + numElementsToCopy - 1 to outData elements // outFirstElement through outFirstElement + numElementsToCopy - 1. // This handles sparse accessors correctly! It's intended as a replacement for // what would be memcpy(..., &buffer.data[...], ...) calls. // // However, it performs no conversion: it assumes (but does not check) that // accessor's elements are of type T. For instance, T should be a struct of two // floats for a VEC2 float accessor. // // This is range-checked, so elements that would be out-of-bounds are not // copied. We assume size_t overflow does not occur. // Note that outDataSizeInT is the number of elements in the outDataBuffer, // while numElementsToCopy is the number of elements to copy, not the number // of elements in accessor. template void copyAccessorData(T* outData, size_t outDataSizeInElements, size_t outFirstElement, const tinygltf::Model& tmodel, const tinygltf::Accessor& accessor, size_t accessorFirstElement, size_t numElementsToCopy) { if(outFirstElement >= outDataSizeInElements) { assert(!"Invalid outFirstElement!"); return; } if(accessorFirstElement >= accessor.count) { assert(!"Invalid accessorFirstElement!"); return; } const tinygltf::BufferView& bufferView = tmodel.bufferViews[accessor.bufferView]; const unsigned char* buffer = &tmodel.buffers[bufferView.buffer].data[accessor.byteOffset + bufferView.byteOffset]; const size_t maxSafeCopySize = std::min(accessor.count - accessorFirstElement, outDataSizeInElements - outFirstElement); numElementsToCopy = std::min(numElementsToCopy, maxSafeCopySize); if(bufferView.byteStride == 0) { memcpy(outData + outFirstElement, reinterpret_cast(buffer) + accessorFirstElement, numElementsToCopy * sizeof(T)); } else { // Must copy one-by-one for(size_t i = 0; i < numElementsToCopy; i++) { outData[outFirstElement + i] = *reinterpret_cast(buffer + bufferView.byteStride * i); } } // Handle sparse accessors by overwriting already copied elements. forEachSparseValue(tmodel, accessor, accessorFirstElement, numElementsToCopy, [&outData](size_t index, const T* value) { outData[index] = *value; }); } // Same as copyAccessorData(T*, ...), but taking a vector. template void copyAccessorData(std::vector& outData, size_t outFirstElement, const tinygltf::Model& tmodel, const tinygltf::Accessor& accessor, size_t accessorFirstElement, size_t numElementsToCopy) { copyAccessorData(outData.data(), outData.size(), outFirstElement, tmodel, accessor, accessorFirstElement, numElementsToCopy); } // Appending to \p attribVec, all the values of \p accessor // Return false if the accessor is invalid. // T must be nvmath::vec2f, nvmath::vec3f, or nvmath::vec4f. template static bool getAccessorData(const tinygltf::Model& tmodel, const tinygltf::Accessor& accessor, std::vector& attribVec) { // Retrieving the data of the accessor const auto nbElems = accessor.count; const size_t oldNumElements = attribVec.size(); attribVec.resize(oldNumElements + nbElems); // Copying the attributes if(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { copyAccessorData(attribVec, oldNumElements, tmodel, accessor, 0, accessor.count); } else { // The component is smaller than float and need to be converted const auto& bufView = tmodel.bufferViews[accessor.bufferView]; const auto& buffer = tmodel.buffers[bufView.buffer]; const unsigned char* bufferByte = &buffer.data[accessor.byteOffset + bufView.byteOffset]; // 2, 3, 4 for VEC2, VEC3, VEC4 const int nbComponents = tinygltf::GetNumComponentsInType(accessor.type); if(nbComponents == -1) return false; // Invalid // Stride per element const size_t byteStride = accessor.ByteStride(bufView); if(byteStride == size_t(-1)) return false; // Invalid if(!(accessor.componentType == TINYGLTF_COMPONENT_TYPE_BYTE || accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE || accessor.componentType == TINYGLTF_COMPONENT_TYPE_SHORT || accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)) { assert(!"Unhandled tinygltf component type!"); return false; } const auto& copyElementFn = [&](size_t elementIdx, const unsigned char* pElement) { T vecValue; for(int c = 0; c < nbComponents; c++) { switch(accessor.componentType) { case TINYGLTF_COMPONENT_TYPE_BYTE: vecValue[c] = float(*(reinterpret_cast(pElement) + c)); if(accessor.normalized) { vecValue[c] = std::max(vecValue[c] / 127.f, -1.f); } break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: vecValue[c] = float(*(reinterpret_cast(pElement) + c)); if(accessor.normalized) { vecValue[c] = vecValue[c] / 255.f; } break; case TINYGLTF_COMPONENT_TYPE_SHORT: vecValue[c] = float(*(reinterpret_cast(pElement) + c)); if(accessor.normalized) { vecValue[c] = std::max(vecValue[c] / 32767.f, -1.f); } break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: vecValue[c] = float(*(reinterpret_cast(pElement) + c)); if(accessor.normalized) { vecValue[c] = vecValue[c] / 65535.f; } break; } } attribVec[oldNumElements + elementIdx] = vecValue; }; for(size_t i = 0; i < nbElems; i++) { copyElementFn(i, bufferByte + byteStride * i); } forEachSparseValue(tmodel, accessor, 0, nbElems, copyElementFn); } return true; } // Appending to \p attribVec, all the values of \p attribName // Return false if the attribute is missing or invalid. // T must be nvmath::vec2f, nvmath::vec3f, or nvmath::vec4f. template static bool getAttribute(const tinygltf::Model& tmodel, const tinygltf::Primitive& primitive, std::vector& attribVec, const std::string& attribName) { const auto& it = primitive.attributes.find(attribName); if(it == primitive.attributes.end()) return false; const auto& accessor = tmodel.accessors[it->second]; return getAccessorData(tmodel, accessor, attribVec); } inline bool hasExtension(const tinygltf::ExtensionMap& extensions, const std::string& name) { return extensions.find(name) != extensions.end(); } // This is appending the incoming data to the binary buffer (just one) // and return the amount in byte of data that was added. template uint32_t appendData(tinygltf::Buffer& buffer, const T& inData) { auto* pData = reinterpret_cast(inData.data()); uint32_t len = static_cast(sizeof(inData[0]) * inData.size()); buffer.data.insert(buffer.data.end(), pData, pData + len); return len; } } // namespace nvh