Introduce the gltfio library.

This is our new mobile-friendly and web-friendly library for loading
glTF assets. It is still a work in progress, but already capable of
loading many conformance models, including those with animation,
skinning, and a couple of extensions (nonlit and texture transforms).

Next week we will add a sample app demonstrating its usage.
This commit is contained in:
Philip Rideout
2019-03-01 07:27:03 -08:00
parent 13cd77bc9a
commit c7848efd86
17 changed files with 3034 additions and 1 deletions
+1
View File
@@ -385,6 +385,7 @@ endif()
if (NOT ANDROID AND NOT WEBGL AND NOT IOS)
add_subdirectory(${LIBRARIES}/bluegl)
add_subdirectory(${LIBRARIES}/filagui)
add_subdirectory(${LIBRARIES}/gltfio)
add_subdirectory(${LIBRARIES}/imageio)
add_subdirectory(${FILAMENT}/java/filamat)
+2 -1
View File
@@ -125,8 +125,9 @@ and tools.
- `filaflat`: Serialization/deserialization library used for materials
- `filagui`: Helper library for [Dear ImGui](https://github.com/ocornut/imgui)
- `filamat`: Material generation library
- `filameshio`: Tiny mesh parsing library (see also `tools/filamesh`)
- `filameshio`: Tiny filamesh parsing library (see also `tools/filamesh`)
- `geometry`: Mesh-related utilities
- `gltfio`: Loader for glTF 2.0
- `image`: Image filtering and simple transforms
- `imageio`: Image file reading / writing, only intended for internal use
- `math`: Math library
+56
View File
@@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.1)
project(gltfio)
set(TARGET gltfio)
set(PUBLIC_HDR_DIR include)
# ==================================================================================================
# Sources and headers
# ==================================================================================================
set(PUBLIC_HDRS
include/gltfio/Animator.h
include/gltfio/AssetLoader.h
include/gltfio/ResourceLoader.h
include/gltfio/FilamentAsset.h
)
set(SRCS
src/Animator.cpp
src/AssetLoader.cpp
src/ResourceLoader.cpp
src/FFilamentAsset.h
src/FilamentAsset.cpp
src/GltfEnums.h
src/MaterialGenerator.cpp
src/MaterialGenerator.h
src/math.h
src/upcast.h
)
# ==================================================================================================
# Include and target definitions
# ==================================================================================================
include_directories(${PUBLIC_HDR_DIR})
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS})
target_link_libraries(${TARGET} PUBLIC math utils filamat filament cgltf stb geometry)
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
# ==================================================================================================
# Compiler flags
# ==================================================================================================
target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register)
if (MSVC OR CLANG_CL)
target_compile_options(${TARGET} PRIVATE $<$<CONFIG:Release>:/fp:fast>)
else()
target_compile_options(${TARGET} PRIVATE $<$<CONFIG:Release>:-ffast-math>)
endif()
# ==================================================================================================
# Installation
# ==================================================================================================
install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR})
install(DIRECTORY ${PUBLIC_HDR_DIR}/gltfio DESTINATION include)
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_ANIMATOR_H
#define GLTFIO_ANIMATOR_H
#include <gltfio/FilamentAsset.h>
namespace gltfio {
namespace details { class FFilamentAsset; }
struct AnimatorImpl;
/**
* Animator can be used for two things: (1) updating matrices in Transform components
* according to glTF animation definitions and (2) updating bone matrices in Renderable components
* according to glTF skin definitions.
*
* For a usage example, see the comment block for AssetLoader.
*
* TODO: add support for morphing.
*/
class Animator {
public:
/**
* Uses TransformManager to apply rotation, translation, and scale to entities that have
* been targeted by the given animation definition.
*/
void applyAnimation(size_t animationIndex, float time) const;
/**
* Uses TransformManager to compute root-to-node transforms for all bone nodes, then passes
* the results into RenderableManager::setBones.
*
* Note that this operation is actually independent of animation, but the Animator seems
* like a reasonable place for a utility like this.
*/
void updateBoneMatrices();
size_t getAnimationCount() const;
float getAnimationDuration(size_t animationIndex) const;
const char* getAnimationName(size_t animationIndex) const;
private:
friend class details::FFilamentAsset;
Animator(FilamentAsset* asset);
~Animator();
AnimatorImpl* mImpl;
};
} // namespace gltfio
#endif // GLTFIO_ANIMATOR_H
+145
View File
@@ -0,0 +1,145 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_ASSETLOADER_H
#define GLTFIO_ASSETLOADER_H
#include <filament/Engine.h>
#include <filament/Material.h>
#include <gltfio/FilamentAsset.h>
#include <utils/NameComponentManager.h>
namespace gltfio {
/**
* AssetLoader consumes a blob of glTF 2.0 content (either JSON or GLB) and produces an "asset",
* which is a bundle of Filament entities, material instances, textures, vertex buffers, and index
* buffers.
*
* AssetLoader does not fetch external buffer data or create textures on its own. Clients can use
* the provided ResourceLoader class for this, which obtains the URI list from the asset. This is
* demonstrated in the code snippet below.
*
* AssetLoader also owns a cache of Material objects that may be re-used across multiple loads.
*
* TODO: currently this uses filamat to generate materials on the fly, but we may wish to allow
* clients to load a small set of precompiled ubershader materials to avoid the footprint of the
* filamat library on resource-constrained platforms.
*
* Example usage:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* auto engine = Engine::create();
* auto loader = AssetLoader::create(engine);
*
* // Parse the glTF content and create Filament entities.
* std::vector<uint8_t> content(...);
* FilamentAsset* asset = loader->createAssetFromJson(content.data(), content.size());
* content.clear();
*
* // Load buffers and textures from disk.
* ResourceLoader({engine, ".", true}).loadResources(asset);
*
* // Obtain the simple animation interface.
* Animator* animator = asset->getAnimator();
*
* // Free the glTF hierarchy as it is no longer needed.
* asset->releaseSourceData();
*
* // Add renderables to the scene.
* scene->addEntities(asset->getEntities(), asset->getEntityCount());
*
* // Execute the render loop and play the first animation.
* do {
* animator->applyAnimation(0, time);
* animator->updateBoneMatrices();
* if (renderer->beginFrame(swapChain)) {
* renderer->render(view);
* renderer->endFrame();
* }
* } while (!quit);
*
* loader->destroyAsset(asset);
* loader->destroyMaterials();
* AssetLoader::destroy(&loader);
* Engine::destroy(&engine);
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
class AssetLoader {
public:
/**
* Creates an asset loader for the given engine.
*
* The engine is held weakly, used only for the creation and destruction of Filament objects.
* The optional name component manager can be used to assign names to renderables.
*/
static AssetLoader* create(filament::Engine* engine, utils::NameComponentManager* = nullptr);
/**
* Frees the loader.
*
* This does not not automatically free the cache of materials (see destroyMaterials), nor
* does it free the entities for created assets (see destroyAsset).
*/
static void destroy(AssetLoader** loader);
/**
* Takes a pointer to the contents of a JSON-based glTF 2.0 file and returns a bundle
* of Filament objects. Returns null on failure.
*/
FilamentAsset* createAssetFromJson(uint8_t const* bytes, uint32_t nbytes);
/**
* Takes a pointer to the contents of a GLB glTF 2.0 file and returns a bundle
* of Filament objects. Returns null on failure.
*/
FilamentAsset* createAssetFromBinary(uint8_t const* bytes, uint32_t nbytes);
/** Destroys the given asset and all of its associated Filament objects. */
void destroyAsset(const FilamentAsset* asset);
/**
* Enables or disables shadows on all subsequently loaded assets.
*
* Initially, loaded assets will cast and receive shadows.
*/
void castShadowsByDefault(bool enable);
void receiveShadowsByDefault(bool enable);
/** Gets cached materials, used internally to create material instances for assets. */
size_t getMaterialsCount() const noexcept;
const filament::Material* const* getMaterials() const noexcept;
/** Destroys all cached materials. */
void destroyMaterials();
protected:
AssetLoader() noexcept = default;
~AssetLoader() = default;
public:
AssetLoader(AssetLoader const&) = delete;
AssetLoader(AssetLoader&&) = delete;
AssetLoader& operator=(AssetLoader const&) = delete;
AssetLoader& operator=(AssetLoader&&) = delete;
};
} // namespace gltfio
#endif // GLTFIO_ASSETLOADER_H
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_FILAMENTASSET_H
#define GLTFIO_FILAMENTASSET_H
#include <filament/Box.h>
#include <filament/TextureSampler.h>
#include <utils/Entity.h>
namespace filament {
class Engine;
class IndexBuffer;
class MaterialInstance;
class VertexBuffer;
}
namespace gltfio {
struct BufferBinding;
struct TextureBinding;
class Animator;
/**
* FilamentAsset owns a bundle of Filament objects that have been created by AssetLoader.
* For usage instructions, see the comment block for AssetLoader.
*
* This class owns a hierarchy of entities that have been loaded from a glTF asset. Every entity has
* a TransformManager component, and some entities also have Name and/or Renderable components.
*
* In addition to the aforementioned entities, an asset has strong ownership over a list of
* VertexBuffer, IndexBuffer, MaterialInstance, Texture, and, optionally, a simple animation engine.
*
* Clients must use ResourceLoader to create Texture objects, compute tangent quaternions, and
* upload data into vertex buffers and index buffers.
*
* TODO: This supports skinning but not morphing.
* TODO: Only the default glTF scene is loaded, other glTF scenes are ignored.
* TODO: Cameras, extras, and extensions are ignored.
*/
class FilamentAsset {
public:
/**
* Gets the list of entities, one for each glTF node. All of these have a Transform component.
* Some of the returned entities may also have a Renderable component.
*/
size_t getEntityCount() const noexcept;
const utils::Entity* getEntities() const noexcept;
/** Gets the transform root for the asset, which has no matching glTF node. */
utils::Entity getRoot() const noexcept;
/** Gets all material instances. These are already bound to renderables. */
size_t getMaterialInstanceCount() const noexcept;
const filament::MaterialInstance* const* getMaterialInstances() const noexcept;
/** Gets loading instructions for vertex buffers and index buffers. */
size_t getBufferBindingCount() const noexcept;
const BufferBinding* getBufferBindings() const noexcept;
/** Gets loading instructions for textures. */
size_t getTextureBindingCount() const noexcept;
const TextureBinding* getTextureBindings() const noexcept;
/** Gets the bounding box computed from the supplied min / max values in glTF accessors. */
filament::Aabb getBoundingBox() const noexcept;
/**
* Lazily creates the animation engine or returns it from the cache.
* The animator is owned by the asset and should not be manually deleted.
*/
Animator* getAnimator() noexcept;
/**
* Reclaims CPU-side memory for URI strings, binding lists, and raw animation data.
*
* This should only be called after ResourceLoader::loadResources().
* If using Animator, this should be called after getAnimator().
*/
void releaseSourceData() noexcept;
protected:
FilamentAsset() noexcept = default;
~FilamentAsset() = default;
public:
FilamentAsset(FilamentAsset const&) = delete;
FilamentAsset(FilamentAsset&&) = delete;
FilamentAsset& operator=(FilamentAsset const&) = delete;
FilamentAsset& operator=(FilamentAsset&&) = delete;
};
/**
* BufferBinding is a read-only structure that tells clients how to load a source blob into a
* VertexBuffer slot or IndexBuffer.
*
* Each binding instance corresponds to one of the following:
*
* (a) One call to VertexBuffer::setBufferAt().
* (b) One call to IndexBuffer::setBuffer().
*
*/
struct BufferBinding {
const char* uri; // unique identifier for the source blob
uint32_t totalSize; // size in bytes of the source blob at the given URI
int bufferIndex; // only used when the destination is a VertexBuffer
uint32_t offset; // byte count used only for vertex and index buffers
uint32_t size; // byte count used only for vertex and index buffers
void** data; // pointer to the resource data in the source asset (if loaded)
// Only one of the following two destinations can be non-null.
filament::VertexBuffer* vertexBuffer;
filament::IndexBuffer* indexBuffer;
bool convertBytesToShorts; // the resource loader must convert the buffer from u8 to u16
bool generateTrivialIndices; // the resource loader must generate indices like: 0, 1, 2, ...
};
/** Describes a binding from a Texture to a MaterialInstance. */
struct TextureBinding {
const char* uri;
uint32_t totalSize;
const char* mimeType;
void** data;
filament::MaterialInstance* materialInstance;
const char* materialParameter;
filament::TextureSampler sampler;
bool srgb;
};
} // namespace gltfio
#endif // GLTFIO_FILAMENTASSET_H
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_RESOURCELOADER_H
#define GLTFIO_RESOURCELOADER_H
#include <filament/Engine.h>
#include <gltfio/FilamentAsset.h>
#include <utils/Path.h>
namespace gltfio {
namespace details {
class FFilamentAsset;
class AssetPool;
}
struct ResourceConfiguration {
class filament::Engine* engine;
utils::Path basePath;
bool normalizeSkinningWeights;
};
/**
* ResourceLoader asynchronously uploads vertex buffers and textures to the GPU, computes
* surface orientation quaternions, and optionally normalizes skinning weights.
*
* For a usage example, see the comment block for AssetLoader.
*
* In theory, this class could cache a map of URL's to data blobs and could therefore be useful
* across multiple assets. However, clients should feel free to immediately destroy this after
* calling loadResources. There is no need to wait for resources to finish uploading because this is
* done in the the background.
*
* The resource loader must be destroyed on the same thread that calls Renderer::render because it
* listens to BufferDescriptor callbacks in order to determine when to free CPU-side data blobs.
*
* TODO: the GPU upload is asynchronous but the load-from-disk and image decode is not.
*/
class ResourceLoader {
public:
ResourceLoader(const ResourceConfiguration& config);
~ResourceLoader();
bool loadResources(FilamentAsset* asset);
private:
bool createTextures(details::FFilamentAsset* asset) const;
void computeTangents(details::FFilamentAsset* asset) const;
void normalizeWeights(details::FFilamentAsset* asset) const;
details::AssetPool* mPool;
const ResourceConfiguration mConfig;
};
} // namespace gltfio
#endif // GLTFIO_RESOURCELOADER_H
+360
View File
@@ -0,0 +1,360 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include <gltfio/Animator.h>
#include "FFilamentAsset.h"
#include "math.h"
#include "upcast.h"
#include <filament/RenderableManager.h>
#include <filament/TransformManager.h>
#include <utils/Log.h>
#include <math/mat4.h>
#include <math/norm.h>
#include <math/quat.h>
#include <math/scalar.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <map>
#include <string>
#include <vector>
using namespace filament;
using namespace filament::math;
using namespace std;
using namespace utils;
namespace gltfio {
using namespace details;
using TimeValues = std::map<float, size_t>;
using SourceValues = std::vector<float>;
struct Sampler {
TimeValues times;
SourceValues values;
enum { LINEAR, STEP, CUBIC } interpolation;
};
struct Channel {
const Sampler* sourceData;
utils::Entity targetEntity;
enum { TRANSLATION, ROTATION, SCALE } transformType;
};
struct Animation {
float duration;
std::string name;
std::vector<Sampler> samplers;
std::vector<Channel> channels;
};
struct AnimatorImpl {
std::vector<Animation> animations;
FFilamentAsset* asset;
RenderableManager* renderableManager;
TransformManager* transformManager;
};
static int numComponents(cgltf_type type) {
switch (type) {
case cgltf_type_vec3: return 3;
case cgltf_type_vec4: return 4;
default: return 1;
}
}
static void convert8(const cgltf_accessor* src, const uint8_t* srcBlob, SourceValues& dst) {
srcBlob += src->buffer_view->offset + src->offset;
dst.resize(src->count * numComponents(src->type));
auto srcValues = (const int8_t*) srcBlob;
for (size_t i = 0, n = dst.size(); i < n; ++i) {
dst[i] = unpackSnorm8(srcValues[i]);
}
}
static void convert8U(const cgltf_accessor* src, const uint8_t* srcBlob, SourceValues& dst) {
srcBlob += src->buffer_view->offset + src->offset;
dst.resize(src->count * numComponents(src->type));
for (size_t i = 0, n = dst.size(); i < n; ++i) {
dst[i] = unpackUnorm8(srcBlob[i]);
}
}
static void convert16(const cgltf_accessor* src, const uint8_t* srcBlob, SourceValues& dst) {
srcBlob += src->buffer_view->offset + src->offset;
dst.resize(src->count * numComponents(src->type));
auto srcValues = (const int16_t*) srcBlob;
for (size_t i = 0, n = dst.size(); i < n; ++i) {
dst[i] = unpackSnorm16(srcValues[i]);
}
}
static void convert16U(const cgltf_accessor* src, const uint8_t* srcBlob, SourceValues& dst) {
srcBlob += src->buffer_view->offset + src->offset;
dst.resize(src->count * numComponents(src->type));
auto srcValues = (const uint16_t*) srcBlob;
for (size_t i = 0, n = dst.size(); i < n; ++i) {
dst[i] = unpackUnorm16(srcValues[i]);
}
}
static void convert32F(const cgltf_accessor* src, const uint8_t* srcBlob, SourceValues& dst) {
srcBlob += src->buffer_view->offset + src->offset;
dst.resize(src->count * numComponents(src->type));
memcpy(dst.data(), srcBlob, dst.size() * sizeof(float));
}
static void createSampler(const cgltf_animation_sampler& src, Sampler& dst) {
// Copy the time values into a red-black tree.
const cgltf_accessor* timelineAccessor = src.input;
const uint8_t* timelineBlob = (const uint8_t*) timelineAccessor->buffer_view->buffer->data;
const float* timelineFloats = (const float*) (timelineBlob + timelineAccessor->offset +
timelineAccessor->buffer_view->offset);
for (size_t i = 0, len = timelineAccessor->count; i < len; ++i) {
dst.times[timelineFloats[i]] = i;
}
// Convert source data to float.
const cgltf_accessor* valuesAccessor = src.output;
const uint8_t* valuesBlob = (const uint8_t*) valuesAccessor->buffer_view->buffer->data;
switch (valuesAccessor->component_type) {
case cgltf_component_type_r_8:
convert8(valuesAccessor, valuesBlob, dst.values);
break;
case cgltf_component_type_r_8u:
convert8U(valuesAccessor, valuesBlob, dst.values);
break;
case cgltf_component_type_r_16:
convert16(valuesAccessor, valuesBlob, dst.values);
break;
case cgltf_component_type_r_16u:
convert16U(valuesAccessor, valuesBlob, dst.values);
break;
case cgltf_component_type_r_32f:
convert32F(valuesAccessor, valuesBlob, dst.values);
break;
default:
slog.e << "Unknown animation type." << io::endl;
return;
}
switch (src.interpolation) {
case cgltf_interpolation_type_linear:
dst.interpolation = Sampler::LINEAR;
break;
case cgltf_interpolation_type_step:
dst.interpolation = Sampler::STEP;
break;
case cgltf_interpolation_type_cubic_spline:
dst.interpolation = Sampler::CUBIC;
break;
}
}
static void setTransformType(const cgltf_animation_channel& src, Channel& dst) {
switch (src.target_path) {
case cgltf_animation_path_type_translation:
dst.transformType = Channel::TRANSLATION;
break;
case cgltf_animation_path_type_rotation:
dst.transformType = Channel::ROTATION;
break;
case cgltf_animation_path_type_scale:
dst.transformType = Channel::SCALE;
break;
case cgltf_animation_path_type_invalid:
case cgltf_animation_path_type_weights:
slog.e << "Unsupported channel path." << io::endl;
break;
}
}
Animator::Animator(FilamentAsset* publicAsset) {
mImpl = new AnimatorImpl();
FFilamentAsset* asset = mImpl->asset = upcast(publicAsset);
mImpl->renderableManager = &asset->mEngine->getRenderableManager();
mImpl->transformManager = &asset->mEngine->getTransformManager();
// Loop over the glTF animation definitions.
const cgltf_data* srcAsset = asset->mSourceAsset;
const cgltf_animation* srcAnims = srcAsset->animations;
mImpl->animations.resize(srcAsset->animations_count);
for (cgltf_size i = 0, len = srcAsset->animations_count; i < len; ++i) {
const cgltf_animation& srcAnim = srcAnims[i];
Animation& dstAnim = mImpl->animations[i];
dstAnim.duration = 0;
if (srcAnim.name) {
dstAnim.name = srcAnim.name;
}
// Import each glTF sampler into a custom data structure.
cgltf_animation_sampler* srcSamplers = srcAnim.samplers;
dstAnim.samplers.resize(srcAnim.samplers_count);
for (cgltf_size j = 0, nsamps = srcAnim.samplers_count; j < nsamps; ++j) {
const cgltf_animation_sampler& srcSampler = srcSamplers[j];
Sampler& dstSampler = dstAnim.samplers[j];
createSampler(srcSampler, dstSampler);
if (dstSampler.times.size() > 1) {
float maxtime = (--dstSampler.times.end())->first;
dstAnim.duration = std::max(dstAnim.duration, maxtime);
}
}
// Import each glTF channel into a custom data structure.
cgltf_animation_channel* srcChannels = srcAnim.channels;
dstAnim.channels.resize(srcAnim.channels_count);
auto& transformManager = *mImpl->transformManager;
for (cgltf_size j = 0, nchans = srcAnim.channels_count; j < nchans; ++j) {
const cgltf_animation_channel& srcChannel = srcChannels[j];
utils::Entity targetEntity = asset->mNodeMap[srcChannel.target_node];
Channel& dstChannel = dstAnim.channels[j];
dstChannel.sourceData = &dstAnim.samplers[srcChannel.sampler - srcSamplers];
dstChannel.targetEntity = targetEntity;
setTransformType(srcChannel, dstChannel);
}
}
}
Animator::~Animator() {
delete mImpl;
}
size_t Animator::getAnimationCount() const {
return mImpl->animations.size();
}
void Animator::applyAnimation(size_t animationIndex, float time) const {
const Animation& anim = mImpl->animations[animationIndex];
time = fmod(time, anim.duration);
for (const auto& channel : anim.channels) {
const Sampler* sampler = channel.sourceData;
if (sampler->times.size() < 2) {
continue;
}
int cindex = &channel - &anim.channels.front();
int sindex = sampler - &anim.samplers.front();
TransformManager::Instance node = mImpl->transformManager->getInstance(channel.targetEntity);
const TimeValues& times = sampler->times;
// Find the first keyframe after the given time, or the keyframe that matches it exactly.
TimeValues::const_iterator iter = times.lower_bound(time);
// Find the two values that we will interpolate between.
TimeValues::const_iterator prevIter;
TimeValues::const_iterator nextIter;
if (iter == times.end()) {
prevIter = --times.end();
nextIter = times.begin();
} else if (iter == times.begin()) {
prevIter = nextIter = iter;
} else {
nextIter = iter;
prevIter = --iter;
}
// Compute the interpolant between 0 and 1.
float prevTime = prevIter->first;
float nextTime = nextIter->first;
float interval = nextTime - prevTime;
if (interval < 0) {
interval += anim.duration;
}
float t = interval == 0 ? 0.0f : ((time - prevTime) / interval);
// Perform the interpolation. This is a simple but inefficient implementation; Filament
// stores transforms as mat4's but glTF animation is based on TRS (translation rotation
// scale). TODO: honor channel.transformType and support STEP / CUBIC interpolation
size_t prevIndex = prevIter->second;
size_t nextIndex = nextIter->second;
mat4f xform = mImpl->transformManager->getTransform(node);
float3 scale;
quatf rotation;
float3 translation;
decomposeMatrix(xform, &translation, &rotation, &scale);
switch (channel.transformType) {
case Channel::SCALE: {
const float3* srcVec3 = (const float3*) sampler->values.data();
scale = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]);
break;
}
case Channel::TRANSLATION: {
const float3* srcVec3 = (const float3*) sampler->values.data();
translation = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]);
break;
}
case Channel::ROTATION: {
const quatf* srcQuat = (const quatf*) sampler->values.data();
rotation = slerp(srcQuat[prevIndex], srcQuat[nextIndex], t);
break;
}
}
xform = composeMatrix(translation, rotation, scale);
mImpl->transformManager->setTransform(node, xform);
}
}
void Animator::updateBoneMatrices() {
vector<mat4f> boneMatrices;
FFilamentAsset* asset = mImpl->asset;
auto renderableManager = mImpl->renderableManager;
auto transformManager = mImpl->transformManager;
for (const auto& skin : asset->mSkins) {
size_t njoints = skin.joints.size();
boneMatrices.resize(njoints);
for (const auto& entity : skin.targets) {
auto renderable = renderableManager->getInstance(entity);
if (!renderable) {
continue;
}
mat4f inverseGlobalTransform;
auto xformable = transformManager->getInstance(entity);
if (xformable) {
inverseGlobalTransform = inverse(transformManager->getWorldTransform(xformable));
}
for (size_t boneIndex = 0; boneIndex < njoints; ++boneIndex) {
const auto& joint = skin.joints[boneIndex];
TransformManager::Instance jointInstance = transformManager->getInstance(joint);
mat4f globalJointTransform = transformManager->getWorldTransform(jointInstance);
boneMatrices[boneIndex] =
inverseGlobalTransform *
globalJointTransform *
skin.inverseBindMatrices[boneIndex];
}
renderableManager->setBones(renderable, boneMatrices.data(), boneMatrices.size());
}
}
}
float Animator::getAnimationDuration(size_t animationIndex) const {
return mImpl->animations[animationIndex].duration;
}
const char* Animator::getAnimationName(size_t animationIndex) const {
return mImpl->animations[animationIndex].name.c_str();
}
} // namespace gltfio
+725
View File
@@ -0,0 +1,725 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include <gltfio/AssetLoader.h>
#include "FFilamentAsset.h"
#include "GltfEnums.h"
#include "MaterialGenerator.h"
#include <filament/Box.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/LightManager.h>
#include <filament/Material.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/TextureSampler.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <math/mat4.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/EntityManager.h>
#include <utils/Log.h>
#include <utils/NameComponentManager.h>
#include <tsl/robin_map.h>
#include <vector>
#define CGLTF_IMPLEMENTATION
#include <cgltf.h>
#include "math.h"
#include "upcast.h"
using namespace filament;
using namespace filament::math;
using namespace utils;
namespace gltfio {
namespace details {
// MeshCache
// ---------
// If a given glTF mesh is referenced by multiple glTF nodes, then it generates a separate Filament
// renderable for each of those nodes. All renderables generated by a given mesh share a common set
// of VertexBuffer and IndexBuffer objects. To achieve the sharing behavior, the loader maintains a
// small cache. The cache keys are glTF mesh definitions and the cache entries are lists of
// primitives, where a "primitive" is a reference to a Filament VertexBuffer and IndexBuffer.
struct Primitive {
VertexBuffer* vertices = nullptr;
IndexBuffer* indices = nullptr;
Aabb aabb; // object-space bounding box
};
using MeshCache = tsl::robin_map<const cgltf_mesh*, std::vector<Primitive>>;
// Filament materials are cached by the MaterialGenerator, but material instances are cached here.
using MatInstanceCache = tsl::robin_map<intptr_t, MaterialInstance*>;
// Filament automatically infers the size of driver-level vertex buffers from the attribute data
// (stride, count, offset) and clients are expected to avoid uploading data blobs that exceed this
// size. Since this information doesn't exist in the glTF we need to compute it manually. This is a
// bit of a cheat, cgltf_calc_size is private but its implementation file is available in this cpp
// file.
static uint32_t computeBindingSize(const cgltf_accessor* accessor){
cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type);
return uint32_t(accessor->stride * (accessor->count - 1) + element_size);
};
static uint32_t computeBindingOffset(const cgltf_accessor* accessor) {
return uint32_t(accessor->offset + accessor->buffer_view->offset);
};
struct FAssetLoader : public AssetLoader {
FAssetLoader(Engine* engine, NameComponentManager* names) :
mEntityManager(EntityManager::get()),
mRenderableManager(engine->getRenderableManager()),
mNameManager(names),
mTransformManager(engine->getTransformManager()),
mMaterials(engine),
mEngine(engine) {}
FFilamentAsset* createAssetFromJson(uint8_t const* bytes, uint32_t nbytes);
FilamentAsset* createAssetFromBinary(uint8_t const* bytes, uint32_t nbytes);
void destroyAsset(const FFilamentAsset* asset) {
delete asset;
}
void castShadowsByDefault(bool enable) {
mCastShadows = enable;
}
void receiveShadowsByDefault(bool enable) {
mReceiveShadows = enable;
}
size_t getMaterialsCount() const noexcept {
return mMaterials.getMaterialsCount();
}
const Material* const* getMaterials() const noexcept {
return mMaterials.getMaterials();
}
void destroyMaterials() {
mMaterials.destroyMaterials();
}
void createAsset(const cgltf_data* srcAsset);
void createEntity(const cgltf_node* node, Entity parent);
void createRenderable(const cgltf_node* node, Entity entity);
bool createPrimitive(const cgltf_primitive* inPrim, Primitive* outPrim, const UvMap& uvmap);
MaterialInstance* createMaterialInstance(const cgltf_material* inputMat, UvMap* uvmap,
bool vertexColor);
void addTextureBinding(MaterialInstance* materialInstance, const char* parameterName,
const cgltf_texture* srcTexture, bool srgb);
void importSkinningData(Skin& dstSkin, const cgltf_skin& srcSkin);
bool primitiveHasVertexColor(const cgltf_primitive* inPrim) const;
bool mCastShadows = true;
bool mReceiveShadows = true;
EntityManager& mEntityManager;
RenderableManager& mRenderableManager;
NameComponentManager* mNameManager;
TransformManager& mTransformManager;
MaterialGenerator mMaterials;
Engine* mEngine;
// The loader owns a few transient mappings used only for the current asset being loaded.
FFilamentAsset* mResult;
MatInstanceCache mMatInstanceCache;
MeshCache mMeshCache;
bool mError = false;
};
FILAMENT_UPCAST(AssetLoader)
} // namespace details
using namespace details;
FFilamentAsset* FAssetLoader::createAssetFromJson(uint8_t const* bytes, uint32_t nbytes) {
cgltf_options options { cgltf_file_type_invalid };
cgltf_data* sourceAsset;
cgltf_result result = cgltf_parse(&options, bytes, nbytes, &sourceAsset);
if (result != cgltf_result_success) {
return nullptr;
}
createAsset(sourceAsset);
return mResult;
}
FilamentAsset* FAssetLoader::createAssetFromBinary(uint8_t const* bytes, uint32_t nbytes) {
cgltf_options options { cgltf_file_type_glb };
cgltf_data* sourceAsset;
cgltf_result result = cgltf_parse(&options, bytes, nbytes, &sourceAsset);
if (result != cgltf_result_success) {
return nullptr;
}
createAsset(sourceAsset);
return mResult;
}
void FAssetLoader::createAsset(const cgltf_data* srcAsset) {
mResult = new FFilamentAsset(mEngine);
mResult->mSourceAsset = srcAsset;
mResult->acquireSourceAsset();
// If there is no default scene specified, then the default is the first one.
// It is not an error for a glTF file to have zero scenes.
const cgltf_scene* scene = srcAsset->scene ? srcAsset->scene : srcAsset->scenes;
if (!scene) {
return;
}
// Create a single root node with an identity transform as a convenience to the client.
mResult->mRoot = mEntityManager.create();
mTransformManager.create(mResult->mRoot);
// One scene may have multiple root nodes. Recurse down and create an entity for each node.
cgltf_node** nodes = scene->nodes;
for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) {
const cgltf_node* root = nodes[i];
createEntity(root, mResult->mRoot);
}
if (mError) {
delete mResult;
mResult = nullptr;
}
// Copy over joint lists (references to TransformManager components) and create buffer bindings
// for inverseBindMatrices.
mResult->mSkins.resize(srcAsset->skins_count);
for (cgltf_size i = 0, len = srcAsset->skins_count; i < len; ++i) {
importSkinningData(mResult->mSkins[i], srcAsset->skins[i]);
}
// For each skin, build a list of renderables that it affects.
for (cgltf_size i = 0, len = srcAsset->nodes_count; i < len; ++i) {
const cgltf_node& node = srcAsset->nodes[i];
if (node.skin) {
int skinIndex = node.skin - &srcAsset->skins[0];
Entity entity = mResult->mNodeMap[&node];
mResult->mSkins[skinIndex].targets.push_back(entity);
}
}
// We're done with the import, so free up transient bookkeeping resources.
mMatInstanceCache.clear();
mMeshCache.clear();
mError = false;
}
void FAssetLoader::createEntity(const cgltf_node* node, Entity parent) {
Entity entity = mEntityManager.create();
// Always create a transform component to reflect the original hierarchy.
mat4f localTransform;
if (node->has_matrix) {
memcpy(&localTransform[0][0], &node->matrix[0], 16 * sizeof(float));
} else {
quatf* rotation = (quatf*) &node->rotation[0];
float3* scale = (float3*) &node->scale[0];
float3* translation = (float3*) &node->translation[0];
localTransform = composeMatrix(*translation, *rotation, *scale);
}
auto parentTransform = mTransformManager.getInstance(parent);
mTransformManager.create(entity, parentTransform, localTransform);
// Update the asset's entity list and private node mapping.
mResult->mEntities.push_back(entity);
mResult->mNodeMap[node] = entity;
// If the node has a mesh, then create a renderable component.
if (node->mesh) {
createRenderable(node, entity);
}
for (cgltf_size i = 0, len = node->children_count; i < len; ++i) {
createEntity(node->children[i], entity);
}
}
void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity) {
const cgltf_mesh* mesh = node->mesh;
// Compute the transform relative to the root.
auto thisTransform = mTransformManager.getInstance(entity);
mat4f worldTransform = mTransformManager.getWorldTransform(thisTransform);
cgltf_size nprims = mesh->primitives_count;
RenderableManager::Builder builder(nprims);
// If the mesh is already loaded, obtain the list of Filament VertexBuffer / IndexBuffer
// objects that were already generated, otherwise allocate a new list of null pointers.
auto iter = mMeshCache.find(mesh);
if (iter == mMeshCache.end()) {
mMeshCache[mesh].resize(nprims);
}
Primitive* outputPrim = mMeshCache[mesh].data();
const cgltf_primitive* inputPrim = &mesh->primitives[0];
if (mNameManager && mesh->name) {
mNameManager->addComponent(entity);
mNameManager->setName(mNameManager->getInstance(entity), mesh->name);
}
Aabb aabb;
// For each prim, create a Filament VertexBuffer, IndexBuffer, and MaterialInstance.
for (cgltf_size index = 0; index < nprims; ++index, ++outputPrim, ++inputPrim) {
RenderableManager::PrimitiveType primType;
if (!getPrimitiveType(inputPrim->type, &primType)) {
slog.e << "Unsupported primitive type." << io::endl;
}
// Create a material instance for this primitive or fetch one from the cache.
UvMap uvmap;
bool hasVertexColor = primitiveHasVertexColor(inputPrim);
MaterialInstance* mi = createMaterialInstance(inputPrim->material, &uvmap, hasVertexColor);
builder.material(index, mi);
// Create a Filament VertexBuffer and IndexBuffer for this prim if we haven't already.
if (!outputPrim->vertices && !createPrimitive(inputPrim, outputPrim, uvmap)) {
mError = true;
continue;
}
// Expand the object-space bounding box.
aabb.min = min(outputPrim->aabb.min, aabb.min);
aabb.max = max(outputPrim->aabb.max, aabb.max);
// We are not using the optional offset, minIndex, maxIndex, and count arguments when
// calling geometry() on the builder. It appears that the glTF spec does not have
// facilities for these parameters, which is not a huge loss since some of the buffer
// view and accessor features already have this functionality.
builder.geometry(index, primType, outputPrim->vertices, outputPrim->indices);
}
// Expand the world-space bounding box.
float3 minpt = (worldTransform * float4(aabb.min, 1.0)).xyz;
float3 maxpt = (worldTransform * float4(aabb.max, 1.0)).xyz;
mResult->mBoundingBox.min = min(mResult->mBoundingBox.min, minpt);
mResult->mBoundingBox.max = max(mResult->mBoundingBox.max, maxpt);
if (node->skin) {
builder.skinning(node->skin->joints_count);
}
builder
.boundingBox({aabb.min, aabb.max})
.culling(true)
.castShadows(mCastShadows)
.receiveShadows(mReceiveShadows)
.build(*mEngine, entity);
// TODO: support vertex morphing by honoring mesh->weights and mesh->weight_count.
}
bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* outPrim,
const UvMap& uvmap) {
// In glTF, each primitive may or may not have an index buffer. If a primitive does not have an
// index buffer, we ask the ResourceLoader to generate a trivial index buffer.
IndexBuffer* indices;
const cgltf_accessor* indicesAccessor = inPrim->indices;
if (indicesAccessor) {
IndexBuffer::Builder ibb;
ibb.indexCount(indicesAccessor->count);
IndexBuffer::IndexType indexType;
if (!getIndexType(indicesAccessor->component_type, &indexType)) {
utils::slog.e << "Unrecognized index type." << utils::io::endl;
return false;
}
ibb.bufferType(indexType);
indices = ibb.build(*mEngine);
const cgltf_buffer_view* bv = indicesAccessor->buffer_view;
mResult->mBufferBindings.emplace_back(BufferBinding {
.uri = bv->buffer->uri,
.totalSize = uint32_t(bv->buffer->size),
.offset = computeBindingOffset(indicesAccessor),
.size = computeBindingSize(indicesAccessor),
.data = &bv->buffer->data,
.indexBuffer = indices,
.convertBytesToShorts = indicesAccessor->component_type == cgltf_component_type_r_8u,
.generateTrivialIndices = false
});
} else {
const cgltf_size vertexCount = inPrim->attributes[0].data->count;
indices = IndexBuffer::Builder()
.indexCount(vertexCount)
.bufferType(IndexBuffer::IndexType::UINT)
.build(*mEngine);
mResult->mBufferBindings.emplace_back(BufferBinding {
.indexBuffer = indices,
.size = uint32_t(vertexCount * sizeof(uint32_t)),
.generateTrivialIndices = true
});
}
mResult->mIndexBuffers.push_back(indices);
// We do not necessarily upload all glTF attribute buffers to the GPU. For example, we do not
// upload tangent vectors in their source format or more than two UV sets. However the buffer
// count that gets passed to the Builder should be equal to the glTF attribute count because we
// do not remap the slots.
VertexBuffer::Builder vbb;
vbb.bufferCount(inPrim->attributes_count);
for (int slot = 0; slot < inPrim->attributes_count; slot++) {
const cgltf_attribute& inputAttribute = inPrim->attributes[slot];
const cgltf_accessor* inputAccessor = inputAttribute.data;
// At a minimum, surface orientation requires normals to be present in the source data.
// Here we re-purpose the normals slot to point to the quats that get computed later.
if (inputAttribute.type == cgltf_attribute_type_normal) {
vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4);
vbb.normalized(VertexAttribute::TANGENTS);
continue;
}
// The glTF tangent data is ignored here, but honored in ResourceLoader.
if (inputAttribute.type == cgltf_attribute_type_tangent) {
continue;
}
// Translate the cgltf attribute enum into a Filament enum and ignore all uv sets
// that do not have entries in the mapping table.
VertexAttribute semantic;
if (!getVertexAttrType(inputAttribute.type, &semantic)) {
utils::slog.e << "Unrecognized vertex semantic." << utils::io::endl;
return false;
}
UvSet uvset = uvmap[inputAttribute.index];
if (inputAttribute.type == cgltf_attribute_type_texcoord) {
switch (uvset) {
case UV0:
semantic = VertexAttribute::UV0;
break;
case UV1:
semantic = VertexAttribute::UV1;
break;
case UNUSED:
// It is perfectly acceptable to drop unused texture coordinate sets. In fact
// this can occur quite frequently, e.g. if the material has attached textures.
continue;
}
}
// This will needlessly set the same vertex count multiple times, which should be fine.
vbb.vertexCount(inputAccessor->count);
// The positions accessor is required to have min/max properties, use them to expand
// the bounding box for this primitive.
if (inputAttribute.type == cgltf_attribute_type_position) {
const float* minp = &inputAccessor->min[0];
const float* maxp = &inputAccessor->max[0];
outPrim->aabb.min = min(outPrim->aabb.min, float3(minp[0], minp[1], minp[2]));
outPrim->aabb.max = max(outPrim->aabb.max, float3(maxp[0], maxp[1], maxp[2]));
}
VertexBuffer::AttributeType atype;
if (!getElementType(inputAccessor->type, inputAccessor->component_type, &atype)) {
slog.e << "Unsupported accessor type." << io::endl;
return false;
}
if (inputAccessor->is_sparse) {
slog.e << "Sparse accessors not yet supported." << io::endl;
return false;
}
// The cgltf library provides a stride value for all accessors, even though they do not
// exist in the glTF file. It is computed from the type and the stride of the buffer view.
// As a convenience, cgltf also replaces zero (default) stride with the actual stride.
vbb.attribute(semantic, slot, atype, 0, inputAccessor->stride);
if (inputAccessor->normalized) {
vbb.normalized(semantic);
}
}
VertexBuffer* vertices = mResult->mPrimMap[inPrim] = vbb.build(*mEngine);
mResult->mVertexBuffers.push_back(vertices);
for (int slot = 0; slot < inPrim->attributes_count; slot++) {
const cgltf_attribute& inputAttribute = inPrim->attributes[slot];
const cgltf_accessor* inputAccessor = inputAttribute.data;
const cgltf_buffer_view* bv = inputAccessor->buffer_view;
if (inputAttribute.type == cgltf_attribute_type_normal ||
inputAttribute.type == cgltf_attribute_type_tangent) {
continue;
}
if (inputAttribute.type == cgltf_attribute_type_texcoord &&
uvmap[inputAttribute.index] == UNUSED) {
continue;
}
mResult->mBufferBindings.emplace_back(BufferBinding {
.uri = bv->buffer->uri,
.totalSize = (uint32_t) bv->buffer->size,
.bufferIndex = slot,
.offset = computeBindingOffset(inputAccessor),
.size = computeBindingSize(inputAccessor),
.data = &bv->buffer->data,
.vertexBuffer = vertices,
.indexBuffer = nullptr,
.convertBytesToShorts = false,
.generateTrivialIndices = false
});
}
outPrim->indices = indices;
outPrim->vertices = vertices;
return true;
}
MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_material* inputMat,
UvMap* uvmap, bool vertexColor) {
intptr_t key = ((intptr_t) inputMat) ^ (vertexColor ? 1 : 0);
auto iter = mMatInstanceCache.find(key);
if (iter != mMatInstanceCache.end()) {
return iter->second;
}
// The default glTF material is non-lit black.
if (inputMat == nullptr) {
MaterialKey matkey {
.unlit = true
};
Material* mat = mMaterials.getOrCreateMaterial(&matkey, uvmap, "default");
MaterialInstance* mi = mat->createInstance();
mResult->mMaterialInstances.push_back(mi);
return mMatInstanceCache[0] = mi;
}
if (inputMat->has_pbr_specular_glossiness) {
slog.e << "KHR_materials_pbrSpecularGlossiness is not supported." << io::endl;
}
auto pbrConfig = inputMat->pbr_metallic_roughness;
bool hasTextureTransforms = pbrConfig.base_color_texture.has_transform ||
pbrConfig.metallic_roughness_texture.has_transform ||
inputMat->normal_texture.has_transform ||
inputMat->occlusion_texture.has_transform ||
inputMat->emissive_texture.has_transform;
MaterialKey matkey {
.doubleSided = (bool) inputMat->double_sided,
.unlit = (bool) inputMat->unlit,
.hasVertexColors = vertexColor,
.hasBaseColorTexture = pbrConfig.base_color_texture.texture,
.hasMetallicRoughnessTexture = pbrConfig.metallic_roughness_texture.texture,
.hasNormalTexture = inputMat->normal_texture.texture,
.hasOcclusionTexture = inputMat->occlusion_texture.texture,
.hasEmissiveTexture = inputMat->emissive_texture.texture,
.alphaMode = AlphaMode::OPAQUE,
.alphaMaskThreshold = 0.5f,
.baseColorUV = (uint8_t) pbrConfig.base_color_texture.texcoord,
.metallicRoughnessUV = (uint8_t) pbrConfig.metallic_roughness_texture.texcoord,
.emissiveUV = (uint8_t) inputMat->emissive_texture.texcoord,
.aoUV = (uint8_t) inputMat->occlusion_texture.texcoord,
.normalUV = (uint8_t) inputMat->normal_texture.texcoord,
.hasTextureTransforms = hasTextureTransforms
};
switch (inputMat->alpha_mode) {
case cgltf_alpha_mode_opaque:
matkey.alphaMode = AlphaMode::OPAQUE;
break;
case cgltf_alpha_mode_mask:
matkey.alphaMode = AlphaMode::MASKED;
matkey.alphaMaskThreshold = inputMat->alpha_cutoff;
break;
case cgltf_alpha_mode_blend:
matkey.alphaMode = AlphaMode::TRANSPARENT;
break;
}
// This not only creates (or fetches) a material, it modifies the material key according to
// our rendering constraints. For example, Filament only supports 2 sets of texture coordinates.
Material* mat = mMaterials.getOrCreateMaterial(&matkey, uvmap, inputMat->name);
// Create an instance of the material that has a unique set of texture bindings etc.
MaterialInstance* mi = mat->createInstance();
mResult->mMaterialInstances.push_back(mi);
const float* e = &inputMat->emissive_factor[0];
mi->setParameter("emissiveFactor", float3(e[0], e[1], e[2]));
mi->setParameter("normalScale", inputMat->normal_texture.scale);
mi->setParameter("aoStrength", inputMat->occlusion_texture.scale);
const float* c = &pbrConfig.base_color_factor[0];
mi->setParameter("baseColorFactor", float4(c[0], c[1], c[2], c[3]));
mi->setParameter("metallicFactor", pbrConfig.metallic_factor);
mi->setParameter("roughnessFactor", pbrConfig.roughness_factor);
if (matkey.hasBaseColorTexture) {
addTextureBinding(mi, "baseColorMap", pbrConfig.base_color_texture.texture, true);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = pbrConfig.base_color_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
mi->setParameter("baseColorUvMatrix", uvmat);
}
}
if (matkey.hasMetallicRoughnessTexture) {
addTextureBinding(mi, "metallicRoughnessMap",
pbrConfig.metallic_roughness_texture.texture, false);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = pbrConfig.metallic_roughness_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
mi->setParameter("metallicRoughnessUvMatrix", uvmat);
}
}
if (matkey.hasNormalTexture) {
addTextureBinding(mi, "normalMap", inputMat->normal_texture.texture, false);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = inputMat->normal_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
mi->setParameter("normalUvMatrix", uvmat);
}
}
if (matkey.hasOcclusionTexture) {
addTextureBinding(mi, "occlusionMap", inputMat->occlusion_texture.texture, false);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = inputMat->occlusion_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
mi->setParameter("occlusionUvMatrix", uvmat);
}
}
if (matkey.hasEmissiveTexture) {
addTextureBinding(mi, "emissiveMap", inputMat->emissive_texture.texture, true);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = inputMat->emissive_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
mi->setParameter("emissiveUvMatrix", uvmat);
}
}
return mMatInstanceCache[key] = mi;
}
void FAssetLoader::addTextureBinding(MaterialInstance* materialInstance, const char* parameterName,
const cgltf_texture* srcTexture, bool srgb) {
if (!srcTexture->image) {
slog.w << "Texture is missing image (" << srcTexture->name << ")." << io::endl;
return;
}
TextureSampler dstSampler;
auto srcSampler = srcTexture->sampler;
if (srcSampler) {
dstSampler.setWrapModeS(getWrapMode(srcSampler->wrap_s));
dstSampler.setWrapModeT(getWrapMode(srcSampler->wrap_t));
dstSampler.setMagFilter(getMagFilter(srcSampler->mag_filter));
dstSampler.setMinFilter(getMinFilter(srcSampler->min_filter));
} else {
// These defaults are stipulated by the spec:
dstSampler.setWrapModeS(TextureSampler::WrapMode::REPEAT);
dstSampler.setWrapModeT(TextureSampler::WrapMode::REPEAT);
// These defaults are up the implementation but since we generate mipmaps unconditionally,
// we might as well use them. In practice the conformance models look awful without
// using mipmapping by default.
dstSampler.setMagFilter(TextureSampler::MagFilter::LINEAR);
dstSampler.setMinFilter(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR);
}
auto bv = srcTexture->image->buffer_view;
mResult->mTextureBindings.push_back(TextureBinding {
.uri = srcTexture->image->uri,
.totalSize = uint32_t(bv ? bv->buffer->size : 0),
.mimeType = srcTexture->image->mime_type,
.data = bv ? &bv->buffer->data : nullptr,
.materialInstance = materialInstance,
.materialParameter = parameterName,
.sampler = dstSampler,
.srgb = srgb
});
}
void FAssetLoader::importSkinningData(Skin& dstSkin, const cgltf_skin& srcSkin) {
if (srcSkin.name) {
dstSkin.name = srcSkin.name;
}
dstSkin.joints.resize(srcSkin.joints_count);
const auto& nodeMap = mResult->mNodeMap;
for (cgltf_size i = 0, len = srcSkin.joints_count; i < len; ++i) {
dstSkin.joints[i] = nodeMap.at(srcSkin.joints[i]);
}
}
bool FAssetLoader::primitiveHasVertexColor(const cgltf_primitive* inPrim) const {
for (int slot = 0; slot < inPrim->attributes_count; slot++) {
const cgltf_attribute& inputAttribute = inPrim->attributes[slot];
if (inputAttribute.type == cgltf_attribute_type_color) {
return true;
}
}
return false;
}
AssetLoader* AssetLoader::create(Engine* engine, NameComponentManager* names) {
return new FAssetLoader(engine, names);
}
void AssetLoader::destroy(AssetLoader** loader) {
delete *loader;
*loader = nullptr;
}
FilamentAsset* AssetLoader::createAssetFromJson(uint8_t const* bytes, uint32_t nbytes) {
return upcast(this)->createAssetFromJson(bytes, nbytes);
}
FilamentAsset* AssetLoader::createAssetFromBinary(uint8_t const* bytes, uint32_t nbytes) {
return upcast(this)->createAssetFromBinary(bytes, nbytes);
}
void AssetLoader::destroyAsset(const FilamentAsset* asset) {
upcast(this)->destroyAsset(upcast(asset));
}
void AssetLoader::castShadowsByDefault(bool enable) {
upcast(this)->castShadowsByDefault(enable);
}
void AssetLoader::receiveShadowsByDefault(bool enable) {
upcast(this)->receiveShadowsByDefault(enable);
}
size_t AssetLoader::getMaterialsCount() const noexcept {
return upcast(this)->getMaterialsCount();
}
const Material* const* AssetLoader::getMaterials() const noexcept {
return upcast(this)->getMaterials();
}
void AssetLoader::destroyMaterials() {
upcast(this)->destroyMaterials();
}
} // namespace gltfio
+175
View File
@@ -0,0 +1,175 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_FFILAMENTASSET_H
#define GLTFIO_FFILAMENTASSET_H
#include <gltfio/FilamentAsset.h>
#include <gltfio/Animator.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/Texture.h>
#include <filament/TextureSampler.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <math/mat4.h>
#include <utils/Entity.h>
#include <cgltf.h>
#include "upcast.h"
#include <tsl/robin_map.h>
#include <set>
#include <string>
#include <vector>
namespace gltfio {
namespace details {
struct Skin {
std::string name;
std::vector<filament::math::mat4f> inverseBindMatrices;
std::vector<utils::Entity> joints;
std::vector<utils::Entity> targets;
};
struct FFilamentAsset : public FilamentAsset {
FFilamentAsset(filament::Engine* engine) : mEngine(engine) {}
~FFilamentAsset() {
releaseSourceData();
delete mAnimator;
mEngine->destroy(mRoot);
for (auto entity : mEntities) {
mEngine->destroy(entity);
}
for (auto mi : mMaterialInstances) {
mEngine->destroy(mi);
}
for (auto vb : mVertexBuffers) {
mEngine->destroy(vb);
}
for (auto ib : mIndexBuffers) {
mEngine->destroy(ib);
}
for (auto tx : mTextures) {
mEngine->destroy(tx);
}
}
size_t getEntityCount() const noexcept {
return mEntities.size();
}
const utils::Entity* getEntities() const noexcept {
return mEntities.data();
}
utils::Entity getRoot() const noexcept {
return mRoot;
}
size_t getMaterialInstanceCount() const noexcept {
return mMaterialInstances.size();
}
const filament::MaterialInstance* const* getMaterialInstances() const noexcept {
return mMaterialInstances.data();
}
size_t getBufferBindingCount() const noexcept {
return mBufferBindings.size();
}
const BufferBinding* getBufferBindings() const noexcept {
return mBufferBindings.data();
}
size_t getTextureBindingCount() const noexcept {
return mTextureBindings.size();
}
const TextureBinding* getTextureBindings() const noexcept {
return mTextureBindings.data();
}
filament::Aabb getBoundingBox() const noexcept {
return mBoundingBox;
}
Animator* getAnimator() noexcept {
if (!mAnimator) {
mAnimator = new Animator(this);
}
return mAnimator;
}
void releaseSourceData() noexcept {
mBufferBindings.clear();
mBufferBindings.shrink_to_fit();
mTextureBindings.clear();
mTextureBindings.shrink_to_fit();
mNodeMap.clear();
mPrimMap.clear();
releaseSourceAsset();
}
void acquireSourceAsset() {
++mSourceAssetRefCount;
}
void releaseSourceAsset() {
if (--mSourceAssetRefCount == 0) {
cgltf_free((cgltf_data*) mSourceAsset);
mSourceAsset = nullptr;
}
}
filament::Engine* mEngine;
std::vector<utils::Entity> mEntities;
std::vector<filament::MaterialInstance*> mMaterialInstances;
std::vector<filament::VertexBuffer*> mVertexBuffers;
std::vector<filament::IndexBuffer*> mIndexBuffers;
std::vector<filament::Texture*> mTextures;
filament::Aabb mBoundingBox;
utils::Entity mRoot;
std::vector<Skin> mSkins;
Animator* mAnimator = nullptr;
int mSourceAssetRefCount = 0;
/** @{
* Transient source data that can freed via releaseSourceData(). */
std::vector<BufferBinding> mBufferBindings;
std::vector<TextureBinding> mTextureBindings;
const cgltf_data* mSourceAsset = nullptr;
tsl::robin_map<const cgltf_node*, utils::Entity> mNodeMap;
tsl::robin_map<const cgltf_primitive*, filament::VertexBuffer*> mPrimMap;
/** @} */
};
FILAMENT_UPCAST(FilamentAsset)
} // namespace details
} // namespace gltfio
#endif // GLTFIO_FFILAMENTASSET_H
+74
View File
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include "FFilamentAsset.h"
using namespace filament;
using namespace utils;
namespace gltfio {
using namespace details;
size_t FilamentAsset::getEntityCount() const noexcept {
return upcast(this)->getEntityCount();
}
const Entity* FilamentAsset::getEntities() const noexcept {
return upcast(this)->getEntities();
}
Entity FilamentAsset::getRoot() const noexcept {
return upcast(this)->getRoot();
}
size_t FilamentAsset::getMaterialInstanceCount() const noexcept {
return upcast(this)->getMaterialInstanceCount();
}
const MaterialInstance* const* FilamentAsset::getMaterialInstances() const noexcept {
return upcast(this)->getMaterialInstances();
}
size_t FilamentAsset::getBufferBindingCount() const noexcept {
return upcast(this)->getBufferBindingCount();
}
const BufferBinding* FilamentAsset::getBufferBindings() const noexcept {
return upcast(this)->getBufferBindings();
}
size_t FilamentAsset::getTextureBindingCount() const noexcept {
return upcast(this)->getTextureBindingCount();
}
const TextureBinding* FilamentAsset::getTextureBindings() const noexcept {
return upcast(this)->getTextureBindings();
}
filament::Aabb FilamentAsset::getBoundingBox() const noexcept {
return upcast(this)->getBoundingBox();
}
Animator* FilamentAsset::getAnimator() noexcept {
return upcast(this)->getAnimator();
}
void FilamentAsset::releaseSourceData() noexcept {
return upcast(this)->releaseSourceData();
}
} // namespace gltfio
+233
View File
@@ -0,0 +1,233 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_GLTFENUMS_H
#define GLTFIO_GLTFENUMS_H
#include <filament/IndexBuffer.h>
#include <filament/RenderableManager.h>
#include <filament/TextureSampler.h>
#include <filament/VertexBuffer.h>
#include <cgltf.h>
#define GL_NEAREST 0x2600
#define GL_LINEAR 0x2601
#define GL_NEAREST_MIPMAP_NEAREST 0x2700
#define GL_LINEAR_MIPMAP_NEAREST 0x2701
#define GL_NEAREST_MIPMAP_LINEAR 0x2702
#define GL_LINEAR_MIPMAP_LINEAR 0x2703
#define GL_REPEAT 0x2901
#define GL_MIRRORED_REPEAT 0x8370
#define GL_CLAMP_TO_EDGE 0x812F
inline filament::TextureSampler::WrapMode getWrapMode(cgltf_int wrap) {
switch (wrap) {
case GL_REPEAT:
return filament::TextureSampler::WrapMode::REPEAT;
case GL_MIRRORED_REPEAT:
return filament::TextureSampler::WrapMode::MIRRORED_REPEAT;
case GL_CLAMP_TO_EDGE:
return filament::TextureSampler::WrapMode::CLAMP_TO_EDGE;
}
return filament::TextureSampler::WrapMode::REPEAT;
}
inline filament::TextureSampler::MinFilter getMinFilter(cgltf_int minFilter) {
switch (minFilter) {
case GL_NEAREST:
return filament::TextureSampler::MinFilter::NEAREST;
case GL_LINEAR:
return filament::TextureSampler::MinFilter::LINEAR;
case GL_NEAREST_MIPMAP_NEAREST:
return filament::TextureSampler::MinFilter::NEAREST_MIPMAP_NEAREST;
case GL_LINEAR_MIPMAP_NEAREST:
return filament::TextureSampler::MinFilter::LINEAR_MIPMAP_NEAREST;
case GL_NEAREST_MIPMAP_LINEAR:
return filament::TextureSampler::MinFilter::NEAREST_MIPMAP_LINEAR;
case GL_LINEAR_MIPMAP_LINEAR:
return filament::TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR;
}
return filament::TextureSampler::MinFilter::NEAREST;
}
inline filament::TextureSampler::MagFilter getMagFilter(cgltf_int magFilter) {
switch (magFilter) {
case GL_NEAREST:
return filament::TextureSampler::MagFilter::NEAREST;
case GL_LINEAR:
return filament::TextureSampler::MagFilter::LINEAR;
}
return filament::TextureSampler::MagFilter::NEAREST;
}
inline bool getVertexAttrType(cgltf_attribute_type atype, filament::VertexAttribute* attrType) {
switch (atype) {
case cgltf_attribute_type_position:
*attrType = filament::VertexAttribute::POSITION;
return true;
case cgltf_attribute_type_texcoord:
*attrType = filament::VertexAttribute::UV0;
return true;
case cgltf_attribute_type_color:
*attrType = filament::VertexAttribute::COLOR;
return true;
case cgltf_attribute_type_joints:
*attrType = filament::VertexAttribute::BONE_INDICES;
return true;
case cgltf_attribute_type_weights:
*attrType = filament::VertexAttribute::BONE_WEIGHTS;
return true;
case cgltf_attribute_type_normal:
case cgltf_attribute_type_tangent:
default:
return false;
}
}
inline bool getIndexType(cgltf_component_type ctype, filament::IndexBuffer::IndexType* itype) {
switch (ctype) {
case cgltf_component_type_r_8u:
case cgltf_component_type_r_16u:
*itype = filament::IndexBuffer::IndexType::USHORT;
return true;
case cgltf_component_type_r_32u:
*itype = filament::IndexBuffer::IndexType::UINT;
return true;
default:
break;
}
return false;
}
inline bool getPrimitiveType(cgltf_primitive_type in,
filament::RenderableManager::PrimitiveType* out) {
switch (in) {
case cgltf_primitive_type_points:
*out = filament::RenderableManager::PrimitiveType::POINTS;
return true;
case cgltf_primitive_type_lines:
*out = filament::RenderableManager::PrimitiveType::LINES;
return true;
case cgltf_primitive_type_triangles:
*out = filament::RenderableManager::PrimitiveType::TRIANGLES;
return true;
case cgltf_primitive_type_line_loop:
case cgltf_primitive_type_line_strip:
case cgltf_primitive_type_triangle_strip:
case cgltf_primitive_type_triangle_fan:
return false;
}
return false;
}
inline bool getElementType(cgltf_type type, cgltf_component_type ctype,
filament::VertexBuffer::AttributeType* atype) {
switch (type) {
case cgltf_type_scalar:
switch (ctype) {
case cgltf_component_type_r_8:
*atype = filament::VertexBuffer::AttributeType::BYTE;
return true;
case cgltf_component_type_r_8u:
*atype = filament::VertexBuffer::AttributeType::UBYTE;
return true;
case cgltf_component_type_r_16:
*atype = filament::VertexBuffer::AttributeType::SHORT;
return true;
case cgltf_component_type_r_16u:
*atype = filament::VertexBuffer::AttributeType::USHORT;
return true;
case cgltf_component_type_r_32u:
*atype = filament::VertexBuffer::AttributeType::UINT;
return true;
case cgltf_component_type_r_32f:
*atype = filament::VertexBuffer::AttributeType::FLOAT;
return true;
default:
return false;
}
break;
case cgltf_type_vec2:
switch (ctype) {
case cgltf_component_type_r_8:
*atype = filament::VertexBuffer::AttributeType::BYTE2;
return true;
case cgltf_component_type_r_8u:
*atype = filament::VertexBuffer::AttributeType::UBYTE2;
return true;
case cgltf_component_type_r_16:
*atype = filament::VertexBuffer::AttributeType::SHORT2;
return true;
case cgltf_component_type_r_16u:
*atype = filament::VertexBuffer::AttributeType::USHORT2;
return true;
case cgltf_component_type_r_32f:
*atype = filament::VertexBuffer::AttributeType::FLOAT2;
return true;
default:
return false;
}
break;
case cgltf_type_vec3:
switch (ctype) {
case cgltf_component_type_r_8:
*atype = filament::VertexBuffer::AttributeType::BYTE3;
return true;
case cgltf_component_type_r_8u:
*atype = filament::VertexBuffer::AttributeType::UBYTE3;
return true;
case cgltf_component_type_r_16:
*atype = filament::VertexBuffer::AttributeType::SHORT3;
return true;
case cgltf_component_type_r_16u:
*atype = filament::VertexBuffer::AttributeType::USHORT3;
return true;
case cgltf_component_type_r_32f:
*atype = filament::VertexBuffer::AttributeType::FLOAT3;
return true;
default:
return false;
}
break;
case cgltf_type_vec4:
switch (ctype) {
case cgltf_component_type_r_8:
*atype = filament::VertexBuffer::AttributeType::BYTE4;
return true;
case cgltf_component_type_r_8u:
*atype = filament::VertexBuffer::AttributeType::UBYTE4;
return true;
case cgltf_component_type_r_16:
*atype = filament::VertexBuffer::AttributeType::SHORT4;
return true;
case cgltf_component_type_r_16u:
*atype = filament::VertexBuffer::AttributeType::USHORT4;
return true;
case cgltf_component_type_r_32f:
*atype = filament::VertexBuffer::AttributeType::FLOAT4;
return true;
default:
return false;
}
break;
default:
return false;
}
return false;
}
#endif // GLTFIO_GLTFENUMS_H
+316
View File
@@ -0,0 +1,316 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include "MaterialGenerator.h"
#include <filamat/MaterialBuilder.h>
#include <utils/Log.h>
#include <string>
using namespace filamat;
using namespace filament;
using namespace utils;
namespace gltfio {
namespace details {
bool MaterialGenerator::EqualFn::operator()(const MaterialKey& k1, const MaterialKey& k2) const {
return
(k1.doubleSided == k2.doubleSided) &&
(k1.unlit == k2.unlit) &&
(k1.hasVertexColors == k2.hasVertexColors) &&
(k1.hasBaseColorTexture == k2.hasBaseColorTexture) &&
(k1.hasMetallicRoughnessTexture == k2.hasMetallicRoughnessTexture) &&
(k1.hasNormalTexture == k2.hasNormalTexture) &&
(k1.hasOcclusionTexture == k2.hasOcclusionTexture) &&
(k1.hasEmissiveTexture == k2.hasEmissiveTexture) &&
(k1.alphaMode == k2.alphaMode) &&
(k1.baseColorUV == k2.baseColorUV) &&
(k1.metallicRoughnessUV == k2.metallicRoughnessUV) &&
(k1.emissiveUV == k2.emissiveUV) &&
(k1.aoUV == k2.aoUV) &&
(k1.normalUV == k2.normalUV) &&
(k1.alphaMaskThreshold == k2.alphaMaskThreshold);
}
MaterialGenerator::MaterialGenerator(Engine* engine) : mEngine(engine) {
MaterialBuilder::init();
}
size_t MaterialGenerator::getMaterialsCount() const noexcept {
return mMaterials.size();
}
const Material* const* MaterialGenerator::getMaterials() const noexcept {
return mMaterials.data();
}
void MaterialGenerator::destroyMaterials() {
for (auto& iter : mCache) {
mEngine->destroy(iter.second);
}
mMaterials.clear();
mCache.clear();
}
static std::string shaderFromKey(const MaterialKey& config, const UvMap& uvmap) {
const auto normalUV = std::to_string(uvmap[config.normalUV] - 1);
const auto baseColorUV = std::to_string(uvmap[config.baseColorUV] - 1);
const auto metallicRoughnessUV = std::to_string(uvmap[config.metallicRoughnessUV] - 1);
const auto emissiveUV = std::to_string(uvmap[config.emissiveUV] - 1);
const auto aoUV = std::to_string(uvmap[config.aoUV] - 1);
std::string shader = R"SHADER(
// Sigh, assimp flips texture coords in its glTF2Importer, but we're not using assimp so we
// need to flip them here.
#if defined(HAS_ATTRIBUTE_UV0)
float2 uv0() { vec2 uv = getUV0(); uv.y = 1.0 - uv.y; return uv; }
#endif
#if defined(HAS_ATTRIBUTE_UV1)
float2 uv1() { vec2 uv = getUV1(); uv.y = 1.0 - uv.y; return uv; }
#endif
void material(inout MaterialInputs material) {
)SHADER";
if (config.hasNormalTexture && !config.unlit) {
shader += "float2 normalUV = uv" + normalUV + "();\n";
if (config.hasTextureTransforms) {
shader += "normalUV = (vec3(normalUV, 1.0) * materialParams.normalUvMatrix).xy;\n";
}
shader += R"SHADER(
material.normal = texture(materialParams_normalMap, normalUV).xyz * 2.0 - 1.0;
material.normal.y = -material.normal.y;
material.normal.xy *= materialParams.normalScale;
)SHADER";
}
shader += R"SHADER(
prepareMaterial(material);
material.baseColor = materialParams.baseColorFactor;
)SHADER";
if (config.hasBaseColorTexture) {
shader += "float2 baseColorUV = uv" + baseColorUV + "();\n";
if (config.hasTextureTransforms) {
shader += "baseColorUV = (vec3(baseColorUV, 1.0) * materialParams.baseColorUvMatrix).xy;\n";
}
shader += R"SHADER(
material.baseColor *= texture(materialParams_baseColorMap, baseColorUV);
)SHADER";
}
if (config.alphaMode == AlphaMode::TRANSPARENT) {
shader += R"SHADER(
material.baseColor.rgb *= material.baseColor.a;
)SHADER";
}
if (config.hasVertexColors) {
shader += "material.baseColor *= getColor();\n";
}
if (!config.unlit) {
shader += R"SHADER(
material.roughness = materialParams.roughnessFactor;
material.metallic = materialParams.metallicFactor;
material.emissive.rgb = materialParams.emissiveFactor.rgb;
)SHADER";
if (config.hasMetallicRoughnessTexture) {
shader += "float2 metallicRoughnessUV = uv" + metallicRoughnessUV + "();\n";
if (config.hasTextureTransforms) {
shader += "metallicRoughnessUV = (vec3(metallicRoughnessUV, 1.0) * materialParams.metallicRoughnessUvMatrix).xy;\n";
}
shader += R"SHADER(
vec4 roughness = texture(materialParams_metallicRoughnessMap, metallicRoughnessUV);
material.roughness *= roughness.g;
material.metallic *= roughness.b;
)SHADER";
}
if (config.hasOcclusionTexture) {
shader += "float2 aoUV = uv" + aoUV + "();\n";
if (config.hasTextureTransforms) {
shader += "aoUV = (vec3(aoUV, 1.0) * materialParams.occlusionUvMatrix).xy;\n";
}
shader += R"SHADER(
material.ambientOcclusion = texture(materialParams_occlusionMap, aoUV).r *
materialParams.aoStrength;
)SHADER";
}
if (config.hasEmissiveTexture) {
shader += "float2 emissiveUV = uv" + emissiveUV + "();\n";
if (config.hasTextureTransforms) {
shader += "aoUV = (vec3(emissiveUV, 1.0) * materialParams.emissiveUvMatrix).xy;\n";
}
shader += R"SHADER(
material.emissive.rgb *= texture(materialParams_emissiveMap, emissiveUV).rgb;
material.emissive.a = 3.0;
)SHADER";
}
}
shader += "}\n";
return shader;
}
// Filament supports up to 2 UV sets. glTF has arbitrary texcoord set indices, but it allows
// implementations to support only 2 simultaneous sets. Here we build a mapping table with 1-based
// indices where 0 means unused. Note that the order in which we drop textures can affect the look
// of certain assets. This "order of degradation" is stipulated by the glTF 2.0 specification.
static void constrainMaterial(MaterialKey* key, UvMap* uvmap) {
const int MAX_INDEX = 2;
UvMap retval {};
int index = 1;
if (key->hasBaseColorTexture) {
retval[key->baseColorUV] = (UvSet) index++;
}
if (key->hasMetallicRoughnessTexture && retval[key->metallicRoughnessUV] == UNUSED) {
retval[key->metallicRoughnessUV] = (UvSet) index++;
}
if (key->hasNormalTexture && retval[key->normalUV] == UNUSED) {
if (index > MAX_INDEX) {
key->hasNormalTexture = false;
} else {
retval[key->normalUV] = (UvSet) index++;
}
}
if (key->hasOcclusionTexture && retval[key->aoUV] == UNUSED) {
if (index > MAX_INDEX) {
key->hasOcclusionTexture = false;
} else {
retval[key->aoUV] = (UvSet) index++;
}
}
if (key->hasEmissiveTexture && retval[key->emissiveUV] == UNUSED) {
if (index > MAX_INDEX) {
key->hasEmissiveTexture = false;
} else {
retval[key->emissiveUV] = (UvSet) index++;
}
}
*uvmap = retval;
}
static Material* createMaterial(Engine* engine, const MaterialKey& config, const UvMap& uvmap,
const char* name) {
using CullingMode = MaterialBuilder::CullingMode;
std::string shader = shaderFromKey(config, uvmap);
MaterialBuilder builder = MaterialBuilder()
.name(name)
.material(shader.c_str())
.culling(config.doubleSided ? CullingMode::NONE : CullingMode::BACK)
.doubleSided(config.doubleSided);
auto uvset = (uint8_t*) &uvmap.front();
static_assert(std::tuple_size<UvMap>::value == 8, "Badly sized uvset.");
int numTextures = std::max({
uvset[0], uvset[1], uvset[2], uvset[3],
uvset[4], uvset[5], uvset[6], uvset[7],
});
if (numTextures > 0) {
builder.require(VertexAttribute::UV0);
}
if (numTextures > 1) {
builder.require(VertexAttribute::UV1);
}
// BASE COLOR
builder.parameter(MaterialBuilder::UniformType::FLOAT4, "baseColorFactor");
if (config.hasBaseColorTexture) {
builder.parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "baseColorMap");
if (config.hasTextureTransforms) {
builder.parameter(MaterialBuilder::UniformType::MAT3, "baseColorUvMatrix");
}
}
if (config.hasVertexColors) {
builder.require(VertexAttribute::COLOR);
}
// METALLIC-ROUGHNESS
builder.parameter(MaterialBuilder::UniformType::FLOAT, "metallicFactor");
builder.parameter(MaterialBuilder::UniformType::FLOAT, "roughnessFactor");
if (config.hasMetallicRoughnessTexture) {
builder.parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "metallicRoughnessMap");
if (config.hasTextureTransforms) {
builder.parameter(MaterialBuilder::UniformType::MAT3, "metallicRoughnessUvMatrix");
}
}
// NORMAL MAP
// In the glTF spec normalScale is in normalTextureInfo; in cgltf it is part of texture_view.
builder.parameter(MaterialBuilder::UniformType::FLOAT, "normalScale");
if (config.hasNormalTexture) {
builder.parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "normalMap");
if (config.hasTextureTransforms) {
builder.parameter(MaterialBuilder::UniformType::MAT3, "normalUvMatrix");
}
}
// AMBIENT OCCLUSION
// In the glTF spec aoStrength is in occlusionTextureInfo; in cgltf it is part of texture_view.
builder.parameter(MaterialBuilder::UniformType::FLOAT, "aoStrength");
if (config.hasOcclusionTexture) {
builder.parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "occlusionMap");
if (config.hasTextureTransforms) {
builder.parameter(MaterialBuilder::UniformType::MAT3, "occlusionUvMatrix");
}
}
// EMISSIVE
builder.parameter(MaterialBuilder::UniformType::FLOAT3, "emissiveFactor");
if (config.hasEmissiveTexture) {
builder.parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "emissiveMap");
if (config.hasTextureTransforms) {
builder.parameter(MaterialBuilder::UniformType::MAT3, "emissiveUvMatrix");
}
}
switch(config.alphaMode) {
case AlphaMode::MASKED:
builder.blending(MaterialBuilder::BlendingMode::MASKED);
builder.maskThreshold(config.alphaMaskThreshold);
break;
case AlphaMode::TRANSPARENT:
builder.blending(MaterialBuilder::BlendingMode::TRANSPARENT);
break;
default:
builder.blending(MaterialBuilder::BlendingMode::OPAQUE);
}
builder.shading(config.unlit ? Shading::UNLIT : Shading::LIT);
Package pkg = builder.build();
return Material::Builder().package(pkg.getData(), pkg.getSize()).build(*engine);
}
Material* MaterialGenerator::getOrCreateMaterial(MaterialKey* config, UvMap* uvmap,
const char* label) {
constrainMaterial(config, uvmap);
auto iter = mCache.find(*config);
if (iter == mCache.end()) {
Material* mat = createMaterial(mEngine, *config, *uvmap, label);
mCache.emplace(std::make_pair(*config, mat));
mMaterials.push_back(mat);
return mat;
}
return iter->second;
}
} // namespace details
} // namespace gltfio
+91
View File
@@ -0,0 +1,91 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_MATERIALGENERATOR_H
#define GLTFIO_MATERIALGENERATOR_H
#include <filament/Engine.h>
#include <filament/Material.h>
#include <utils/Hash.h>
#include <tsl/robin_map.h>
#include <array>
namespace gltfio {
namespace details {
enum class AlphaMode : uint8_t {
OPAQUE,
MASKED,
TRANSPARENT
};
struct MaterialKey {
bool doubleSided : 1;
bool unlit : 1;
bool hasVertexColors : 1;
bool hasBaseColorTexture : 1;
bool hasMetallicRoughnessTexture : 1;
bool hasNormalTexture : 1;
bool hasOcclusionTexture : 1;
bool hasEmissiveTexture : 1;
AlphaMode alphaMode;
uint8_t baseColorUV;
uint8_t metallicRoughnessUV;
uint8_t emissiveUV;
uint8_t aoUV;
uint8_t normalUV;
float alphaMaskThreshold;
bool hasTextureTransforms;
};
// Define a mapping from a uv set index in the source asset to one of Filament's uv sets.
enum UvSet : uint8_t { UNUSED, UV0, UV1 };
using UvMap = std::array<UvSet, 8>;
// The MaterialGenerator uses filamat to generate materials that each unconditionally make the
// minimum number of texture lookups. This complexity could be avoided if we were to use an
// ubershader approach, but this allows us to generate efficient and streamlined shaders that have
// no branching.
class MaterialGenerator final {
public:
MaterialGenerator(filament::Engine* engine);
// Creates or fetches a compiled Filament material. The given configuration key might be mutated
// due to resource constraints. The second argument is populated with a small table that maps
// from a glTF uv index to a Filament uv index. The third argument is an optional tag that
// is not a part of the cache key.
filament::Material* getOrCreateMaterial(MaterialKey* config, UvMap* uvmap,
const char* label = "material");
size_t getMaterialsCount() const noexcept;
const filament::Material* const* getMaterials() const noexcept;
void destroyMaterials();
private:
using HashFn = utils::hash::MurmurHashFn<MaterialKey>;
struct EqualFn { bool operator()(const MaterialKey& k1, const MaterialKey& k2) const; };
tsl::robin_map<MaterialKey, filament::Material*, HashFn, EqualFn> mCache;
std::vector<filament::Material*> mMaterials;
filament::Engine* mEngine;
};
} // namespace details
} // namespace gltfio
#endif // GLTFIO_MATERIALGENERATOR_H
+412
View File
@@ -0,0 +1,412 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include <gltfio/ResourceLoader.h>
#include "FFilamentAsset.h"
#include "upcast.h"
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/MaterialInstance.h>
#include <filament/Texture.h>
#include <filament/VertexBuffer.h>
#include <geometry/SurfaceOrientation.h>
#include <math/quat.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/Log.h>
#include <cgltf.h>
#include <stb_image.h>
#include <tsl/robin_map.h>
#include <string>
using namespace filament;
using namespace filament::math;
using namespace utils;
namespace gltfio {
namespace details {
// The AssetPool tracks references to raw source data (cgltf hierarchies) and frees them
// appropriately. It releases all source assets only after the pending upload count is zero and the
// client has destroyed the ResourceLoader object. If the ResourceLoader is destroyed while uploads
// are still pending, then the AssetPool will stay alive until all uploads are complete.
class AssetPool {
public:
AssetPool() {}
~AssetPool() {
for (auto asset : mAssets) {
asset->releaseSourceAsset();
}
}
void addAsset(FFilamentAsset* asset) {
mAssets.push_back(asset);
asset->acquireSourceAsset();
}
void addPendingUpload() {
++mPendingUploads;
}
static void onLoadedResource(void* buffer, size_t size, void* user) {
auto pool = (AssetPool*) user;
if (--pool->mPendingUploads == 0 && pool->mLoaderDestroyed) {
delete pool;
}
}
void onLoaderDestroyed() {
if (mPendingUploads == 0) {
delete this;
} else {
mLoaderDestroyed = true;
}
}
private:
std::vector<FFilamentAsset*> mAssets;
bool mLoaderDestroyed = false;
int mPendingUploads = 0;
};
} // namespace details
using namespace details;
ResourceLoader::ResourceLoader(const ResourceConfiguration& config) : mConfig(config),
mPool(new AssetPool) {}
ResourceLoader::~ResourceLoader() {
mPool->onLoaderDestroyed();
}
static void importSkinningData(Skin& dstSkin, const cgltf_skin& srcSkin) {
const cgltf_accessor* srcMatrices = srcSkin.inverse_bind_matrices;
dstSkin.inverseBindMatrices.resize(srcSkin.joints_count);
if (srcMatrices) {
auto dstMatrices = (uint8_t*) dstSkin.inverseBindMatrices.data();
auto srcBuffer = srcMatrices->buffer_view->buffer->data;
memcpy(dstMatrices, srcBuffer, srcSkin.joints_count * sizeof(mat4f));
}
}
static void convertBytesToShorts(uint16_t* dst, const uint8_t* src, size_t count) {
for (size_t i = 0; i < count; ++i) {
dst[i] = src[i];
}
}
static void generateTrivialIndices(uint32_t* dst, size_t numVertices) {
for (size_t i = 0; i < numVertices; ++i) {
dst[i] = i;
}
}
bool ResourceLoader::loadResources(FilamentAsset* asset) {
FFilamentAsset* fasset = upcast(asset);
mPool->addAsset(fasset);
auto gltf = (cgltf_data*) fasset->mSourceAsset;
// Read data from the file system and base64 URLs.
cgltf_options options {};
cgltf_result result = cgltf_load_buffers(&options, gltf, mConfig.basePath.c_str());
if (result != cgltf_result_success) {
slog.e << "Unable to load resources." << io::endl;
return false;
}
// To be robust against the glTF conformance suite, we optionally ensure that skinning weights
// sum to 1.0 at every vertex. Note that if the same weights buffer is shared in multiple
// places, this will needlessly repeat the work. In the future we would like to remove this
// feature, and instead simply require correct models. See also:
// https://github.com/KhronosGroup/glTF-Sample-Models/issues/215
if (mConfig.normalizeSkinningWeights) {
normalizeWeights(fasset);
}
// Upload data to the GPU.
const BufferBinding* bindings = asset->getBufferBindings();
for (size_t i = 0, n = asset->getBufferBindingCount(); i < n; ++i) {
auto bb = bindings[i];
const uint8_t* data8 = bb.data ? (bb.offset + (const uint8_t*) *bb.data) : nullptr;
if (bb.vertexBuffer) {
mPool->addPendingUpload();
VertexBuffer::BufferDescriptor bd(data8, bb.size, AssetPool::onLoadedResource, mPool);
bb.vertexBuffer->setBufferAt(*mConfig.engine, bb.bufferIndex, std::move(bd));
} else if (bb.generateTrivialIndices) {
uint32_t* data32 = (uint32_t*) malloc(bb.size);
generateTrivialIndices(data32, bb.size / sizeof(uint32_t));
auto callback = (IndexBuffer::BufferDescriptor::Callback) free;
IndexBuffer::BufferDescriptor bd(data32, bb.size, callback);
bb.indexBuffer->setBuffer(*mConfig.engine, std::move(bd));
} else if (bb.convertBytesToShorts) {
size_t size16 = bb.size * 2;
uint16_t* data16 = (uint16_t*) malloc(size16);
convertBytesToShorts(data16, data8, bb.size);
auto callback = (IndexBuffer::BufferDescriptor::Callback) free;
IndexBuffer::BufferDescriptor bd(data16, size16, callback);
bb.indexBuffer->setBuffer(*mConfig.engine, std::move(bd));
} else if (bb.indexBuffer) {
mPool->addPendingUpload();
IndexBuffer::BufferDescriptor bd(data8, bb.size, AssetPool::onLoadedResource, mPool);
bb.indexBuffer->setBuffer(*mConfig.engine, std::move(bd));
}
}
// Copy over the inverse bind matrices to allow users to destroy the source asset.
for (cgltf_size i = 0, len = gltf->skins_count; i < len; ++i) {
importSkinningData(fasset->mSkins[i], gltf->skins[i]);
}
// Compute surface orientation quaternions if necessary.
computeTangents(fasset);
// Finally, load image files and create Filament Textures.
return createTextures(fasset);
}
bool ResourceLoader::createTextures(details::FFilamentAsset* asset) const {
// Define a simple functor that creates a Filament Texture from a blob of texels.
// TODO: this could be optimized, e.g. do not generate mips if never mipmap-sampled, and use a
// more compact format when possible.
auto createTexture = [this, asset](stbi_uc* texels, uint32_t w, uint32_t h, bool srgb) {
Texture *tex = Texture::Builder()
.width(w)
.height(h)
.levels(0xff)
.format(srgb ? driver::TextureFormat::SRGB8_A8 : driver::TextureFormat::RGBA8)
.build(*mConfig.engine);
Texture::PixelBufferDescriptor pbd(texels,
size_t(w * h * 4),
Texture::Format::RGBA,
Texture::Type::UBYTE,
(driver::BufferDescriptor::Callback) &free);
tex->setImage(*mConfig.engine, 0, std::move(pbd));
tex->generateMipmaps(*mConfig.engine);
asset->mTextures.push_back(tex);
return tex;
};
// Decode textures and associate them with material instance parameters.
// To prevent needless re-decoding, we create a small map of Filament Texture objects where the
// map key is (usually) a pointer to a URI string. In the case of buffer view textures, the
// cache key is a data pointer.
stbi_uc* texels;
int width, height, comp;
Texture* tex;
tsl::robin_map<void*, Texture*> textures;
const TextureBinding* texbindings = asset->getTextureBindings();
for (size_t i = 0, n = asset->getTextureBindingCount(); i < n; ++i) {
auto tb = texbindings[i];
if (tb.data) {
tex = textures[tb.data];
if (!tex) {
texels = stbi_load_from_memory((const stbi_uc*) *tb.data, tb.totalSize,
&width, &height, &comp, 4);
if (texels == nullptr) {
slog.e << "Unable to decode texture. " << io::endl;
return false;
}
textures[tb.data] = tex = createTexture(texels, width, height, tb.srgb);
}
} else {
tex = textures[(void*) tb.uri];
if (!tex) {
utils::Path fullpath = this->mConfig.basePath + tb.uri;
texels = stbi_load(fullpath.c_str(), &width, &height, &comp, 4);
if (texels == nullptr) {
slog.e << "Unable to decode texture: " << tb.uri << io::endl;
return false;
}
textures[(void*) tb.uri] = tex = createTexture(texels, width, height, tb.srgb);
}
}
tb.materialInstance->setParameter(tb.materialParameter, tex, tb.sampler);
}
return true;
}
void ResourceLoader::computeTangents(FFilamentAsset* asset) const {
// Declare vectors of normals and tangents, which we'll extract & convert from the source.
std::vector<float3> fp32Normals;
std::vector<float4> fp32Tangents;
std::vector<float3> fp32Positions;
std::vector<float2> fp32TexCoords;
std::vector<uint3> ui32Triangles;
auto computeQuats = [&](const cgltf_primitive& prim) {
cgltf_size vertexCount = 0;
// Collect accessors for normals, tangents, etc.
const int NUM_ATTRIBUTES = 8;
int slots[NUM_ATTRIBUTES] = {};
const cgltf_accessor* accessors[NUM_ATTRIBUTES] = {};
for (cgltf_size slot = 0; slot < prim.attributes_count; slot++) {
const cgltf_attribute& attr = prim.attributes[slot];
// Ignore the second set of UV's.
if (attr.index != 0) {
continue;
}
vertexCount = attr.data->count;
slots[attr.type] = slot;
accessors[attr.type] = attr.data;
}
// At a minimum we need normals to generate tangents.
auto normalsInfo = accessors[cgltf_attribute_type_normal];
if (normalsInfo == nullptr || vertexCount == 0) {
return;
}
short4* quats = (short4*) malloc(sizeof(short4) * vertexCount);
geometry::SurfaceOrientation::Builder sob;
sob.vertexCount(vertexCount);
// Convert normals into packed floats.
assert(normalsInfo->count == vertexCount);
assert(normalsInfo->type == cgltf_type_vec3);
fp32Normals.resize(vertexCount);
for (cgltf_size i = 0; i < vertexCount; ++i) {
cgltf_accessor_read_float(normalsInfo, i, &fp32Normals[i].x, 3);
}
sob.normals(fp32Normals.data());
// Convert tangents into packed floats.
auto tangentsInfo = accessors[cgltf_attribute_type_tangent];
if (tangentsInfo) {
if (tangentsInfo->count != vertexCount || tangentsInfo->type != cgltf_type_vec4) {
slog.e << "Bad tangent count or type." << io::endl;
return;
}
fp32Tangents.resize(vertexCount);
for (cgltf_size i = 0; i < vertexCount; ++i) {
cgltf_accessor_read_float(tangentsInfo, i, &fp32Tangents[i].x, 4);
}
sob.tangents(fp32Tangents.data());
}
auto positionsInfo = accessors[cgltf_attribute_type_position];
if (positionsInfo) {
if (positionsInfo->count != vertexCount || positionsInfo->type != cgltf_type_vec3) {
slog.e << "Bad position count or type." << io::endl;
return;
}
fp32Positions.resize(vertexCount);
for (cgltf_size i = 0; i < vertexCount; ++i) {
cgltf_accessor_read_float(positionsInfo, i, &fp32Positions[i].x, 3);
}
sob.positions(fp32Positions.data());
}
if (prim.indices) {
size_t triangleCount = prim.indices->count / 3;
ui32Triangles.resize(triangleCount);
cgltf_size j = 0;
for (auto& triangle : ui32Triangles) {
triangle.x = cgltf_accessor_read_index(prim.indices, j++);
triangle.y = cgltf_accessor_read_index(prim.indices, j++);
triangle.z = cgltf_accessor_read_index(prim.indices, j++);
}
} else {
size_t triangleCount = vertexCount / 3;
ui32Triangles.resize(triangleCount);
cgltf_size j = 0;
for (auto& triangle : ui32Triangles) {
triangle.x = j++;
triangle.y = j++;
triangle.z = j++;
}
}
sob.triangleCount(ui32Triangles.size());
sob.triangles(ui32Triangles.data());
auto texcoordsInfo = accessors[cgltf_attribute_type_texcoord];
if (texcoordsInfo) {
if (texcoordsInfo->count != vertexCount || texcoordsInfo->type != cgltf_type_vec2) {
slog.e << "Bad texcoord count or type." << io::endl;
return;
}
fp32TexCoords.resize(vertexCount);
for (cgltf_size i = 0; i < vertexCount; ++i) {
cgltf_accessor_read_float(texcoordsInfo, i, &fp32TexCoords[i].x, 2);
}
sob.uvs(fp32TexCoords.data());
}
// Compute surface orientation quaternions.
auto helper = sob.build();
helper.getQuats(quats, vertexCount);
// Upload quaternions to the GPU.
auto callback = (VertexBuffer::BufferDescriptor::Callback) free;
VertexBuffer::BufferDescriptor bd(quats, vertexCount * sizeof(short4), callback);
VertexBuffer* vb = asset->mPrimMap.at(&prim);
vb->setBufferAt(*mConfig.engine, slots[cgltf_attribute_type_normal], std::move(bd));
};
for (auto iter : asset->mNodeMap) {
const cgltf_mesh* mesh = iter.first->mesh;
if (mesh) {
cgltf_size nprims = mesh->primitives_count;
for (cgltf_size index = 0; index < nprims; ++index) {
computeQuats(mesh->primitives[index]);
}
}
}
}
void ResourceLoader::normalizeWeights(details::FFilamentAsset* asset) const {
auto normalize = [](cgltf_accessor* data) {
if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) {
slog.w << "Cannot normalize weights, unsupported attribute type." << io::endl;
return;
}
uint8_t* bytes = (uint8_t*) data->buffer_view->buffer->data;
float4* floats = (float4*) (bytes + data->offset + data->buffer_view->offset);
for (cgltf_size i = 0; i < data->count; ++i) {
float4 weights = floats[i];
float sum = weights.x + weights.y + weights.z + weights.w;
floats[i] = weights / sum;
}
};
const cgltf_data* gltf = asset->mSourceAsset;
cgltf_size mcount = gltf->meshes_count;
for (cgltf_size mindex = 0; mindex < mcount; ++mindex) {
const cgltf_mesh& mesh = gltf->meshes[mindex];
cgltf_size pcount = mesh.primitives_count;
for (cgltf_size pindex = 0; pindex < pcount; ++pindex) {
const cgltf_primitive& prim = mesh.primitives[pindex];
cgltf_size acount = prim.attributes_count;
for (cgltf_size aindex = 0; aindex < acount; ++aindex) {
const auto& attr = prim.attributes[aindex];
if (attr.type == cgltf_attribute_type_weights) {
normalize(attr.data);
}
}
}
}
}
} // namespace gltfio
+110
View File
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef GLTFIO_MATH_H
#define GLTFIO_MATH_H
#include <math/quat.h>
#include <math/vec3.h>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/TVecHelpers.h>
namespace gltfio {
inline void decomposeMatrix(const filament::math::mat4f& mat, filament::math::float3* translation,
filament::math::quatf* rotation, filament::math::float3* scale) {
using namespace filament::math;
// Extract translation.
*translation = mat[3].xyz;
// Extract upper-left for determinant computation.
const float a = mat[0][0];
const float b = mat[0][1];
const float c = mat[0][2];
const float d = mat[1][0];
const float e = mat[1][1];
const float f = mat[1][2];
const float g = mat[2][0];
const float h = mat[2][1];
const float i = mat[2][2];
const float A = e * i - f * h;
const float B = f * g - d * i;
const float C = d * h - e * g;
// Extract scale.
const float det(a * A + b * B + c * C);
float scalex = length(float3({a, b, c}));
float scaley = length(float3({d, e, f}));
float scalez = length(float3({g, h, i}));
float3 s = { scalex, scaley, scalez };
if (det < 0) {
s = -s;
}
*scale = s;
// Remove scale from the matrix.
mat4f clone = mat;
clone[0] /= s.x;
clone[1] /= s.y;
clone[2] /= s.z;
// Extract rotation.
*rotation = clone.toQuaternion();
}
inline filament::math::mat4f composeMatrix(const filament::math::float3& translation,
const filament::math::quatf& rotation, const filament::math::float3& scale) {
float tx = translation[0];
float ty = translation[1];
float tz = translation[2];
float qx = rotation[0];
float qy = rotation[1];
float qz = rotation[2];
float qw = rotation[3];
float sx = scale[0];
float sy = scale[1];
float sz = scale[2];
return filament::math::mat4f(
(1 - 2 * qy*qy - 2 * qz*qz) * sx,
(2 * qx*qy + 2 * qz*qw) * sy,
(2 * qx*qz - 2 * qy*qw) * sz,
0.f,
(2 * qx*qy - 2 * qz*qw) * sx,
(1 - 2 * qx*qx - 2 * qz*qz) * sy,
(2 * qy*qz + 2 * qx*qw) * sz,
0.f,
(2 * qx*qz + 2 * qy*qw) * sx,
(2 * qy*qz - 2 * qx*qw) * sy,
(1 - 2 * qx*qx - 2 * qy*qy) * sz,
0.f, tx, ty, tz, 1.f);
}
inline filament::math::mat3f matrixFromUvTransform(const float offset[2], float rotation,
const float scale[2]) {
float tx = offset[0];
float ty = offset[1];
float sx = scale[0];
float sy = scale[1];
float c = cos(rotation);
float s = sin(rotation);
return filament::math::mat3f(sx * c, sx * s, tx, -sy * s, sy * c, ty, 0, 0, 1);
};
} // namespace gltfio
#endif // GLTFIO_MATH_H
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*/
#ifndef TNT_FILAMENT_UPCAST_H
#define TNT_FILAMENT_UPCAST_H
/*
* Generates functions to safely upcast a pointer Bar* to FBar*
* FILAMENT_UPCAST() should be included in the header file
* declaring FBar, e.g.:
*
* #include <Bar.h>
*
* class FBar : public Bar {
* };
*
* FILAMENT_UPCAST(Bar)
*
*/
#define FILAMENT_UPCAST(CLASS) \
inline F##CLASS& upcast(CLASS& that) noexcept { \
return static_cast<F##CLASS &>(that); \
} \
inline const F##CLASS& upcast(const CLASS& that) noexcept { \
return static_cast<const F##CLASS &>(that); \
} \
inline F##CLASS* upcast(CLASS* that) noexcept { \
return static_cast<F##CLASS *>(that); \
} \
inline F##CLASS const* upcast(CLASS const* that) noexcept { \
return static_cast<F##CLASS const *>(that); \
}
#endif // TNT_FILAMENT_UPCAST_H