void ImportModel_GLTF(const std::string& fileName, Scene& scene) { string directory, name; wiHelper::SplitPath(fileName, directory, name); string extension = wiHelper::toUpper(wiHelper::GetExtensionFromFileName(name)); wiHelper::RemoveExtensionFromFileName(name); tinygltf::TinyGLTF loader; std::string err; std::string warn; loader.SetImageLoader(tinygltf::LoadImageData, nullptr); loader.SetImageWriter(tinygltf::WriteImageData, nullptr); LoaderState state; state.scene = &scene; bool ret; if (!extension.compare("GLTF")) { ret = loader.LoadASCIIFromFile(&state.gltfModel, &err, &warn, fileName); } else { ret = loader.LoadBinaryFromFile(&state.gltfModel, &err, &warn, fileName); // for binary glTF(.glb) } if (!ret) { wiHelper::messageBox(err, "GLTF error!"); } Entity rootEntity = CreateEntity(); scene.transforms.Create(rootEntity); // Create materials: for (auto& x : state.gltfModel.materials) { Entity materialEntity = scene.Entity_CreateMaterial(x.name); MaterialComponent& material = *scene.materials.GetComponent(materialEntity); material.baseColor = XMFLOAT4(1, 1, 1, 1); material.roughness = 1.0f; material.metalness = 1.0f; material.reflectance = 0.02f; // metallic-roughness workflow: auto& baseColorTexture = x.values.find("baseColorTexture"); auto& metallicRoughnessTexture = x.values.find("metallicRoughnessTexture"); auto& baseColorFactor = x.values.find("baseColorFactor"); auto& roughnessFactor = x.values.find("roughnessFactor"); auto& metallicFactor = x.values.find("metallicFactor"); // common workflow: auto& normalTexture = x.additionalValues.find("normalTexture"); auto& emissiveTexture = x.additionalValues.find("emissiveTexture"); auto& occlusionTexture = x.additionalValues.find("occlusionTexture"); auto& emissiveFactor = x.additionalValues.find("emissiveFactor"); auto& alphaCutoff = x.additionalValues.find("alphaCutoff"); auto& alphaMode = x.additionalValues.find("alphaMode"); if (baseColorTexture != x.values.end()) { auto& tex = state.gltfModel.textures[baseColorTexture->second.TextureIndex()]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "basecolor"); material.baseColorMapName = img.uri; material.uvset_baseColorMap = baseColorTexture->second.TextureTexCoord(); } if (normalTexture != x.additionalValues.end()) { auto& tex = state.gltfModel.textures[normalTexture->second.TextureIndex()]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "normal"); material.normalMapName = img.uri; material.SetFlipNormalMap(true); // gltf import will always flip normal map by default material.uvset_normalMap = normalTexture->second.TextureTexCoord(); } if (metallicRoughnessTexture != x.values.end()) { auto& tex = state.gltfModel.textures[metallicRoughnessTexture->second.TextureIndex()]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "roughness_metallic"); material.surfaceMapName = img.uri; material.uvset_surfaceMap = metallicRoughnessTexture->second.TextureTexCoord(); } if (emissiveTexture != x.additionalValues.end()) { auto& tex = state.gltfModel.textures[emissiveTexture->second.TextureIndex()]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "emissive"); material.emissiveMapName = img.uri; material.uvset_emissiveMap = emissiveTexture->second.TextureTexCoord(); } if (occlusionTexture != x.additionalValues.end()) { auto& tex = state.gltfModel.textures[occlusionTexture->second.TextureIndex()]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "occlusion"); material.occlusionMapName = img.uri; material.uvset_occlusionMap = occlusionTexture->second.TextureTexCoord(); material.SetOcclusionEnabled_Secondary(true); } if (baseColorFactor != x.values.end()) { material.baseColor.x = static_cast<float>(baseColorFactor->second.ColorFactor()[0]); material.baseColor.y = static_cast<float>(baseColorFactor->second.ColorFactor()[1]); material.baseColor.z = static_cast<float>(baseColorFactor->second.ColorFactor()[2]); material.baseColor.w = static_cast<float>(baseColorFactor->second.ColorFactor()[3]); } if (roughnessFactor != x.values.end()) { material.roughness = static_cast<float>(roughnessFactor->second.Factor()); } if (metallicFactor != x.values.end()) { material.metalness = static_cast<float>(metallicFactor->second.Factor()); } if (emissiveFactor != x.additionalValues.end()) { material.emissiveColor.x = static_cast<float>(emissiveFactor->second.ColorFactor()[0]); material.emissiveColor.y = static_cast<float>(emissiveFactor->second.ColorFactor()[1]); material.emissiveColor.z = static_cast<float>(emissiveFactor->second.ColorFactor()[2]); material.emissiveColor.w = static_cast<float>(emissiveFactor->second.ColorFactor()[3]); } if (alphaCutoff != x.additionalValues.end()) { material.alphaRef = 1 - static_cast<float>(alphaCutoff->second.Factor()); } if (alphaMode != x.additionalValues.end()) { if (alphaMode->second.string_value.compare("BLEND") == 0) { material.userBlendMode = BLENDMODE_ALPHA; } } // specular-glossiness workflow (todo): auto& specularGlossinessWorkflow = x.extensions.find("KHR_materials_pbrSpecularGlossiness"); if (specularGlossinessWorkflow != x.extensions.end()) { material.SetUseSpecularGlossinessWorkflow(true); if (specularGlossinessWorkflow->second.Has("diffuseTexture")) { int index = specularGlossinessWorkflow->second.Get("diffuseTexture").Get("index").Get<int>(); auto& tex = state.gltfModel.textures[index]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "diffuse"); material.baseColorMapName = img.uri; material.uvset_baseColorMap = (uint32_t)specularGlossinessWorkflow->second.Get("diffuseTexture").Get("texCoord").Get<int>(); } if (specularGlossinessWorkflow->second.Has("specularGlossinessTexture")) { int index = specularGlossinessWorkflow->second.Get("specularGlossinessTexture").Get("index").Get<int>(); auto& tex = state.gltfModel.textures[index]; auto& img = state.gltfModel.images[tex.source]; RegisterTexture2D(&img, "specular_glossiness"); material.surfaceMapName = img.uri; material.uvset_surfaceMap = (uint32_t)specularGlossinessWorkflow->second.Get("specularGlossinessTexture").Get("texCoord").Get<int>(); } if (specularGlossinessWorkflow->second.Has("diffuseFactor")) { auto& factor = specularGlossinessWorkflow->second.Get("diffuseFactor"); material.baseColor.x = factor.ArrayLen() > 0 ? float(factor.Get(0).IsNumber() ? factor.Get(0).Get<double>() : factor.Get(0).Get<int>()) : 1.0f; material.baseColor.y = factor.ArrayLen() > 1 ? float(factor.Get(1).IsNumber() ? factor.Get(1).Get<double>() : factor.Get(1).Get<int>()) : 1.0f; material.baseColor.z = factor.ArrayLen() > 2 ? float(factor.Get(2).IsNumber() ? factor.Get(2).Get<double>() : factor.Get(2).Get<int>()) : 1.0f; material.baseColor.w = factor.ArrayLen() > 3 ? float(factor.Get(3).IsNumber() ? factor.Get(3).Get<double>() : factor.Get(3).Get<int>()) : 1.0f; } //if (specularGlossinessWorkflow->second.Has("specularFactor")) //{ // auto& factor = specularGlossinessWorkflow->second.Get("specularFactor"); // material.baseColor.x = factor.ArrayLen() > 0 ? float(factor.Get(0).IsNumber() ? factor.Get(0).Get<double>() : factor.Get(0).Get<int>()) : 1.0f; // material.baseColor.y = factor.ArrayLen() > 0 ? float(factor.Get(1).IsNumber() ? factor.Get(1).Get<double>() : factor.Get(1).Get<int>()) : 1.0f; // material.baseColor.z = factor.ArrayLen() > 0 ? float(factor.Get(2).IsNumber() ? factor.Get(2).Get<double>() : factor.Get(2).Get<int>()) : 1.0f; // material.baseColor.w = factor.ArrayLen() > 0 ? float(factor.Get(3).IsNumber() ? factor.Get(3).Get<double>() : factor.Get(3).Get<int>()) : 1.0f; //} if (specularGlossinessWorkflow->second.Has("glossinessFactor")) { auto& factor = specularGlossinessWorkflow->second.Get("glossinessFactor"); material.roughness = 1 - float(factor.IsNumber() ? factor.Get<double>() : factor.Get<int>()); } } // Avoid zero roughness factors: material.roughness = max(0.001f, material.roughness); // Retrieve textures by name: if (!material.baseColorMapName.empty()) material.baseColorMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.baseColorMapName); if (!material.normalMapName.empty()) material.normalMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.normalMapName); if (!material.surfaceMapName.empty()) material.surfaceMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.surfaceMapName); if (!material.emissiveMapName.empty()) material.emissiveMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.emissiveMapName); if (!material.occlusionMapName.empty()) material.occlusionMap = (Texture2D*)wiResourceManager::GetGlobal().add(material.occlusionMapName); } if (scene.materials.GetCount() == 0) { scene.Entity_CreateMaterial("gltfimport_defaultMaterial"); } // Create meshes: for (auto& x : state.gltfModel.meshes) { Entity meshEntity = scene.Entity_CreateMesh(x.name); MeshComponent& mesh = *scene.meshes.GetComponent(meshEntity); for (auto& prim : x.primitives) { assert(prim.indices >= 0); // Fill indices: const tinygltf::Accessor& accessor = state.gltfModel.accessors[prim.indices]; const tinygltf::BufferView& bufferView = state.gltfModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer& buffer = state.gltfModel.buffers[bufferView.buffer]; int stride = accessor.ByteStride(bufferView); size_t indexCount = accessor.count; size_t indexOffset = mesh.indices.size(); mesh.indices.resize(indexOffset + indexCount); mesh.subsets.push_back(MeshComponent::MeshSubset()); mesh.subsets.back().indexOffset = (uint32_t)indexOffset; mesh.subsets.back().indexCount = (uint32_t)indexCount; mesh.subsets.back().materialID = scene.materials.GetEntity(max(0, prim.material)); uint32_t vertexOffset = (uint32_t)mesh.vertex_positions.size(); const unsigned char* data = buffer.data.data() + accessor.byteOffset + bufferView.byteOffset; int index_remap[3]; if (transform_to_LH) { index_remap[0] = 0; index_remap[1] = 1; index_remap[2] = 2; } else { index_remap[0] = 0; index_remap[1] = 2; index_remap[2] = 1; } if (stride == 1) { for (size_t i = 0; i < indexCount; i += 3) { mesh.indices[indexOffset + i + 0] = vertexOffset + data[i + index_remap[0]]; mesh.indices[indexOffset + i + 1] = vertexOffset + data[i + index_remap[1]]; mesh.indices[indexOffset + i + 2] = vertexOffset + data[i + index_remap[2]]; } } else if (stride == 2) { for (size_t i = 0; i < indexCount; i += 3) { mesh.indices[indexOffset + i + 0] = vertexOffset + ((uint16_t*)data)[i + index_remap[0]]; mesh.indices[indexOffset + i + 1] = vertexOffset + ((uint16_t*)data)[i + index_remap[1]]; mesh.indices[indexOffset + i + 2] = vertexOffset + ((uint16_t*)data)[i + index_remap[2]]; } } else if (stride == 4) { for (size_t i = 0; i < indexCount; i += 3) { mesh.indices[indexOffset + i + 0] = vertexOffset + ((uint32_t*)data)[i + index_remap[0]]; mesh.indices[indexOffset + i + 1] = vertexOffset + ((uint32_t*)data)[i + index_remap[1]]; mesh.indices[indexOffset + i + 2] = vertexOffset + ((uint32_t*)data)[i + index_remap[2]]; } } else { assert(0 && "unsupported index stride!"); } for (auto& attr : prim.attributes) { const string& attr_name = attr.first; int attr_data = attr.second; const tinygltf::Accessor& accessor = state.gltfModel.accessors[attr_data]; const tinygltf::BufferView& bufferView = state.gltfModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer& buffer = state.gltfModel.buffers[bufferView.buffer]; int stride = accessor.ByteStride(bufferView); size_t vertexCount = accessor.count; const unsigned char* data = buffer.data.data() + accessor.byteOffset + bufferView.byteOffset; if (!attr_name.compare("POSITION")) { mesh.vertex_positions.resize(vertexOffset + vertexCount); assert(stride == 12); for (size_t i = 0; i < vertexCount; ++i) { mesh.vertex_positions[vertexOffset + i] = ((XMFLOAT3*)data)[i]; } } else if (!attr_name.compare("NORMAL")) { mesh.vertex_normals.resize(vertexOffset + vertexCount); assert(stride == 12); for (size_t i = 0; i < vertexCount; ++i) { mesh.vertex_normals[vertexOffset + i] = ((XMFLOAT3*)data)[i]; } } else if (!attr_name.compare("TEXCOORD_0")) { mesh.vertex_uvset_0.resize(vertexOffset + vertexCount); assert(stride == 8); for (size_t i = 0; i < vertexCount; ++i) { const XMFLOAT2& tex = ((XMFLOAT2*)data)[i]; mesh.vertex_uvset_0[vertexOffset + i].x = tex.x; mesh.vertex_uvset_0[vertexOffset + i].y = tex.y; } } else if (!attr_name.compare("TEXCOORD_1")) { mesh.vertex_uvset_1.resize(vertexOffset + vertexCount); assert(stride == 8); for (size_t i = 0; i < vertexCount; ++i) { const XMFLOAT2& tex = ((XMFLOAT2*)data)[i]; mesh.vertex_uvset_1[vertexOffset + i].x = tex.x; mesh.vertex_uvset_1[vertexOffset + i].y = tex.y; } } else if (!attr_name.compare("JOINTS_0")) { mesh.vertex_boneindices.resize(vertexOffset + vertexCount); if (stride == 4) { struct JointTmp { uint8_t ind[4]; }; for (size_t i = 0; i < vertexCount; ++i) { const JointTmp& joint = ((JointTmp*)data)[i]; mesh.vertex_boneindices[vertexOffset + i].x = joint.ind[0]; mesh.vertex_boneindices[vertexOffset + i].y = joint.ind[1]; mesh.vertex_boneindices[vertexOffset + i].z = joint.ind[2]; mesh.vertex_boneindices[vertexOffset + i].w = joint.ind[3]; } } else if (stride == 8) { struct JointTmp { uint16_t ind[4]; }; for (size_t i = 0; i < vertexCount; ++i) { const JointTmp& joint = ((JointTmp*)data)[i]; mesh.vertex_boneindices[vertexOffset + i].x = joint.ind[0]; mesh.vertex_boneindices[vertexOffset + i].y = joint.ind[1]; mesh.vertex_boneindices[vertexOffset + i].z = joint.ind[2]; mesh.vertex_boneindices[vertexOffset + i].w = joint.ind[3]; } } else { assert(0); } } else if (!attr_name.compare("WEIGHTS_0")) { mesh.vertex_boneweights.resize(vertexOffset + vertexCount); assert(stride == 16); for (size_t i = 0; i < vertexCount; ++i) { mesh.vertex_boneweights[vertexOffset + i] = ((XMFLOAT4*)data)[i]; } } else if (!attr_name.compare("COLOR_0")) { mesh.vertex_colors.resize(vertexOffset + vertexCount); assert(stride == 16); for (size_t i = 0; i < vertexCount; ++i) { const XMFLOAT4& color = ((XMFLOAT4*)data)[i]; uint32_t rgba = wiMath::CompressColor(color); mesh.vertex_colors[vertexOffset + i] = rgba; } } } } mesh.CreateRenderData(); } // Create armatures: for (auto& skin : state.gltfModel.skins) { Entity armatureEntity = CreateEntity(); scene.names.Create(armatureEntity) = skin.name; scene.layers.Create(armatureEntity); scene.transforms.Create(armatureEntity); ArmatureComponent& armature = scene.armatures.Create(armatureEntity); if (skin.inverseBindMatrices >= 0) { const tinygltf::Accessor &accessor = state.gltfModel.accessors[skin.inverseBindMatrices]; const tinygltf::BufferView &bufferView = state.gltfModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer &buffer = state.gltfModel.buffers[bufferView.buffer]; armature.inverseBindMatrices.resize(accessor.count); memcpy(armature.inverseBindMatrices.data(), &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(XMFLOAT4X4)); } else { assert(0); } } // Create transform hierarchy, assign objects, meshes, armatures, cameras: const tinygltf::Scene &gltfScene = state.gltfModel.scenes[max(0, state.gltfModel.defaultScene)]; for (size_t i = 0; i < gltfScene.nodes.size(); i++) { LoadNode(gltfScene.nodes[i], rootEntity, state); } // Create armature-bone mappings: int armatureIndex = 0; for (auto& skin : state.gltfModel.skins) { ArmatureComponent& armature = scene.armatures[armatureIndex++]; const size_t jointCount = skin.joints.size(); armature.boneCollection.resize(jointCount); // Create bone collection: for (size_t i = 0; i < jointCount; ++i) { int jointIndex = skin.joints[i]; Entity boneEntity = state.entityMap[jointIndex]; armature.boneCollection[i] = boneEntity; } } // Create animations: for (auto& anim : state.gltfModel.animations) { Entity entity = CreateEntity(); scene.names.Create(entity) = anim.name; AnimationComponent& animationcomponent = scene.animations.Create(entity); animationcomponent.samplers.resize(anim.samplers.size()); animationcomponent.channels.resize(anim.channels.size()); for (size_t i = 0; i < anim.samplers.size(); ++i) { auto& sam = anim.samplers[i]; if (!sam.interpolation.compare("LINEAR")) { animationcomponent.samplers[i].mode = AnimationComponent::AnimationSampler::Mode::LINEAR; } else if (!sam.interpolation.compare("STEP")) { animationcomponent.samplers[i].mode = AnimationComponent::AnimationSampler::Mode::STEP; } // AnimationSampler input = keyframe times { const tinygltf::Accessor& accessor = state.gltfModel.accessors[sam.input]; const tinygltf::BufferView& bufferView = state.gltfModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer& buffer = state.gltfModel.buffers[bufferView.buffer]; assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); int stride = accessor.ByteStride(bufferView); size_t count = accessor.count; animationcomponent.samplers[i].keyframe_times.resize(count); const unsigned char* data = buffer.data.data() + accessor.byteOffset + bufferView.byteOffset; assert(stride == 4); for (size_t j = 0; j < count; ++j) { float time = ((float*)data)[j]; animationcomponent.samplers[i].keyframe_times[j] = time; animationcomponent.start = min(animationcomponent.start, time); animationcomponent.end = max(animationcomponent.end, time); } } // AnimationSampler output = keyframe data { const tinygltf::Accessor& accessor = state.gltfModel.accessors[sam.output]; const tinygltf::BufferView& bufferView = state.gltfModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer& buffer = state.gltfModel.buffers[bufferView.buffer]; int stride = accessor.ByteStride(bufferView); size_t count = accessor.count; const unsigned char* data = buffer.data.data() + accessor.byteOffset + bufferView.byteOffset; switch (accessor.type) { case TINYGLTF_TYPE_VEC3: { assert(stride == sizeof(XMFLOAT3)); animationcomponent.samplers[i].keyframe_data.resize(count * 3); for (size_t j = 0; j < count; ++j) { ((XMFLOAT3*)animationcomponent.samplers[i].keyframe_data.data())[j] = ((XMFLOAT3*)data)[j]; } } break; case TINYGLTF_TYPE_VEC4: { assert(stride == sizeof(XMFLOAT4)); animationcomponent.samplers[i].keyframe_data.resize(count * 4); for (size_t j = 0; j < count; ++j) { ((XMFLOAT4*)animationcomponent.samplers[i].keyframe_data.data())[j] = ((XMFLOAT4*)data)[j]; } } break; default: assert(0); break; } } } for (size_t i = 0; i < anim.channels.size(); ++i) { auto& channel = anim.channels[i]; animationcomponent.channels[i].target = state.entityMap[channel.target_node]; assert(channel.sampler >= 0); animationcomponent.channels[i].samplerIndex = (uint32_t)channel.sampler; if (!channel.target_path.compare("scale")) { animationcomponent.channels[i].path = AnimationComponent::AnimationChannel::Path::SCALE; } else if (!channel.target_path.compare("rotation")) { animationcomponent.channels[i].path = AnimationComponent::AnimationChannel::Path::ROTATION; } else if (!channel.target_path.compare("translation")) { animationcomponent.channels[i].path = AnimationComponent::AnimationChannel::Path::TRANSLATION; } else { animationcomponent.channels[i].path = AnimationComponent::AnimationChannel::Path::UNKNOWN; } } } if (transform_to_LH) { TransformComponent& transform = *scene.transforms.GetComponent(rootEntity); transform.scale_local.z = -transform.scale_local.z; transform.SetDirty(); } scene.Update(0); }