From 21fd32266a646256d08ec2260b063d61327b5d14 Mon Sep 17 00:00:00 2001 From: Doris Wu Date: Wed, 21 Jan 2026 17:11:35 +0800 Subject: [PATCH] Add support for custom morphing (#9550) BUGS=[443321184] --- NEW_RELEASE_NOTES.md | 1 + .../filament-android/src/main/cpp/Engine.cpp | 16 + .../src/main/cpp/MorphTargetBuffer.cpp | 42 +++ .../com/google/android/filament/Engine.java | 20 ++ .../android/filament/MorphTargetBuffer.java | 66 ++++ filament/include/filament/MorphTargetBuffer.h | 92 ++++- filament/include/filament/RenderableManager.h | 11 + filament/src/MorphTargetBuffer.cpp | 13 +- filament/src/RenderPass.cpp | 2 +- filament/src/components/RenderableManager.cpp | 28 +- filament/src/components/RenderableManager.h | 12 +- filament/src/details/MorphTargetBuffer.cpp | 72 +++- filament/src/details/MorphTargetBuffer.h | 13 + filament/src/details/Scene.cpp | 2 +- filament/src/details/View.cpp | 18 +- .../include/private/filament/UibStructs.h | 10 +- samples/CMakeLists.txt | 2 + samples/hellomorphing.cpp | 2 + samples/hellouvmorphing.cpp | 330 ++++++++++++++++++ samples/materials/uvmorph.mat | 38 ++ shaders/src/surface_getters.vs | 66 +++- shaders/src/surface_main.vs | 4 +- shaders/src/surface_types.glsl | 8 +- 23 files changed, 807 insertions(+), 61 deletions(-) create mode 100644 samples/hellouvmorphing.cpp create mode 100644 samples/materials/uvmorph.mat diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index f408ba3462..227236689f 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -6,3 +6,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut +- engine: Support custom attributes morphing, and allow for omitting position and/or normal data. [⚠️ **Recompile Materials**] \ No newline at end of file diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index e1c33f8c93..62a753d775 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -207,6 +208,14 @@ Java_com_google_android_filament_Engine_nDestroySkinningBuffer(JNIEnv*, jclass, return engine->destroy(skinningBuffer); } +extern "C" JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_Engine_nDestroyMorphTargetBuffer(JNIEnv*, jclass, + jlong nativeEngine, jlong nativeMorphTargetBuffer) { + Engine* engine = (Engine*) nativeEngine; + MorphTargetBuffer* mtb = (MorphTargetBuffer*) nativeMorphTargetBuffer; + return engine->destroy(mtb); +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv*, jclass, jlong nativeEngine, jlong nativeIndirectLight) { @@ -328,6 +337,13 @@ Java_com_google_android_filament_Engine_nIsValidSkinningBuffer(JNIEnv*, jclass, return (jboolean)engine->isValid((SkinningBuffer*)nativeSkinningBuffer); } +extern "C" JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_Engine_nIsValidMorphTargetBuffer(JNIEnv*, jclass, + jlong nativeEngine, jlong nativeMorphTargetBuffer) { + Engine* engine = (Engine*) nativeEngine; + return (jboolean) engine->isValid((MorphTargetBuffer*) nativeMorphTargetBuffer); +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_Engine_nIsValidIndirectLight(JNIEnv*, jclass, jlong nativeEngine, jlong nativeIndirectLight) { diff --git a/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp b/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp index 893a9216a5..cd6d31c2c3 100644 --- a/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp +++ b/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp @@ -58,6 +58,27 @@ Java_com_google_android_filament_MorphTargetBuffer_nBuilderCount(JNIEnv*, jclass builder->count((size_t) count); } +extern "C" JNIEXPORT void JNICALL + Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithPositions(JNIEnv*, jclass, + jlong nativeBuilder, jboolean enabled) { + MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder; + builder->withPositions(enabled); +} + +extern "C" JNIEXPORT void JNICALL + Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithTangents(JNIEnv*, jclass, + jlong nativeBuilder, jboolean enabled) { + MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder; + builder->withTangents(enabled); +} + +extern "C" JNIEXPORT void JNICALL + Java_com_google_android_filament_MorphTargetBuffer_nBuilderEnableCustomMorphing(JNIEnv*, + jclass, jlong nativeBuilder, jboolean enabled) { + MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder; + builder->enableCustomMorphing(enabled); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass, @@ -112,3 +133,24 @@ Java_com_google_android_filament_MorphTargetBuffer_nGetCount(JNIEnv*, jclass, MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; return (jint)morphTargetBuffer->getCount(); } + +extern "C" JNIEXPORT jboolean JNICALL + Java_com_google_android_filament_MorphTargetBuffer_nHasPositions(JNIEnv*, jclass, + jlong nativeObject) { + MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject; + return (jboolean) morphTargetBuffer->hasPositions(); +} + +extern "C" JNIEXPORT jboolean JNICALL + Java_com_google_android_filament_MorphTargetBuffer_nHasTangents(JNIEnv*, jclass, + jlong nativeObject) { + MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject; + return (jboolean) morphTargetBuffer->hasTangents(); +} + +extern "C" JNIEXPORT jboolean JNICALL + Java_com_google_android_filament_MorphTargetBuffer_nIsCustomMorphingEnabled(JNIEnv*, jclass, + jlong nativeObject) { + MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject; + return (jboolean) morphTargetBuffer->isCustomMorphingEnabled(); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 299ceed272..b22a39af5c 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -939,6 +939,15 @@ public class Engine { return nIsValidSkinningBuffer(getNativeObject(), object.getNativeObject()); } + /** + * Returns whether the object is valid. + * @param object Object to check for validity + * @return returns true if the specified object is valid. + */ + public boolean isValidMorphTargetBuffer(@NonNull MorphTargetBuffer object) { + return nIsValidMorphTargetBuffer(getNativeObject(), object.getNativeObject()); + } + /** * Returns whether the object is valid. * @param object Object to check for validity @@ -1192,6 +1201,15 @@ public class Engine { skinningBuffer.clearNativeObject(); } + /** + * Destroys a {@link MorphTargetBuffer} and frees all its associated resources. + * @param morphTargetBuffer the {@link MorphTargetBuffer} to destroy + */ + public void destroyMorphTargetBuffer(@NonNull MorphTargetBuffer morphTargetBuffer) { + assertDestroy(nDestroyMorphTargetBuffer(getNativeObject(), morphTargetBuffer.getNativeObject())); + morphTargetBuffer.clearNativeObject(); + } + /** * Destroys a {@link IndirectLight} and frees all its associated resources. * @param ibl the {@link IndirectLight} to destroy @@ -1483,6 +1501,7 @@ public class Engine { private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer); private static native boolean nDestroyVertexBuffer(long nativeEngine, long nativeVertexBuffer); private static native boolean nDestroySkinningBuffer(long nativeEngine, long nativeSkinningBuffer); + private static native boolean nDestroyMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer); private static native boolean nDestroyIndirectLight(long nativeEngine, long nativeIndirectLight); private static native boolean nDestroyMaterial(long nativeEngine, long nativeMaterial); private static native boolean nDestroyMaterialInstance(long nativeEngine, long nativeMaterialInstance); @@ -1499,6 +1518,7 @@ public class Engine { private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer); private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer); private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer); + private static native boolean nIsValidMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer); private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight); private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial); private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance); diff --git a/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java index 619870d372..ad0f83f2e5 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java @@ -64,6 +64,45 @@ public class MorphTargetBuffer { return this; } + /** + * Use this method to enable or disable the built-in position morphing buffer. + * Default is true. + * + * @param enabled true to enable, false to disable + * @return this Builder object for chaining calls + */ + @NonNull + public Builder withPositions(boolean enabled) { + nBuilderWithPositions(mNativeBuilder, enabled); + return this; + } + + /** + * Use this method to enable or disable the built-in tangent morphing buffer. + * Default is true. + * + * @param enabled true to enable, false to disable + * @return this Builder object for chaining calls + */ + @NonNull + public Builder withTangents(boolean enabled) { + nBuilderWithTangents(mNativeBuilder, enabled); + return this; + } + + /** + * Use this method to enable or disable custom morphing. + * Default is false. + * + * @param enabled true to enable, false to disable + * @return this Builder object for chaining calls + */ + @NonNull + public Builder enableCustomMorphing(boolean enabled) { + nBuilderEnableCustomMorphing(mNativeBuilder, enabled); + return this; + } + /** * Creates and returns the MorphTargetBuffer object. * @@ -156,6 +195,27 @@ public class MorphTargetBuffer { return nGetCount(mNativeObject); } + /** + * @return true if this MorphTargetBuffer has a position buffer. + */ + public boolean hasPositions() { + return nHasPositions(mNativeObject); + } + + /** + * @return true if this MorphTargetBuffer has a tangent buffer. + */ + public boolean hasTangents() { + return nHasTangents(mNativeObject); + } + + /** + * @return true if custom morphing is enabled. + */ + public boolean isCustomMorphingEnabled() { + return nIsCustomMorphingEnabled(mNativeObject); + } + public long getNativeObject() { if (mNativeObject == 0) { throw new IllegalStateException("Calling method on destroyed MorphTargetBuffer"); @@ -171,10 +231,16 @@ public class MorphTargetBuffer { private static native void nDestroyBuilder(long nativeBuilder); private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount); private static native void nBuilderCount(long nativeBuilder, int count); + private static native void nBuilderWithPositions(long nativeBuilder, boolean enabled); + private static native void nBuilderWithTangents(long nativeBuilder, boolean enabled); + private static native void nBuilderEnableCustomMorphing(long nativeBuilder, boolean enabled); private static native long nBuilderBuild(long nativeBuilder, long nativeEngine); private static native int nSetPositionsAt(long nativeObject, long nativeEngine, int targetIndex, float[] positions, int count); private static native int nSetTangentsAt(long nativeObject, long nativeEngine, int targetIndex, short[] tangents, int count); private static native int nGetVertexCount(long nativeObject); private static native int nGetCount(long nativeObject); + private static native boolean nHasPositions(long nativeObject); + private static native boolean nHasTangents(long nativeObject); + private static native boolean nIsCustomMorphingEnabled(long nativeObject); } diff --git a/filament/include/filament/MorphTargetBuffer.h b/filament/include/filament/MorphTargetBuffer.h index 594f1a6ca4..ce331d3e1d 100644 --- a/filament/include/filament/MorphTargetBuffer.h +++ b/filament/include/filament/MorphTargetBuffer.h @@ -31,10 +31,29 @@ namespace filament { /** - * MorphTargetBuffer is used to hold morphing data (positions and tangents). + * A container for vertex morphing data that supports both automatic and manual morphing. * - * Both positions and tangents are required. + * MorphTargetBuffer operates in a hybrid model depending on the attribute being morphed: * + * 1. Automatic for Built-ins (positions/tangents): + * Enable via `withPositions(true)` or `withTangents(true)`. The MorphTargetBuffer will + * allocate internal storage and hold the data for these attributes, which you upload + * via `setPositionsAt()` or `setTangentsAt()`. The framework automatically applies + * the morphing logic in the vertex shader. + * + * 2. Manual for Custom Data (e.g., UVs, colors): + * The MorphTargetBuffer does NOT hold data for custom targets. The user is responsible + * for the full data pipeline: + * - Create and manage a separate `Texture` to hold the morph target data (offsets). + * - In the material, declare a `sampler2d_array` parameter. + * - Bind the `Texture` to the material instance. + * - In the vertex shader, manually call `morphData2`, `morphData3`, or `morphData4` + * with the custom sampler to apply the morphing. + * + * A MorphTargetBuffer object must be associated with a Renderable via + * `RenderableManager::Builder::morphing()` to enable the morphing pipeline for all cases. + * + * @see RenderableManager */ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { struct BuilderDetails; @@ -91,6 +110,46 @@ public: */ Builder& name(utils::StaticString const& name) noexcept; + /** + * Enables and allocates the built-in buffer for position morphing. + * + * If enabled, `setPositionsAt` can be called to set the position data for each target. + * The vertex position will be morphed automatically without any further actions. + * + * @param enable true to enable, false to disable. Default is true. + * @return A reference to this Builder for chaining calls. + */ + Builder& withPositions(bool enable = true) noexcept; + + /** + * Enables and allocates the built-in buffer for tangent/normal morphing. + * + * If enabled, `setTangentsAt` can be called to set the tangent data for each target. + * The vertex position will be morphed automatically without any further actions. + * + * @param enable true to enable, false to disable. Default is true. + * @return A reference to this Builder for chaining calls. + */ + Builder& withTangents(bool enable = true) noexcept; + + /** + * Enables the custom morphing pipeline. + * + * When enabled, the `morphData2`, `morphData3`, and `morphData4` helper functions are + * available in the vertex shader. You must provide a 2D array texture containing the morph + * deltas, bind it to a `sampler2DArray` uniform, and call the appropriate `morphData` + * function to apply the morphing to your custom attributes. + * + * Note: Unlike `withPositions` or `withTangents`, this does NOT allocate any internal + * storage. You are responsible for managing the morph data texture. + * + * Custom morphing can be used together with automatic position and/or tangent morphing. + * + * @param enable true to enable, false to disable. Default is false. + * @return A reference to this Builder for chaining calls. + */ + Builder& enableCustomMorphing(bool enable) noexcept; + /** * Creates the MorphTargetBuffer object and returns a pointer to it. * @@ -110,16 +169,14 @@ public: /** * Updates positions for the given morph target. * + * This method can only be called if the MorphTargetBuffer was built with `withPositions(true)`. * This is equivalent to the float4 method, but uses 1.0 for the 4th component. * - * Both positions and tangents must be provided. - * * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. * @param targetIndex the index of morph target to be updated. * @param positions pointer to at least "count" positions * @param count number of float3 vectors in positions - * @param offset offset into the target buffer, expressed as a number of float4 vectors - * @see setTangentsAt + * @param offset offset into the target buffer, expressed as a number of float3 vectors */ void setPositionsAt(Engine& engine, size_t targetIndex, math::float3 const* UTILS_NONNULL positions, size_t count, size_t offset = 0); @@ -127,14 +184,13 @@ public: /** * Updates positions for the given morph target. * - * Both positions and tangents must be provided. + * This method can only be called if the MorphTargetBuffer was built with `withPositions(true)`. * * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. * @param targetIndex the index of morph target to be updated. * @param positions pointer to at least "count" positions * @param count number of float4 vectors in positions * @param offset offset into the target buffer, expressed as a number of float4 vectors - * @see setTangentsAt */ void setPositionsAt(Engine& engine, size_t targetIndex, math::float4 const* UTILS_NONNULL positions, size_t count, size_t offset = 0); @@ -142,6 +198,7 @@ public: /** * Updates tangents for the given morph target. * + * This method can only be called if the MorphTargetBuffer was built with `withTangents(true)`. * These quaternions must be represented as signed shorts, where real numbers in the [-1,+1] * range multiplied by 32767. * @@ -150,7 +207,6 @@ public: * @param tangents pointer to at least "count" tangents * @param count number of short4 quaternions in tangents * @param offset offset into the target buffer, expressed as a number of short4 vectors - * @see setPositionsAt */ void setTangentsAt(Engine& engine, size_t targetIndex, math::short4 const* UTILS_NONNULL tangents, size_t count, size_t offset = 0); @@ -167,6 +223,24 @@ public: */ size_t getCount() const noexcept; + /** + * Returns true if this MorphTargetBuffer has a position buffer. + * @see Builder::withPositions + */ + bool hasPositions() const noexcept; + + /** + * Returns true if this MorphTargetBuffer has a tangent buffer. + * @see Builder::withTangents + */ + bool hasTangents() const noexcept; + + /** + * Returns true if custom morphing is enabled + * @see Builder::enableCustomMorphing + */ + bool isCustomMorphingEnabled() const noexcept; + protected: // prevent heap allocation ~MorphTargetBuffer() = default; diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 8013312d77..51e7d0da85 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -165,6 +165,17 @@ public: STATIC //!< skinning/morphing not allowed and Vertex/IndexBuffer immutables }; + /** + * Type of morphing for a Renderable. + * This usually acts as a bitmask of multiple types. + */ + enum class MorphType : uint8_t { + NONE = 0, + POSITION = 1, + TANGENT = 2, + CUSTOM = 4 + }; + /** * Creates a builder for renderable components. * diff --git a/filament/src/MorphTargetBuffer.cpp b/filament/src/MorphTargetBuffer.cpp index 4a4f0de490..39d04b51e0 100644 --- a/filament/src/MorphTargetBuffer.cpp +++ b/filament/src/MorphTargetBuffer.cpp @@ -43,5 +43,16 @@ size_t MorphTargetBuffer::getCount() const noexcept { return downcast(this)->getCount(); } -} // namespace filament +bool MorphTargetBuffer::hasPositions() const noexcept { + return downcast(this)->hasPositions(); +} +bool MorphTargetBuffer::hasTangents() const noexcept { + return downcast(this)->hasTangents(); +} + +bool MorphTargetBuffer::isCustomMorphingEnabled() const noexcept { + return downcast(this)->isCustomMorphingEnabled(); +} + +} // namespace filament diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 44608b5bbb..7b85ebe2dc 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -611,7 +611,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag // calculate the per-primitive face winding order inversion bool const inverseFrontFaces = viewInverseFrontFaces ^ soaVisibility[i].reversedWindingOrder; - bool const hasMorphing = soaSkinningData[i].morphing; + bool const hasMorphing = soaSkinningData[i].morphType != RenderableManager::Builder::MorphType::NONE; bool const hasSkinning = soaSkinningData[i].skinning; bool const hasSkinningOrMorphing = hasSkinning || hasMorphing; diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index a0c791d3d3..bcb637ff40 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -68,8 +68,33 @@ using namespace filament::math; using namespace utils; namespace filament { +namespace { + +RenderableManager::Builder::MorphType morphTargetBufferToBuildType( + const MorphTargetBuffer* const buffer, size_t const morphTargetCount) { + using MorphType = RenderableManager::Builder::MorphType; + if (!buffer || morphTargetCount == 0) { + return MorphType::NONE; + } + + auto type = static_cast(MorphType::NONE); + if (buffer->hasPositions()) { + type |= static_cast(MorphType::POSITION); + } + + if (buffer->hasTangents()) { + type |= static_cast(MorphType::TANGENT); + } + + if (buffer->isCustomMorphingEnabled()) { + type |= static_cast(MorphType::CUSTOM); + } + + return static_cast(type); +} using namespace backend; +} // anonymous namespace struct RenderableManager::BuilderDetails { using Entry = FRenderableManager::Entry; @@ -578,7 +603,8 @@ void FRenderableManager::create( setScreenSpaceContactShadows(ci, builder->mScreenSpaceContactShadows); setCulling(ci, builder->mCulling); setSkinning(ci, false); - setMorphing(ci, builder->mMorphTargetCount); + setMorphing(ci, morphTargetBufferToBuildType(builder->mMorphTargetBuffer, + builder->mMorphTargetCount)); setFogEnabled(ci, builder->mFogEnabled); // do this after calling setAxisAlignedBoundingBox static_cast(mManager[ci].visibility).geometryType = builder->mGeometryType; diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index d9a7efaa88..134205cf2e 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -63,6 +63,7 @@ class FRenderableManager : public RenderableManager { public: using Instance = Instance; using GeometryType = Builder::GeometryType; + using MorphType = Builder::MorphType; // TODO: consider renaming, this pertains to material variants, not strictly visibility. struct Visibility { @@ -80,7 +81,7 @@ public: struct Skinning { bool skinning : 1; - bool morphing : 1; + MorphType morphType : 3; }; static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits"); @@ -154,7 +155,7 @@ public: void setSkinningBuffer(Instance instance, FSkinningBuffer* skinningBuffer, size_t count, size_t offset); - inline void setMorphing(Instance instance, bool enable); + inline void setMorphing(Instance instance, MorphType type); void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset); void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex, size_t offset); @@ -412,15 +413,16 @@ void FRenderableManager::setSkinning(Instance const instance, bool const enable) } } -void FRenderableManager::setMorphing(Instance const instance, bool const enable) { +void FRenderableManager::setMorphing(Instance const instance, Builder::MorphType const type) { if (instance) { Visibility& visibility = mManager[instance].visibility; - FILAMENT_CHECK_PRECONDITION(visibility.geometryType != GeometryType::STATIC || !enable) + FILAMENT_CHECK_PRECONDITION( + visibility.geometryType != GeometryType::STATIC || type != MorphType::NONE) << "Morphing can't be used with STATIC geometry"; Skinning& skinning = mManager[instance].skinning; - skinning.morphing = enable; + skinning.morphType = type; } } diff --git a/filament/src/details/MorphTargetBuffer.cpp b/filament/src/details/MorphTargetBuffer.cpp index 1ec2b35042..8750cf9947 100644 --- a/filament/src/details/MorphTargetBuffer.cpp +++ b/filament/src/details/MorphTargetBuffer.cpp @@ -36,6 +36,9 @@ using namespace math; struct MorphTargetBuffer::BuilderDetails { size_t mVertexCount = 0; size_t mCount = 0; + bool mWithPositions = true; + bool mWithTangents = true; + bool mEnableCustomMorphing = false; }; using BuilderType = MorphTargetBuffer; @@ -56,6 +59,21 @@ MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::count(size_t const count return *this; } +MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::withPositions(bool enable) noexcept { + mImpl->mWithPositions = enable; + return *this; +} + +MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::withTangents(bool enable) noexcept { + mImpl->mWithTangents = enable; + return *this; +} + +MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::enableCustomMorphing(bool enable) noexcept { + mImpl->mEnableCustomMorphing = enable; + return *this; +} + MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::name(const char* name, size_t const len) noexcept { return BuilderNameMixin::name(name, len); } @@ -108,11 +126,17 @@ inline size_t getSize(size_t const vertexCount) noexcept { FMorphTargetBuffer::EmptyMorphTargetBuilder::EmptyMorphTargetBuilder() { mImpl->mVertexCount = 1; mImpl->mCount = 1; + mImpl->mWithPositions = true; + mImpl->mWithTangents = true; } FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder) - : mVertexCount(builder->mVertexCount), + : mEnableCustomMorphing(builder->mEnableCustomMorphing), + mVertexCount(builder->mVertexCount), mCount(builder->mCount) { + FILAMENT_CHECK_PRECONDITION( + builder->mEnableCustomMorphing || builder->mWithPositions || builder->mWithTangents) + << "Requires enable at least one of the morphing type."; if (UTILS_UNLIKELY(engine.getSupportedFeatureLevel() <= FeatureLevel::FEATURE_LEVEL_0)) { // feature level 0 doesn't support morph target buffers @@ -121,22 +145,25 @@ FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder) FEngine::DriverApi& driver = engine.getDriverApi(); - // create buffer (here a texture) to store the morphing vertex data - mPbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, - TextureFormat::RGBA32F, 1, - getWidth(mVertexCount), - getHeight(mVertexCount), - mCount, - TextureUsage::DEFAULT, - utils::ImmutableCString{ builder.getName() }); + if (builder->mWithPositions) { + mPbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, + TextureFormat::RGBA32F, 1, + getWidth(mVertexCount), + getHeight(mVertexCount), + mCount, + TextureUsage::DEFAULT, + utils::ImmutableCString{ builder.getName() }); + } - mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, - TextureFormat::RGBA16I, 1, - getWidth(mVertexCount), - getHeight(mVertexCount), - mCount, - TextureUsage::DEFAULT, - utils::ImmutableCString{ builder.getName() }); + if (builder->mWithTangents) { + mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, + TextureFormat::RGBA16I, 1, + getWidth(mVertexCount), + getHeight(mVertexCount), + mCount, + TextureUsage::DEFAULT, + utils::ImmutableCString{ builder.getName() }); + } } void FMorphTargetBuffer::terminate(FEngine& engine) { @@ -151,6 +178,10 @@ void FMorphTargetBuffer::terminate(FEngine& engine) { void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex, float3 const* positions, size_t const count, size_t const offset) { + FILAMENT_CHECK_PRECONDITION(mPbHandle) + << "setPositionsAt() called on a MorphTargetBuffer without a position buffer. Use " + "withPositions(true) in the Builder."; + FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount) << "MorphTargetBuffer (size=" << (unsigned)mVertexCount << ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")"; @@ -174,6 +205,10 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetInde void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex, float4 const* positions, size_t const count, size_t const offset) { + FILAMENT_CHECK_PRECONDITION(mPbHandle) + << "setPositionsAt() called on a MorphTargetBuffer without a position buffer. Use " + "withPositions(true) in the Builder."; + FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount) << "MorphTargetBuffer (size=" << mVertexCount << ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")"; @@ -196,6 +231,10 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetInde void FMorphTargetBuffer::setTangentsAt(FEngine& engine, size_t const targetIndex, short4 const* tangents, size_t const count, size_t const offset) { + FILAMENT_CHECK_PRECONDITION(mTbHandle) + << "setTangentsAt() called on a MorphTargetBuffer without a tangent buffer. Use " + "withTangents(true) in the Builder."; + FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount) << "MorphTargetBuffer (size=" << mVertexCount << ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")"; @@ -278,4 +317,3 @@ void FMorphTargetBuffer::updateDataAt(DriverApi& driver, } } // namespace filament - diff --git a/filament/src/details/MorphTargetBuffer.h b/filament/src/details/MorphTargetBuffer.h index e386627770..ebfec81cd8 100644 --- a/filament/src/details/MorphTargetBuffer.h +++ b/filament/src/details/MorphTargetBuffer.h @@ -56,6 +56,18 @@ public: void setTangentsAt(FEngine& engine, size_t targetIndex, math::short4 const* tangents, size_t count, size_t offset); + bool hasPositions() const noexcept { + return bool(mPbHandle); + } + + bool hasTangents() const noexcept { + return bool(mTbHandle); + } + + bool isCustomMorphingEnabled() const noexcept { + return mEnableCustomMorphing; + } + inline size_t getVertexCount() const noexcept { return mVertexCount; } inline size_t getCount() const noexcept { return mCount; } @@ -72,6 +84,7 @@ private: backend::PixelDataFormat format, backend::PixelDataType type, const char* out, size_t elementSize, size_t targetIndex, size_t count, size_t offset); + bool mEnableCustomMorphing; backend::TextureHandle mPbHandle; backend::TextureHandle mTbHandle; uint32_t mVertexCount; diff --git a/filament/src/details/Scene.cpp b/filament/src/details/Scene.cpp index 8023e1b865..56e2da509e 100644 --- a/filament/src/details/Scene.cpp +++ b/filament/src/details/Scene.cpp @@ -395,7 +395,7 @@ void FScene::prepareVisibleRenderables(Range visibleRenderables) noexc uboData.flagsChannels = PerRenderableData::packFlagsChannels( skinning.skinning, - skinning.morphing, + static_cast(skinning.morphType), visibility.screenSpaceContactShadows, sceneData.elementAt(i).buffer != nullptr, sceneData.elementAt(i)); diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index d2905aa5ba..294e979c08 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -33,6 +33,7 @@ #include "details/Engine.h" #include "details/IndirectLight.h" #include "details/InstanceBuffer.h" +#include "details/MorphTargetBuffer.h" #include "details/RenderTarget.h" #include "details/Renderer.h" #include "details/Scene.h" @@ -832,13 +833,18 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren +PerRenderableBindingPoints::MORPHING_UNIFORMS, morphing.handle, 0, sizeof(PerRenderableMorphingUib)); - descriptorSet.setSampler(layout, - +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, - morphing.morphTargetBuffer->getPositionsHandle(), {}); + const auto* mtb = morphing.morphTargetBuffer; + if (mtb->hasPositions()) { + descriptorSet.setSampler(layout, + +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, + mtb->getPositionsHandle(), {}); + } - descriptorSet.setSampler(layout, - +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS, - morphing.morphTargetBuffer->getTangentsHandle(), {}); + if (mtb->hasTangents()) { + descriptorSet.setSampler(layout, + +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS, + mtb->getTangentsHandle(), {}); + } } descriptorSet.commit(layout, driver); diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index 20ae328825..286b7d6490 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -235,12 +235,14 @@ struct PerRenderableData { math::float4 reserved[8]; static uint32_t packFlagsChannels( - bool skinning, bool morphing, bool contactShadows, bool hasInstanceBuffer, + bool skinning, uint8_t morphing, bool contactShadows, bool hasInstanceBuffer, uint8_t channels) noexcept { + const uint32_t morphingFlags = uint32_t(morphing) << 9; + return (skinning ? 0x100 : 0) | - (morphing ? 0x200 : 0) | - (contactShadows ? 0x400 : 0) | - (hasInstanceBuffer ? 0x800 : 0) | + morphingFlags | + (contactShadows ? 0x1000 : 0) | + (hasInstanceBuffer ? 0x2000 : 0) | channels; } }; diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index acf9c26894..9015104988 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -39,6 +39,7 @@ set(MATERIAL_SRCS materials/sandboxSubsurface.mat materials/sandboxUnlit.mat materials/texturedLit.mat + materials/uvmorph.mat ) # Tint does not support setting gl_PointSize, disable the relevant sample if using WebGPU @@ -271,6 +272,7 @@ if (NOT ANDROID) add_demo(heightfield) add_demo(helloasync) add_demo(hellomorphing) + add_demo(hellouvmorphing) add_demo(hellopbr) add_demo(hellotriangle) add_demo(helloskinning) diff --git a/samples/hellomorphing.cpp b/samples/hellomorphing.cpp index 33b6b1f8bb..bae7fbbcd1 100644 --- a/samples/hellomorphing.cpp +++ b/samples/hellomorphing.cpp @@ -169,6 +169,8 @@ int main(int argc, char** argv) { .build(*engine); app.mt1 = MorphTargetBuffer::Builder() + .withPositions(true) + .withTangents(true) .vertexCount(9 * 2) .count(3) .build(*engine); diff --git a/samples/hellouvmorphing.cpp b/samples/hellouvmorphing.cpp new file mode 100644 index 0000000000..b7a71c94a0 --- /dev/null +++ b/samples/hellouvmorphing.cpp @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2025 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 "common/arguments.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +#include "generated/resources/resources.h" + +using namespace filament; +using namespace filament::math; +using utils::Entity; +using utils::EntityManager; +using utils::Path; + +struct Vertex { + float3 position; + float2 uv; +}; + +struct App { + Config config; + VertexBuffer* vb = nullptr; + IndexBuffer* ib = nullptr; + Material* mat = nullptr; + MaterialInstance* mi = nullptr; + Texture* baseColorMap = nullptr; + Texture* uvMorphTexture = nullptr; + Camera* cam = nullptr; + Entity camera; + Skybox* skybox = nullptr; + Entity renderable; + MorphTargetBuffer* mtb = nullptr; + + std::vector vertices; + std::vector indices; + std::vector uvMorphData; + std::vector posMorphData; + bool posMorphing = false; +}; + +static constexpr int GRID_SIZE = 20; +static constexpr float GRID_SCALE = 2.0f; + +static void printUsage(char* name) { + std::string exec_name(utils::Path(name).getName()); + std::string usage("HELLOUVMORPHING renders a quad with uv morphing\n" + "Usage:\n" + " HELLOUVMORPHING [options]\n" + "Options:\n" + " --help, -h\n" + " Prints this message\n" + " --positions, -p\n" + " Enable position morphing\n\n" + "API_USAGE"); + const std::string from("HELLOUVMORPHING"); + for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) { + usage.replace(pos, from.length(), exec_name); + } + const std::string apiUsage("API_USAGE"); + for (size_t pos = usage.find(apiUsage); pos != std::string::npos; pos = usage.find(apiUsage, pos)) { + usage.replace(pos, apiUsage.length(), samples::getBackendAPIArgumentsUsage()); + } + std::cout << usage; +} + +static int handleCommandLineArguments(int argc, char* argv[], App* app) { + static constexpr const char* OPTSTR = "ha:p"; + static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, + { "api", required_argument, nullptr, 'a' }, { "positions", no_argument, nullptr, 'p' }, + { nullptr, 0, nullptr, 0 } }; + int opt; + int option_index = 0; + while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) { + std::string arg(optarg ? optarg : ""); + switch (opt) { + default: + case 'h': + printUsage(argv[0]); + exit(0); + case 'a': + app->config.backend = samples::parseArgumentsForBackend(arg); + break; + case 'p': + app->posMorphing = true; + break; + } + } + return optind; +} + +int main(int argc, char** argv) { + App app{}; + app.config.title = "hellouvmorphing"; + handleCommandLineArguments(argc, argv, &app); + + auto setup = [&app](Engine* engine, View* view, Scene* scene) { + app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine); + scene->setSkybox(app.skybox); + view->setPostProcessingEnabled(false); + + app.vertices.reserve(GRID_SIZE * GRID_SIZE); + app.indices.reserve((GRID_SIZE - 1) * (GRID_SIZE - 1) * 6); + + // Grid vertices + for (int y = 0; y < GRID_SIZE; ++y) { + for (int x = 0; x < GRID_SIZE; ++x) { + float u = (float) x / (GRID_SIZE - 1); + float v = (float) y / (GRID_SIZE - 1); + float px = (u * 2.0f - 1.0f) * GRID_SCALE; + float yScale = app.posMorphing ? 1.0f : GRID_SCALE; + float py = (v * 2.0f - 1.0f) * yScale; + app.vertices.push_back({ { px, py, 0.0f }, { u, v } }); + } + } + + // Grid triangles + for (int y = 0; y < GRID_SIZE - 1; ++y) { + for (int x = 0; x < GRID_SIZE - 1; ++x) { + uint16_t tl = y * GRID_SIZE + x; + uint16_t tr = tl + 1; + uint16_t bl = (y + 1) * GRID_SIZE + x; + uint16_t br = bl + 1; + + // CCW Triangles + app.indices.push_back(tl); + app.indices.push_back(tr); + app.indices.push_back(bl); + + app.indices.push_back(tr); + app.indices.push_back(br); + app.indices.push_back(bl); + } + } + + app.vb = VertexBuffer::Builder() + .vertexCount(app.vertices.size()) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT3, 0, sizeof(Vertex)) + .attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, + sizeof(float3), sizeof(Vertex)) + .build(*engine); + + app.vb->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor( + app.vertices.data(), app.vertices.size() * sizeof(Vertex), + [](void* buffer, size_t size, void* user) {}, nullptr)); + + app.ib = IndexBuffer::Builder() + .indexCount(app.indices.size()) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + + app.ib->setBuffer(*engine, + IndexBuffer::BufferDescriptor( + app.indices.data(), app.indices.size() * sizeof(uint16_t), + [](void* buffer, size_t size, void* user) {}, nullptr)); + + // Create a 4x1 "rainbow" texture with Mipmaps + static const uint32_t rainbow[] = {0xff0000ff, 0xff00ff00, 0xffff0000, 0xff00ffff}; + app.baseColorMap = Texture::Builder() + .width(4) + .height(1) + .levels(0xff) // Auto levels + .format(Texture::InternalFormat::RGBA8) + .sampler(Texture::Sampler::SAMPLER_2D) + .build(*engine); + + Texture::PixelBufferDescriptor pbd(rainbow, sizeof(rainbow), + Texture::Format::RGBA, Texture::Type::UBYTE); + app.baseColorMap->setImage(*engine, 0, std::move(pbd)); + + // Generate swirl deltas + const float2 center = { 0.5f, 0.5f }; + app.uvMorphData.reserve(app.vertices.size()); + + for (const auto& v: app.vertices) { + float2 uv = v.uv; + float2 toCenter = uv - center; + float dist = length(toCenter); + + float angle = (1.0f - dist) * 3.14159f * 4.0f; + float s = sin(angle); + float c = cos(angle); + + float2 rotated; + rotated.x = toCenter.x * c - toCenter.y * s; + rotated.y = toCenter.x * s + toCenter.y * c; + + float2 newUV = rotated + center; + app.uvMorphData.push_back(newUV - uv); + } + + size_t width = app.vertices.size(); + size_t height = 1; + + app.uvMorphTexture = Texture::Builder() + .width(width) + .height(height) + .depth(1) + .levels(1) + .format(Texture::InternalFormat::RG32F) + .sampler(Texture::Sampler::SAMPLER_2D_ARRAY) + .build(*engine); + + Texture::PixelBufferDescriptor uvDeltaDesc( + app.uvMorphData.data(), app.uvMorphData.size() * sizeof(float2), + Texture::Format::RG, Texture::Type::FLOAT, + [](void* buffer, size_t size, void* user) {}, nullptr); + + app.uvMorphTexture->setImage(*engine, 0, 0, 0, 0, width, height, 1, std::move(uvDeltaDesc)); + + app.mat = Material::Builder() + .package(RESOURCES_UVMORPH_DATA, RESOURCES_UVMORPH_SIZE) + .build(*engine); + app.mi = app.mat->createInstance(); + + TextureSampler colorSampler(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR, + TextureSampler::MagFilter::LINEAR, TextureSampler::WrapMode::REPEAT); + app.mi->setParameter("baseColor", app.baseColorMap, colorSampler); + + TextureSampler dataSampler(TextureSampler::MinFilter::NEAREST, TextureSampler::MagFilter::NEAREST); + app.mi->setParameter("uv0_morph", app.uvMorphTexture, dataSampler); + + app.mtb = MorphTargetBuffer::Builder() + .vertexCount(app.vertices.size()) + .count(1) + .withPositions(app.posMorphing) + .enableCustomMorphing(true) + .build(*engine); + + if (app.posMorphing) { + // Generate position morph deltas + app.posMorphData.reserve(app.vertices.size()); + for (const auto& v: app.vertices) { + // Morph to square (GRID_SCALE) + float targetPy = (v.uv.y * 2.0f - 1.0f) * GRID_SCALE; + float currentPy = v.position.y; + float deltaY = targetPy - currentPy; + // W must be 0 for deltas + app.posMorphData.push_back({ 0, deltaY, 0, 0.0f }); + } + app.mtb->setPositionsAt(*engine, 0, app.posMorphData.data(), app.posMorphData.size()); + } + + app.renderable = EntityManager::get().create(); + RenderableManager::Builder(1) + .boundingBox({ { -GRID_SCALE, -GRID_SCALE, -1 }, { GRID_SCALE, GRID_SCALE, 1 } }) + .material(0, app.mi) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib) + .culling(false) + .morphing(app.mtb) + .build(*engine, app.renderable); + scene->addEntity(app.renderable); + + app.camera = utils::EntityManager::get().create(); + app.cam = engine->createCamera(app.camera); + view->setCamera(app.cam); + }; + + auto cleanup = [&app](Engine* engine, View*, Scene*) { + engine->destroy(app.skybox); + engine->destroy(app.renderable); + engine->destroy(app.mi); + engine->destroy(app.mat); + engine->destroy(app.baseColorMap); + engine->destroy(app.uvMorphTexture); + engine->destroy(app.vb); + engine->destroy(app.ib); + engine->destroy(app.mtb); + engine->destroyCameraComponent(app.camera); + EntityManager::get().destroy(app.camera); + }; + + FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { + constexpr float ZOOM = 2.5f; + const uint32_t w = view->getViewport().width; + const uint32_t h = view->getViewport().height; + const float aspect = (float)w / h; + app.cam->setProjection(Camera::Projection::ORTHO, -aspect * ZOOM, aspect * ZOOM, -ZOOM, ZOOM, 0, 1); + + auto& rm = engine->getRenderableManager(); + float weight = (sin(now * 2.0) + 1.0f) * 0.5f; + rm.setMorphWeights(rm.getInstance(app.renderable), &weight, 1); + }); + + FilamentApp::get().run(app.config, setup, cleanup); + + return 0; +} diff --git a/samples/materials/uvmorph.mat b/samples/materials/uvmorph.mat new file mode 100644 index 0000000000..44fa04dd47 --- /dev/null +++ b/samples/materials/uvmorph.mat @@ -0,0 +1,38 @@ +material { + name : UvMorph, + requires : [uv0], + shadingModel : unlit, + culling: none, + apiLevel: 2, + parameters : [ + { + type : sampler2d, + name : baseColor + }, + { + type : sampler2dArray, + name : uv0_morph, + shader: vertex + } + ] +} + + + +vertex { + void materialVertex(inout MaterialVertexInputs material) { + #if defined(VARIANT_HAS_SKINNING_OR_MORPHING) + if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_CUSTOM_BIT) != 0) { + // Call morphData2 with the sampler that we declared and bound manually. + morphData2(material.uv0, materialParams_uv0_morph); + } + #endif + } +} + +fragment { + void material(inout MaterialInputs material) { + prepareMaterial(material); + material.baseColor = texture(materialParams_baseColor, getUV0()); + } +} diff --git a/shaders/src/surface_getters.vs b/shaders/src/surface_getters.vs index b717b61232..1a71a06fa6 100644 --- a/shaders/src/surface_getters.vs +++ b/shaders/src/surface_getters.vs @@ -154,6 +154,50 @@ void skinNormalTangent(inout vec3 n, inout vec3 t, const uvec4 ids, const vec4 w #define MAX_MORPH_TARGET_BUFFER_WIDTH 2048 +#if CLIENT_MATERIAL_API_LEVEL >= UNSTABLE_MATERIAL_API_LEVEL +void morphData2(inout vec2 v, highp sampler2DArray data) { + int index = getVertexIndex() + pushConstants.morphingBufferOffset; + ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + int c = object_uniforms_morphTargetCount; + for (int i = 0; i < c; ++i) { + float w = morphingUniforms.weights[i][0]; + if (w != 0.0) { + texcoord.z = i; + v += w * texelFetch(data, texcoord, 0).xy; + } + } +} + +void morphData3(inout vec3 v, highp sampler2DArray data) { + int index = getVertexIndex() + pushConstants.morphingBufferOffset; + ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + int c = object_uniforms_morphTargetCount; + for (int i = 0; i < c; ++i) { + float w = morphingUniforms.weights[i][0]; + if (w != 0.0) { + texcoord.z = i; + v += w * texelFetch(data, texcoord, 0).xyz; + } + } +} + +void morphData4(inout vec4 v, highp sampler2DArray data) { + int index = getVertexIndex() + pushConstants.morphingBufferOffset; + ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + int c = object_uniforms_morphTargetCount; + for (int i = 0; i < c; ++i) { + float w = morphingUniforms.weights[i][0]; + if (w != 0.0) { + texcoord.z = i; + v += w * texelFetch(data, texcoord, 0); + } + } +} + +void morphPosition(inout vec4 p) { + morphData4(p, sampler1_positions); +} +#else void morphPosition(inout vec4 p) { int index = getVertexIndex() + pushConstants.morphingBufferOffset; ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); @@ -166,6 +210,7 @@ void morphPosition(inout vec4 p) { } } } +#endif void morphNormal(inout vec3 n) { vec3 baseNormal = n; @@ -190,17 +235,16 @@ vec4 getPosition() { vec4 pos = mesh_position; #if defined(VARIANT_HAS_SKINNING_OR_MORPHING) - - if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) { -#if defined(LEGACY_MORPHING) - pos += morphingUniforms.weights[0] * mesh_custom0; - pos += morphingUniforms.weights[1] * mesh_custom1; - pos += morphingUniforms.weights[2] * mesh_custom2; - pos += morphingUniforms.weights[3] * mesh_custom3; -#else - morphPosition(pos); -#endif - } + if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_POSITION_BIT) != 0) { + #if defined(LEGACY_MORPHING) + pos += morphingUniforms.weights[0] * mesh_custom0; + pos += morphingUniforms.weights[1] * mesh_custom1; + pos += morphingUniforms.weights[2] * mesh_custom2; + pos += morphingUniforms.weights[3] * mesh_custom3; + #else + morphPosition(pos); + #endif + } if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0) { skinPosition(pos.xyz, mesh_bone_indices, mesh_bone_weights); diff --git a/shaders/src/surface_main.vs b/shaders/src/surface_main.vs index 17d32a9fd8..a0ccb0ef4e 100644 --- a/shaders/src/surface_main.vs +++ b/shaders/src/surface_main.vs @@ -61,7 +61,7 @@ void main() { toTangentFrame(mesh_tangents, material.worldNormal, vertex_worldTangent.xyz); #if defined(VARIANT_HAS_SKINNING_OR_MORPHING) - if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) { + if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_TANGENT_BIT) != 0) { #if defined(LEGACY_MORPHING) vec3 normal0, normal1, normal2, normal3; toTangentFrame(mesh_custom4, normal0); @@ -96,7 +96,7 @@ void main() { toTangentFrame(mesh_tangents, material.worldNormal); #if defined(VARIANT_HAS_SKINNING_OR_MORPHING) - if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) { + if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_TANGENT_BIT) != 0) { #if defined(LEGACY_MORPHING) vec3 normal0, normal1, normal2, normal3; toTangentFrame(mesh_custom4, normal0); diff --git a/shaders/src/surface_types.glsl b/shaders/src/surface_types.glsl index 3208c1ba95..9a73db84ed 100644 --- a/shaders/src/surface_types.glsl +++ b/shaders/src/surface_types.glsl @@ -36,6 +36,8 @@ struct PerRenderableData { // Bits for flagsChannels #define FILAMENT_OBJECT_SKINNING_ENABLED_BIT 0x100 -#define FILAMENT_OBJECT_MORPHING_ENABLED_BIT 0x200 -#define FILAMENT_OBJECT_CONTACT_SHADOWS_BIT 0x400 -#define FILAMENT_OBJECT_INSTANCE_BUFFER_BIT 0x800 +#define FILAMENT_OBJECT_MORPHING_POSITION_BIT 0x200 +#define FILAMENT_OBJECT_MORPHING_TANGENT_BIT 0x400 +#define FILAMENT_OBJECT_MORPHING_CUSTOM_BIT 0x800 +#define FILAMENT_OBJECT_CONTACT_SHADOWS_BIT 0x1000 +#define FILAMENT_OBJECT_INSTANCE_BUFFER_BIT 0x2000