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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user