From 90c17ccbadf6fff0b071ab1f5d785af5d8535712 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Wed, 10 Jul 2019 13:38:24 -0700 Subject: [PATCH] Filament now supports GPU vertex morphing. This works by aliasing CUSTOM0 - CUSTOM7 to morphing attributes, and by extending our existing skinning variant. This PR was tested against some upcoming changes to gltfio. Issue #1149, #1417 --- RELEASE_NOTES.md | 1 + .../src/main/cpp/RenderableManager.cpp | 16 ++++++++ .../android/filament/RenderableManager.java | 12 ++++++ docs/Materials.md.html | 2 +- filament/include/filament/RenderableManager.h | 3 ++ filament/src/Material.cpp | 2 +- filament/src/RenderPass.cpp | 4 +- filament/src/Scene.cpp | 10 +++++ filament/src/components/RenderableManager.cpp | 25 +++++++++-- filament/src/components/RenderableManager.h | 36 +++++++++++++--- filament/src/details/Scene.h | 22 +++++----- .../include/filament/MaterialEnums.h | 11 +++++ .../include/private/filament/UibGenerator.h | 6 ++- .../include/private/filament/Variant.h | 10 ++--- libs/filabridge/src/UibGenerator.cpp | 6 ++- libs/filamat/src/shaders/ShaderGenerator.cpp | 14 +++++-- shaders/src/depth_main.vs | 2 +- shaders/src/getters.vs | 41 +++++++++++-------- shaders/src/main.vs | 27 +++++++++--- tools/matc/src/matc/CommandlineConfig.cpp | 2 +- tools/matc/src/matc/ParametersProcessor.cpp | 2 +- web/filament-js/filament.d.ts | 41 ++++++++++++------- web/filament-js/jsbindings.cpp | 11 ++++- web/filament-js/jsenums.cpp | 10 ++++- 24 files changed, 243 insertions(+), 73 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6e99e8eb51..0d43ddbf54 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -3,6 +3,7 @@ This file contains one line summaries of commits that are worthy of mentioning in release notes. A new header is inserted each time a *tag* is created. +- Added support for vertex morphing. - Introduced custom attributes, accessible from the vertex shader. - Added Java / Kotlin bindings for KtxLoader. - Added JavaScript / Typescript bindings for the new `RenderTarget` class. diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index 4f03667b5b..caaa8c4aea 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -182,6 +182,12 @@ Java_com_google_android_filament_RenderableManager_nBuilderSkinningBones(JNIEnv* return 0; } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nBuilderMorphing(JNIEnv*, jclass, + jlong nativeBuilder, jboolean enabled) { + RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; + builder->morphing(enabled); +} extern "C" JNIEXPORT jint JNICALL @@ -218,6 +224,16 @@ Java_com_google_android_filament_RenderableManager_nSetBonesAsQuaternions(JNIEnv return 0; } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env, jclass, + jlong nativeRenderableManager, jint instance, jfloatArray weights) { + RenderableManager *rm = (RenderableManager *) nativeRenderableManager; + jfloat* vec = env->GetFloatArrayElements(weights, NULL); + math::float4 floatvec(vec[0], vec[1], vec[2], vec[3]); + env->ReleaseFloatArrayElements(weights, vec, JNI_ABORT); + rm->setMorphWeights((RenderableManager::Instance)instance, floatvec); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_nSetAxisAlignedBoundingBox(JNIEnv*, jclass, jlong nativeRenderableManager, jint i, jfloat cx, jfloat cy, jfloat cz, diff --git a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java index 2060398f53..41151788ad 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java @@ -169,6 +169,12 @@ public class RenderableManager { return this; } + @NonNull + public Builder morphing(boolean enabled) { + nBuilderMorphing(mNativeBuilder, enabled); + return this; + } + public void build(@NonNull Engine engine, @Entity int entity) { if (!nBuilderBuild(mNativeBuilder, engine.getNativeObject(), entity)) { throw new IllegalStateException( @@ -225,6 +231,10 @@ public class RenderableManager { } } + public void setMorphWeights(@EntityInstance int i, float[] weights) { + nSetMorphWeights(mNativeObject, i, weights); + } + public void setAxisAlignedBoundingBox(@EntityInstance int i, @NonNull Box aabb) { nSetAxisAlignedBoundingBox(mNativeObject, i, aabb.getCenter()[0], aabb.getCenter()[1], aabb.getCenter()[2], @@ -351,9 +361,11 @@ public class RenderableManager { private static native void nBuilderReceiveShadows(long nativeBuilder, boolean enabled); private static native void nBuilderSkinning(long nativeBuilder, int boneCount); private static native int nBuilderSkinningBones(long nativeBuilder, int boneCount, Buffer bones, int remaining); + private static native void nBuilderMorphing(long nativeBuilder, boolean enabled); private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset); private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset); + private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights); private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez); private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value); private static native void nSetPriority(long nativeRenderableManager, int i, int priority); diff --git a/docs/Materials.md.html b/docs/Materials.md.html index 3fd20bfcde..154c54e62f 100644 --- a/docs/Materials.md.html +++ b/docs/Materials.md.html @@ -1717,7 +1717,7 @@ variants: - `directionalLighting`, used when a directional light is present in the scene - `dynamicLighting`, used when a non-directional light (point, spot, etc.) is present in the scene - `shadowReceiver`, used when an object can receive shadows -- `skinning`, used when an object is animated using GPU skinning +- `skinning`, used when an object is animated using GPU skinning or vertex morphing Example: ``` diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 0d2f877e0d..ed7a7bab74 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -31,6 +31,7 @@ #include #include +#include #include @@ -86,6 +87,7 @@ public: Builder& skinning(size_t boneCount) noexcept; // 0 by default, 255 max Builder& skinning(size_t boneCount, Bone const* bones) noexcept; Builder& skinning(size_t boneCount, math::mat4f const* transforms) noexcept; + Builder& morphing(bool enable) noexcept; // false by default // Sets an ordering index for blended primitives that all live at the same Z value. Builder& blendOrder(size_t index, uint16_t order) noexcept; // 0 by default @@ -146,6 +148,7 @@ public: void setBones(Instance instance, Bone const* transforms, size_t boneCount = 1, size_t offset = 0) noexcept; void setBones(Instance instance, math::mat4f const* transforms, size_t boneCount = 1, size_t offset = 0) noexcept; + void setMorphWeights(Instance instance, math::float4 const& weights) noexcept; // getters... const Box& getAxisAlignedBoundingBox(Instance instance) const noexcept; diff --git a/filament/src/Material.cpp b/filament/src/Material.cpp index eeba2a6d26..05874db273 100644 --- a/filament/src/Material.cpp +++ b/filament/src/Material.cpp @@ -335,7 +335,7 @@ backend::Handle FMaterial::getSurfaceProgramSlow(uint8_t var .setUniformBlock(BindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib().getName()) .setUniformBlock(BindingPoints::PER_MATERIAL_INSTANCE, mUniformInterfaceBlock.getName()); - if (Variant(variantKey).hasSkinning()) { + if (Variant(variantKey).hasSkinningOrMorphing()) { pb.setUniformBlock(BindingPoints::PER_RENDERABLE_BONES, UibGenerator::getPerRenderableBonesUib().getName()); } diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 267ede40ca..87c6d386ba 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -366,7 +366,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, cmdColor.primitive.index = (uint16_t)i; cmdColor.primitive.perRenderableBones = soaBonesUbh[i]; materialVariant.setShadowReceiver(soaVisibility[i].receiveShadows & hasShadowing); - materialVariant.setSkinning(soaVisibility[i].skinning); + materialVariant.setSkinning(soaVisibility[i].skinning || soaVisibility[i].morphing); // we're assuming we're always doing the depth (either way, it's correct) // this will generate front to back rendering @@ -375,7 +375,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, cmdDepth.key |= makeField(distanceBits, DISTANCE_BITS_MASK, DISTANCE_BITS_SHIFT); cmdDepth.primitive.index = (uint16_t)i; cmdDepth.primitive.perRenderableBones = soaBonesUbh[i]; - cmdDepth.primitive.materialVariant.setSkinning(soaVisibility[i].skinning); + cmdDepth.primitive.materialVariant.setSkinning(soaVisibility[i].skinning || soaVisibility[i].morphing); const bool shadowCaster = soaVisibility[i].castShadows & hasShadowing; const bool writeDepthForShadowCasters = depthContainsShadowCasters & shadowCaster; diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 8b6340ce81..97c95e7164 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -124,6 +124,7 @@ void FScene::prepare(const mat4f& worldOriginTransform) { rcm.getBonesUbh(ri), worldAABB.center, 0, + rcm.getMorphWeights(ri), rcm.getLayerMask(ri), worldAABB.halfExtent, {}, {}); @@ -196,6 +197,15 @@ void FScene::updateUBOs(utils::Range visibleRenderables, backend::Hand UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, worldFromModelNormalMatrix), m); + + UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, skinningEnabled), + sceneData.elementAt(i).skinning); + + UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, morphingEnabled), + sceneData.elementAt(i).morphing); + + UniformBuffer::setUniform(buffer, + offset + offsetof(PerRenderableUib, morphWeights), sceneData.elementAt(i)); } // TODO: handle static objects separately diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 96db1bcce2..1c0fe04ac1 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -45,12 +45,14 @@ struct RenderableManager::BuilderDetails { bool mCulling : 1; bool mCastShadows : 1; bool mReceiveShadows : 1; + bool mMorphingEnabled : 1; size_t mSkinningBoneCount = 0; Bone const* mUserBones = nullptr; mat4f const* mUserBoneMatrices = nullptr; explicit BuilderDetails(size_t count) - : mEntries(count), mCulling(true), mCastShadows(false), mReceiveShadows(true) { + : mEntries(count), mCulling(true), mCastShadows(false), mReceiveShadows(true), + mMorphingEnabled(false) { } // this is only needed for the explicit instantiation below BuilderDetails() = default; @@ -152,6 +154,11 @@ RenderableManager::Builder& RenderableManager::Builder::skinning( return *this; } +RenderableManager::Builder& RenderableManager::Builder::morphing(bool enable) noexcept { + mImpl->mMorphingEnabled = enable; + return *this; +} + RenderableManager::Builder& RenderableManager::Builder::blendOrder(size_t index, uint16_t blendOrder) noexcept { if (index < mImpl->mEntries.size()) { mImpl->mEntries[index].blendOrder = blendOrder; @@ -274,9 +281,11 @@ void FRenderableManager::create( setReceiveShadows(ci, builder->mReceiveShadows); setCulling(ci, builder->mCulling); setSkinning(ci, false); + setMorphing(ci, builder->mMorphingEnabled); + setMorphWeights(ci, {0, 0, 0, 0}); const size_t count = builder->mSkinningBoneCount; - if (UTILS_UNLIKELY(count)) { + if (UTILS_UNLIKELY(count > 0 || builder->mMorphingEnabled)) { std::unique_ptr& bones = manager[ci].bones; // Note that we are sizing the bones UBO according to CONFIG_MAX_BONE_COUNT rather than // mSkinningBoneCount. According to the OpenGL ES 3.2 specification in 7.6.3 Uniform @@ -298,7 +307,7 @@ void FRenderableManager::create( }); assert(bones); if (bones) { - setSkinning(ci, true); + setSkinning(ci, count > 0); if (builder->mUserBones) { setBones(ci, builder->mUserBones, count); } else if (builder->mUserBoneMatrices) { @@ -491,6 +500,12 @@ void FRenderableManager::setBones(Instance ci, } } +void FRenderableManager::setMorphWeights(Instance ci, const float4& weights) noexcept { + if (ci) { + mManager[ci].morphWeights = weights; + } +} + void FRenderableManager::makeBone(PerRenderableUibBone* UTILS_RESTRICT out, mat4f const& t) noexcept { mat4f m(t); @@ -612,4 +627,8 @@ void RenderableManager::setBones(Instance instance, upcast(this)->setBones(instance, transforms, boneCount, offset); } +void RenderableManager::setMorphWeights(Instance instance, float4 const& weights) noexcept { + upcast(this)->setMorphWeights(instance, weights); +} + } // namespace filament diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 3b30c3e636..6f606122f3 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -48,12 +48,14 @@ class FRenderableManager : public RenderableManager { public: using Instance = RenderableManager::Instance; + // TODO: consider renaming, this pertains to material variants, not strictly visibility. struct Visibility { uint8_t priority : 3; bool castShadows : 1; bool receiveShadows : 1; bool culling : 1; bool skinning : 1; + bool morphing : 1; }; explicit FRenderableManager(FEngine& engine) noexcept; @@ -101,22 +103,27 @@ public: inline void setReceiveShadows(Instance instance, bool enable) noexcept; inline void setCulling(Instance instance, bool enable) noexcept; inline void setSkinning(Instance instance, bool enable) noexcept; + inline void setMorphing(Instance instance, bool enable) noexcept; inline void setPrimitives(Instance instance, utils::Slice const& primitives) noexcept; inline void setBones(Instance instance, Bone const* transforms, size_t boneCount, size_t offset = 0) noexcept; inline void setBones(Instance instance, math::mat4f const* transforms, size_t boneCount, size_t offset = 0) noexcept; + inline void setMorphWeights(Instance instance, const math::float4& weights) noexcept; inline bool isShadowCaster(Instance instance) const noexcept; inline bool isShadowReceiver(Instance instance) const noexcept; inline bool isCullingEnabled(Instance instance) const noexcept; + inline Box const& getAABB(Instance instance) const noexcept; inline Box const& getAxisAlignedBoundingBox(Instance instance) const noexcept { return getAABB(instance); } inline Visibility getVisibility(Instance instance) const noexcept; inline uint8_t getLayerMask(Instance instance) const noexcept; inline uint8_t getPriority(Instance instance) const noexcept; + inline filament::math::float4 getMorphWeights(Instance instance) const noexcept; inline backend::Handle getBonesUbh(Instance instance) const noexcept; + inline uint32_t getBoneCount(Instance instance) const noexcept; inline size_t getLevelCount(Instance instance) const noexcept { return 1; } @@ -152,17 +159,19 @@ private: enum { AABB, // user data LAYERS, // user data + MORPH_WEIGHTS, // user data VISIBILITY, // user data PRIMITIVES, // user data BONES, // filament data, UBO storing a pointer to the bones information }; using Base = utils::SingleInstanceComponentManager< - Box, - uint8_t, - Visibility, - utils::Slice, - std::unique_ptr + Box, // AABB + uint8_t, // LAYERS + filament::math::float4, // MORPH_WEIGHTS + Visibility, // VISIBILITY + utils::Slice, // PRIMITIVES + std::unique_ptr // BONES >; struct Sim : public Base { @@ -179,6 +188,7 @@ private: // this specific usage of union is permitted. All fields are identical Field aabb; Field layers; + Field morphWeights; Field visibility; Field primitives; Field bones; @@ -254,6 +264,13 @@ void FRenderableManager::setSkinning(Instance instance, bool enable) noexcept { } } +void FRenderableManager::setMorphing(Instance instance, bool enable) noexcept { + if (instance) { + Visibility& visibility = mManager[instance].visibility; + visibility.morphing = enable; + } +} + void FRenderableManager::setPrimitives(Instance instance, utils::Slice const& primitives) noexcept { if (instance) { @@ -286,6 +303,10 @@ uint8_t FRenderableManager::getPriority(Instance instance) const noexcept { return getVisibility(instance).priority; } +filament::math::float4 FRenderableManager::getMorphWeights(Instance instance) const noexcept { + return mManager[instance].morphWeights; +} + Box const& FRenderableManager::getAABB(Instance instance) const noexcept { return mManager[instance].aabb; } @@ -295,6 +316,11 @@ backend::Handle FRenderableManager::getBonesUbh(Instan return bones ? bones->handle : backend::Handle{}; } +inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexcept { + std::unique_ptr const& bones = mManager[instance].bones; + return bones ? bones->count : 0; +} + utils::Slice const& FRenderableManager::getRenderPrimitives( Instance instance, uint8_t level) const noexcept { return mManager[instance].primitives; diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index f5f44fb846..f2ebc9a73b 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -101,6 +101,7 @@ public: BONES_UBH, // 4 bones uniform buffer handle WORLD_AABB_CENTER, // 12 world-space bounding box center of the renderable VISIBLE_MASK, // 1 each bit represents a visibility in a pass + MORPH_WEIGHTS, // 4 floats for morphing // These are not needed anymore after culling LAYERS, // 1 layers @@ -112,16 +113,17 @@ public: }; using RenderableSoa = utils::StructureOfArrays< - utils::EntityInstance, - math::mat4f, - FRenderableManager::Visibility, - backend::Handle, - math::float3, - Culler::result_type, - uint8_t, - math::float3, - utils::Slice, - uint32_t + utils::EntityInstance, // RENDERABLE_INSTANCE + math::mat4f, // WORLD_TRANSFORM + FRenderableManager::Visibility, // VISIBILITY_STATE + backend::Handle, // BONES_UBH + math::float3, // WORLD_AABB_CENTER + Culler::result_type, // VISIBLE_MASK + math::float4, // MORPH_WEIGHTS + uint8_t, // LAYERS + math::float3, // WORLD_AABB_EXTENT + utils::Slice, // PRIMITIVES + uint32_t // SUMMED_PRIMITIVE_COUNT >; RenderableSoa const& getRenderableData() const noexcept { return mRenderableData; } diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index 0a2628f896..494e85b110 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -124,6 +124,17 @@ enum VertexAttribute : uint8_t { CUSTOM5 = 13, CUSTOM6 = 14, CUSTOM7 = 15, + + // Aliases for vertex morphing. + MORPH_POSITION_0 = CUSTOM0, + MORPH_POSITION_1 = CUSTOM1, + MORPH_POSITION_2 = CUSTOM2, + MORPH_POSITION_3 = CUSTOM3, + MORPH_TANGENTS_0 = CUSTOM4, + MORPH_TANGENTS_1 = CUSTOM5, + MORPH_TANGENTS_2 = CUSTOM6, + MORPH_TANGENTS_3 = CUSTOM7, + // this is limited by driver::MAX_VERTEX_ATTRIBUTE_COUNT }; diff --git a/libs/filabridge/include/private/filament/UibGenerator.h b/libs/filabridge/include/private/filament/UibGenerator.h index 90eb352f5e..8b9256bd7e 100644 --- a/libs/filabridge/include/private/filament/UibGenerator.h +++ b/libs/filabridge/include/private/filament/UibGenerator.h @@ -92,7 +92,11 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) // PerRenderableUib must have an alignment of 256 to be compatible with all versions of GLES. struct alignas(256) PerRenderableUib { filament::math::mat4f worldFromModelMatrix; - filament::math::mat3f worldFromModelNormalMatrix; + filament::math::mat3f worldFromModelNormalMatrix; // this gets expanded to 48 bytes during the copy to the UBO + alignas(16) filament::math::float4 morphWeights; + uint32_t skinningEnabled; // 0=disabled, 1=enabled, ignored unless variant & SKINNING_OR_MORPHING + uint32_t morphingEnabled; // 0=disabled, 1=enabled, ignored unless variant & SKINNING_OR_MORPHING + filament::math::float2 padding0; }; struct LightsUib { diff --git a/libs/filabridge/include/private/filament/Variant.h b/libs/filabridge/include/private/filament/Variant.h index e6c8405d50..c4d744cf66 100644 --- a/libs/filabridge/include/private/filament/Variant.h +++ b/libs/filabridge/include/private/filament/Variant.h @@ -52,11 +52,11 @@ namespace filament { static constexpr uint8_t DIRECTIONAL_LIGHTING = 0x01; // directional light present, per frame/world position static constexpr uint8_t DYNAMIC_LIGHTING = 0x02; // point, spot or area present, per frame/world position static constexpr uint8_t SHADOW_RECEIVER = 0x04; // receives shadows, per renderable - static constexpr uint8_t SKINNING = 0x08; // GPU skinning + static constexpr uint8_t SKINNING_OR_MORPHING = 0x08; // GPU skinning and/or morphing static constexpr uint8_t VERTEX_MASK = DIRECTIONAL_LIGHTING | SHADOW_RECEIVER | - SKINNING; + SKINNING_OR_MORPHING; static constexpr uint8_t FRAGMENT_MASK = DIRECTIONAL_LIGHTING | DYNAMIC_LIGHTING | @@ -71,17 +71,17 @@ namespace filament { static constexpr uint8_t DEPTH_VARIANT = SHADOW_RECEIVER; // this mask filters out the lighting variants - static constexpr uint8_t UNLIT_MASK = SKINNING; + static constexpr uint8_t UNLIT_MASK = SKINNING_OR_MORPHING; static_assert((VERTEX_MASK | FRAGMENT_MASK) == VARIANT_COUNT - 1, "inconsistency between vertex/fragment masks and variant count"); - inline bool hasSkinning() const noexcept { return key & SKINNING; } + inline bool hasSkinningOrMorphing() const noexcept { return key & SKINNING_OR_MORPHING; } inline bool hasDirectionalLighting() const noexcept { return key & DIRECTIONAL_LIGHTING; } inline bool hasDynamicLighting() const noexcept { return key & DYNAMIC_LIGHTING; } inline bool hasShadowReceiver() const noexcept { return key & SHADOW_RECEIVER; } - inline void setSkinning(bool v) noexcept { set(v, SKINNING); } + inline void setSkinning(bool v) noexcept { set(v, SKINNING_OR_MORPHING); } inline void setDirectionalLighting(bool v) noexcept { set(v, DIRECTIONAL_LIGHTING); } inline void setDynamicLighting(bool v) noexcept { set(v, DYNAMIC_LIGHTING); } inline void setShadowReceiver(bool v) noexcept { set(v, SHADOW_RECEIVER); } diff --git a/libs/filabridge/src/UibGenerator.cpp b/libs/filabridge/src/UibGenerator.cpp index 2a141318ab..a271769132 100644 --- a/libs/filabridge/src/UibGenerator.cpp +++ b/libs/filabridge/src/UibGenerator.cpp @@ -75,7 +75,7 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { .add("userTime", 1, UniformInterfaceBlock::Type::FLOAT4) // ibl max mip level .add("iblMaxMipLevel", 1, UniformInterfaceBlock::Type::FLOAT2) - .add("padding10", 1, UniformInterfaceBlock::Type::FLOAT2) + .add("padding0", 1, UniformInterfaceBlock::Type::FLOAT2) // bring size to 1 KiB .add("padding1", 16, UniformInterfaceBlock::Type::FLOAT4) .build(); @@ -87,6 +87,10 @@ UniformInterfaceBlock const& UibGenerator::getPerRenderableUib() noexcept { .name("ObjectUniforms") .add("worldFromModelMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH) .add("worldFromModelNormalMatrix", 1, UniformInterfaceBlock::Type::MAT3, Precision::HIGH) + .add("morphWeights", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH) + .add("skinningEnabled", 1, UniformInterfaceBlock::Type::INT) + .add("morphingEnabled", 1, UniformInterfaceBlock::Type::INT) + .add("padding0", 1, UniformInterfaceBlock::Type::FLOAT2) .build(); return uib; } diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index d078e0ac6f..9706f88030 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -167,14 +167,22 @@ const std::string ShaderGenerator::createVertexProgram(filament::backend::Shader cg.generateDefine(vs, "HAS_DIRECTIONAL_LIGHTING", litVariants && variant.hasDirectionalLighting()); cg.generateDefine(vs, "HAS_SHADOWING", litVariants && variant.hasShadowReceiver()); cg.generateDefine(vs, "HAS_SHADOW_MULTIPLIER", material.hasShadowMultiplier); - cg.generateDefine(vs, "HAS_SKINNING", variant.hasSkinning()); + cg.generateDefine(vs, "HAS_SKINNING_OR_MORPHING", variant.hasSkinningOrMorphing()); cg.generateDefine(vs, getShadingDefine(material.shading), true); generateMaterialDefines(vs, cg, mProperties); AttributeBitset attributes = material.requiredAttributes; - if (variant.hasSkinning()) { + if (variant.hasSkinningOrMorphing()) { attributes.set(VertexAttribute::BONE_INDICES); attributes.set(VertexAttribute::BONE_WEIGHTS); + attributes.set(VertexAttribute::MORPH_POSITION_0); + attributes.set(VertexAttribute::MORPH_POSITION_1); + attributes.set(VertexAttribute::MORPH_POSITION_2); + attributes.set(VertexAttribute::MORPH_POSITION_3); + attributes.set(VertexAttribute::MORPH_TANGENTS_0); + attributes.set(VertexAttribute::MORPH_TANGENTS_1); + attributes.set(VertexAttribute::MORPH_TANGENTS_2); + attributes.set(VertexAttribute::MORPH_TANGENTS_3); } cg.generateShaderInputs(vs, ShaderType::VERTEX, attributes, interpolation); @@ -192,7 +200,7 @@ const std::string ShaderGenerator::createVertexProgram(filament::backend::Shader BindingPoints::PER_VIEW, UibGenerator::getPerViewUib()); cg.generateUniforms(vs, ShaderType::VERTEX, BindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib()); - if (variant.hasSkinning()) { + if (variant.hasSkinningOrMorphing()) { cg.generateUniforms(vs, ShaderType::VERTEX, BindingPoints::PER_RENDERABLE_BONES, UibGenerator::getPerRenderableBonesUib()); diff --git a/shaders/src/depth_main.vs b/shaders/src/depth_main.vs index 302d1a53dc..3b35446250 100644 --- a/shaders/src/depth_main.vs +++ b/shaders/src/depth_main.vs @@ -4,7 +4,7 @@ void materialVertex(inout MaterialVertexInputs m) { } void main() { #if defined(VERTEX_DOMAIN_DEVICE) - gl_Position = getSkinnedPosition(); + gl_Position = getPosition(); #else MaterialVertexInputs material; initMaterialVertex(material); diff --git a/shaders/src/getters.vs b/shaders/src/getters.vs index 3536459bc7..9702164a91 100644 --- a/shaders/src/getters.vs +++ b/shaders/src/getters.vs @@ -20,7 +20,7 @@ mat3 getWorldFromModelNormalMatrix() { // Attributes access //------------------------------------------------------------------------------ -#if defined(HAS_SKINNING) +#if defined(HAS_SKINNING_OR_MORPHING) vec3 mulBoneNormal(vec3 n, uint i) { vec4 q = bonesUniforms.bones[i + 0u]; vec3 is = bonesUniforms.bones[i + 3u].xyz; @@ -33,7 +33,7 @@ vec3 mulBoneNormal(vec3 n, uint i) { return n; } -vec3 mulBoneVertice(vec3 v, uint i) { +vec3 mulBoneVertex(vec3 v, uint i) { vec4 q = bonesUniforms.bones[i + 0u]; vec3 t = bonesUniforms.bones[i + 1u].xyz; vec3 s = bonesUniforms.bones[i + 2u].xyz; @@ -56,23 +56,32 @@ void skinNormal(inout vec3 n, const uvec4 ids, const vec4 weights) { } void skinPosition(inout vec3 p, const uvec4 ids, const vec4 weights) { - p = mulBoneVertice(p, ids.x * 4u) * weights.x - + mulBoneVertice(p, ids.y * 4u) * weights.y - + mulBoneVertice(p, ids.z * 4u) * weights.z - + mulBoneVertice(p, ids.w * 4u) * weights.w; + p = mulBoneVertex(p, ids.x * 4u) * weights.x + + mulBoneVertex(p, ids.y * 4u) * weights.y + + mulBoneVertex(p, ids.z * 4u) * weights.z + + mulBoneVertex(p, ids.w * 4u) * weights.w; } #endif /** @public-api */ vec4 getPosition() { - return mesh_position; -} + vec4 pos = mesh_position; + +#if defined(HAS_SKINNING_OR_MORPHING) + + if (objectUniforms.morphingEnabled == 1) { + pos += objectUniforms.morphWeights.x * mesh_custom0; + pos += objectUniforms.morphWeights.y * mesh_custom1; + pos += objectUniforms.morphWeights.z * mesh_custom2; + pos += objectUniforms.morphWeights.w * mesh_custom3; + } + + if (objectUniforms.skinningEnabled == 1) { + skinPosition(pos.xyz, mesh_bone_indices, mesh_bone_weights); + } -vec4 getSkinnedPosition() { - vec4 pos = getPosition(); -#if defined(HAS_SKINNING) - skinPosition(pos.xyz, mesh_bone_indices, mesh_bone_weights); #endif + return pos; } @@ -116,17 +125,17 @@ vec4 getCustom7() { return mesh_custom7; } vec4 computeWorldPosition() { #if defined(VERTEX_DOMAIN_OBJECT) mat4 transform = getWorldFromModelMatrix(); - vec3 position = getSkinnedPosition().xyz; + vec3 position = getPosition().xyz; return mulMat4x4Float3(transform, position); #elif defined(VERTEX_DOMAIN_WORLD) - return vec4(getSkinnedPosition().xyz, 1.0); + return vec4(getPosition().xyz, 1.0); #elif defined(VERTEX_DOMAIN_VIEW) mat4 transform = getWorldFromViewMatrix(); - vec3 position = getSkinnedPosition().xyz; + vec3 position = getPosition().xyz; return mulMat4x4Float3(transform, position); #else mat4 transform = getWorldFromViewMatrix() * getViewFromClipMatrix(); - vec3 position = getSkinnedPosition().xyz; + vec3 position = getPosition().xyz; return mulMat4x4Float3(transform, position); #endif } diff --git a/shaders/src/main.vs b/shaders/src/main.vs index e111599853..7e04eab3fb 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -11,9 +11,26 @@ void main() { // We encode the orthonormal basis as a quaternion to save space in the attributes toTangentFrame(mesh_tangents, material.worldNormal, vertex_worldTangent); - #if defined(HAS_SKINNING) - skinNormal(material.worldNormal, mesh_bone_indices, mesh_bone_weights); - skinNormal(vertex_worldTangent, mesh_bone_indices, mesh_bone_weights); + #if defined(HAS_SKINNING_OR_MORPHING) + + if (objectUniforms.morphingEnabled == 1) { + vec3 normal0, normal1, normal2, normal3; + toTangentFrame(mesh_custom4, normal0); + toTangentFrame(mesh_custom5, normal1); + toTangentFrame(mesh_custom6, normal2); + toTangentFrame(mesh_custom7, normal3); + material.worldNormal += objectUniforms.morphWeights.x * normal0; + material.worldNormal += objectUniforms.morphWeights.y * normal1; + material.worldNormal += objectUniforms.morphWeights.z * normal2; + material.worldNormal += objectUniforms.morphWeights.w * normal3; + material.worldNormal = normalize(material.worldNormal); + } + + if (objectUniforms.skinningEnabled == 1) { + skinNormal(material.worldNormal, mesh_bone_indices, mesh_bone_weights); + skinNormal(vertex_worldTangent, mesh_bone_indices, mesh_bone_weights); + } + #endif // We don't need to normalize here, even if there's a scale in the matrix @@ -31,7 +48,7 @@ void main() { // Without anisotropy or normal mapping we only need the normal vector toTangentFrame(mesh_tangents, material.worldNormal); material.worldNormal = objectUniforms.worldFromModelNormalMatrix * material.worldNormal; - #if defined(HAS_SKINNING) + #if defined(HAS_SKINNING_OR_MORPHING) skinNormal(material.worldNormal, mesh_bone_indices, mesh_bone_weights); #endif #endif // MATERIAL_HAS_ANISOTROPY || MATERIAL_HAS_NORMAL @@ -77,7 +94,7 @@ void main() { #if defined(VERTEX_DOMAIN_DEVICE) // The other vertex domains are handled in initMaterialVertex()->computeWorldPosition() - gl_Position = getSkinnedPosition(); + gl_Position = getPosition(); #else gl_Position = getClipFromWorldMatrix() * getWorldPosition(material); #endif diff --git a/tools/matc/src/matc/CommandlineConfig.cpp b/tools/matc/src/matc/CommandlineConfig.cpp index 350ff16f4e..8e4edc9622 100644 --- a/tools/matc/src/matc/CommandlineConfig.cpp +++ b/tools/matc/src/matc/CommandlineConfig.cpp @@ -102,7 +102,7 @@ static uint8_t parseVariantFilter(const std::string& arg) { } else if (item == "shadowReceiver") { variantFilter |= filament::Variant::SHADOW_RECEIVER; } else if (item == "skinning") { - variantFilter |= filament::Variant::SKINNING; + variantFilter |= filament::Variant::SKINNING_OR_MORPHING; } } return variantFilter; diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index 493d9f6930..a55b1ce4e7 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -472,7 +472,7 @@ static bool processVariantFilter(MaterialBuilder& builder, const JsonishValue& v strToEnum["directionalLighting"] = filament::Variant::DIRECTIONAL_LIGHTING; strToEnum["dynamicLighting"] = filament::Variant::DYNAMIC_LIGHTING; strToEnum["shadowReceiver"] = filament::Variant::SHADOW_RECEIVER; - strToEnum["skinning"] = filament::Variant::SKINNING; + strToEnum["skinning"] = filament::Variant::SKINNING_OR_MORPHING; return strToEnum; }(); uint8_t variantFilter = 0; diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 8699b1f527..d598c17c05 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -120,6 +120,7 @@ export class RenderableManager$Builder { public skinning(boneCount: number): RenderableManager$Builder; public skinningBones(transforms: RenderableManager$Bone[]): RenderableManager$Builder; public skinningMatrices(transforms: mat4[]): RenderableManager$Builder; + public morphing(enable: boolean): RenderableManager$Builder; public blendOrder(index: number, order: number): RenderableManager$Builder; public build(engine: Engine, entity: Entity): void; } @@ -166,6 +167,8 @@ export class RenderableManager { offset: number): void public setBonesFromMatrices(instance: RenderableManager$Instance, transforms: mat4[], offset: number): void + public setMorphWeights(instance: RenderableManager$Instance, a: number, b: number, c: number, + d: number); public getAxisAlignedBoundingBox(instance: RenderableManager$Instance): Box; public getPrimitiveCount(instance: RenderableManager$Instance): number; public setMaterialInstanceAt(instance: RenderableManager$Instance, @@ -579,21 +582,29 @@ export enum RenderTarget$AttachmentPoint { } export enum VertexAttribute { - POSITION, - TANGENTS, - COLOR, - UV0, - UV1, - BONE_INDICES, - BONE_WEIGHTS, - CUSTOM0, - CUSTOM1, - CUSTOM2, - CUSTOM3, - CUSTOM4, - CUSTOM5, - CUSTOM6, - CUSTOM7, + POSITION = 0, + TANGENTS = 1, + COLOR = 2, + UV0 = 3, + UV1 = 4, + BONE_INDICES = 5, + BONE_WEIGHTS = 6, + CUSTOM0 = 7, + CUSTOM1 = 8, + CUSTOM2 = 9, + CUSTOM3 = 10, + CUSTOM4 = 11, + CUSTOM5 = 12, + CUSTOM6 = 13, + CUSTOM7 = 14, + MORPH_POSITION_0 = CUSTOM0, + MORPH_POSITION_1 = CUSTOM1, + MORPH_POSITION_2 = CUSTOM2, + MORPH_POSITION_3 = CUSTOM3, + MORPH_TANGENTS_0 = CUSTOM4, + MORPH_TANGENTS_1 = CUSTOM5, + MORPH_TANGENTS_2 = CUSTOM6, + MORPH_TANGENTS_3 = CUSTOM7, } export enum VertexBuffer$AttributeType { diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 7739865a29..3e54fc3b4a 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -649,6 +649,9 @@ class_("RenderableManager$Builder") return &builder->skinning(matrices.size(), matrices.data()); }) + .BUILDER_FUNCTION("morphing", RenderableBuilder, (RenderableBuilder* builder, bool enable), { + return &builder->morphing(enable); }) + .BUILDER_FUNCTION("blendOrder", RenderableBuilder, (RenderableBuilder* builder, size_t index, uint16_t order), { return &builder->blendOrder(index, order); }) @@ -691,7 +694,7 @@ class_("RenderableManager") }), allow_raw_pointers()) .function("setBonesFromMatrices", EMBIND_LAMBDA(void, (RenderableManager* self, - RenderableManager::Instance instance, emscripten::val transforms, size_t offset), { + RenderableManager::Instance instance, emscripten::val transforms, size_t offset), { auto nbones = transforms["length"].as(); std::vector bones(nbones); for (size_t i = 0; i < nbones; i++) { @@ -700,6 +703,12 @@ class_("RenderableManager") self->setBones(instance, bones.data(), bones.size(), offset); }), allow_raw_pointers()) + // NOTE: this cannot take a float4 due to a binding issue. + .function("setMorphWeights", EMBIND_LAMBDA(void, (RenderableManager* self, + RenderableManager::Instance instance, float x, float y, float z, float w), { + self->setMorphWeights(instance, {x, y, z, w}); + }), allow_raw_pointers()) + .function("getAxisAlignedBoundingBox", &RenderableManager::getAxisAlignedBoundingBox) .function("getPrimitiveCount", &RenderableManager::getPrimitiveCount) .function("setMaterialInstanceAt", &RenderableManager::setMaterialInstanceAt, diff --git a/web/filament-js/jsenums.cpp b/web/filament-js/jsenums.cpp index d1db63433c..2b1d9dc81e 100644 --- a/web/filament-js/jsenums.cpp +++ b/web/filament-js/jsenums.cpp @@ -57,7 +57,15 @@ enum_("VertexAttribute") .value("CUSTOM4", CUSTOM4) .value("CUSTOM5", CUSTOM5) .value("CUSTOM6", CUSTOM6) - .value("CUSTOM7", CUSTOM7); + .value("CUSTOM7", CUSTOM7) + .value("MORPH_POSITION_0", MORPH_POSITION_0) + .value("MORPH_POSITION_1", MORPH_POSITION_1) + .value("MORPH_POSITION_2", MORPH_POSITION_2) + .value("MORPH_POSITION_3", MORPH_POSITION_3) + .value("MORPH_TANGENTS_0", MORPH_TANGENTS_0) + .value("MORPH_TANGENTS_1", MORPH_TANGENTS_1) + .value("MORPH_TANGENTS_2", MORPH_TANGENTS_2) + .value("MORPH_TANGENTS_3", MORPH_TANGENTS_3); enum_("VertexBuffer$AttributeType") .value("BYTE", VertexBuffer::AttributeType::BYTE)