From 5d30435620222a9ebfbc91cca47ddc919536fb5b Mon Sep 17 00:00:00 2001 From: fvbj Date: Thu, 21 Sep 2023 22:04:12 +0200 Subject: [PATCH] Extend skinning for >4 bones per vertex (#6772) * Add skinning and morphing samples to check functionality * Implement skinning for more than four bones pair vertex The API allows defining an unlimited number of bone indices and weights of primitives. Data is defined in building process of the renderable manager. Backward compatibility with the original solution. Skinning of vertices is calculated on GPU, data is transferred to the vertex shader in the texture. --- NEW_RELEASE_NOTES.md | 1 + filament/include/filament/RenderableManager.h | 53 ++ filament/include/filament/VertexBuffer.h | 13 + filament/src/RenderPass.cpp | 14 +- filament/src/RenderPass.h | 3 +- filament/src/components/RenderableManager.cpp | 182 ++++- filament/src/components/RenderableManager.h | 7 +- filament/src/details/Scene.h | 2 +- filament/src/details/SkinningBuffer.cpp | 98 +++ filament/src/details/SkinningBuffer.h | 17 + filament/src/details/VertexBuffer.cpp | 69 +- filament/src/details/VertexBuffer.h | 8 + .../include/private/filament/EngineEnums.h | 3 +- .../include/private/filament/SibStructs.h | 6 + libs/filamat/src/shaders/ShaderGenerator.cpp | 4 +- libs/filamat/src/shaders/SibGenerator.cpp | 19 +- libs/filamat/src/shaders/SibGenerator.h | 1 + samples/CMakeLists.txt | 5 + samples/hellomorphing.cpp | 194 ++++++ samples/helloskinning.cpp | 176 +++++ samples/helloskinningbuffer.cpp | 239 +++++++ samples/helloskinningbuffer_morebones.cpp | 248 +++++++ samples/skinningtest.cpp | 649 ++++++++++++++++++ shaders/src/getters.vs | 87 ++- shaders/src/main.vs | 3 +- 25 files changed, 2063 insertions(+), 38 deletions(-) create mode 100644 samples/hellomorphing.cpp create mode 100644 samples/helloskinning.cpp create mode 100644 samples/helloskinningbuffer.cpp create mode 100644 samples/helloskinningbuffer_morebones.cpp create mode 100644 samples/skinningtest.cpp diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index d091003d8c..a326858850 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -8,6 +8,7 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut +- engine: add support for skinning with more than four bones per vertex. - engine: remove `BloomOptions::anamorphism` which wasn't working well in most cases [**API CHANGE**] - engine: new API to return a Material's supported variants, C++ only (b/297456590) - build: fix emscripten-1.3.46 build diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 868375bac0..30705b8d62 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -26,6 +26,7 @@ #include #include +#include #include @@ -349,6 +350,58 @@ public: Builder& skinning(size_t boneCount, Bone const* bones) noexcept; //!< \overload Builder& skinning(size_t boneCount) noexcept; //!< \overload + /** + * Define bone indices and weights "pairs" for vertex skinning as a float2. + * The unsigned int(pair.x) defines index of the bone and pair.y is the bone weight. + * The pairs substitute \c BONE_INDICES and the \c BONE_WEIGHTS defined in the VertexBuffer. + * Both ways of indices and weights definition must not be combined in one primitive. + * Number of pairs per vertex bonesPerVertex is not limited to 4 bones. + * Vertex buffer used for \c primitiveIndex must be set for advance skinning. + * All bone weights of one vertex should sum to one. Otherwise they will be normalized. + * Data must be rectangular and number of bone pairs must be same for all vertices of this + * primitive. + * The data is arranged sequentially, all bone pairs for the first vertex, then for the + * second vertex, and so on. + * + * @param primitiveIndex zero-based index of the primitive, must be less than the primitive + * count passed to Builder constructor + * @param indicesAndWeights pairs of bone index and bone weight for all vertices + * sequentially + * @param count number of all pairs, must be a multiple of vertexCount of the primitive + * count = vertexCount * bonesPerVertex + * @param bonesPerVertex number of bone pairs, same for all vertices of the primitive + * + * @return Builder reference for chaining calls. + * + * @see VertexBuffer:Builder:advancedSkinning + */ + Builder& boneIndicesAndWeights(size_t primitiveIndex, + math::float2 const* indicesAndWeights, size_t count, size_t bonesPerVertex) noexcept; + + /** + * Define bone indices and weights "pairs" for vertex skinning as a float2. + * The unsigned int(pair.x) defines index of the bone and pair.y is the bone weight. + * The pairs substitute \c BONE_INDICES and the \c BONE_WEIGHTS defined in the VertexBuffer. + * Both ways of indices and weights definition must not be combined in one primitive. + * Number of pairs is not limited to 4 bones per vertex. + * Vertex buffer used for \c primitiveIndex must be set for advance skinning. + * All bone weights of one vertex should sum to one. Otherwise they will be normalized. + * Data doesn't have to be rectangular and number of pairs per vertices of primitive can be + * variable. + * The vector of the vertices contains the vectors of the pairs + * + * @param primitiveIndex zero-based index of the primitive, must be less than the primitive + * count passed to Builder constructor + * @param indicesAndWeightsVectors pairs of bone index and bone weight for all vertices of + * the primitive sequentially + * + * @return Builder reference for chaining calls. + * + * @see VertexBuffer:Builder:advancedSkinning + */ + Builder& boneIndicesAndWeights(size_t primitiveIndex, + utils::FixedCapacityVector< + utils::FixedCapacityVector> indicesAndWeightsVector) noexcept; /** * Controls if the renderable has vertex morphing targets, zero by default. This is * required to enable GPU morphing. diff --git a/filament/include/filament/VertexBuffer.h b/filament/include/filament/VertexBuffer.h index 9557a541bc..dd844c375a 100644 --- a/filament/include/filament/VertexBuffer.h +++ b/filament/include/filament/VertexBuffer.h @@ -142,6 +142,19 @@ public: */ Builder& normalized(VertexAttribute attribute, bool normalize = true) noexcept; + /** + * Sets advanced skinning mode. Bone data, indices and weights will be + * set in RenderableManager:Builder:boneIndicesAndWeights methods. + * Works with or without buffer objects. + * + * @param enabled If true, enables advanced skinning mode. False by default. + * + * @return A reference to this Builder for chaining calls. + * + * @see RenderableManager:Builder:boneIndicesAndWeights + */ + Builder& advancedSkinning(bool enabled) noexcept; + /** * Creates the VertexBuffer object and returns a pointer to it. * diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 56425c8fbb..a9fa1dc4ca 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -241,7 +241,8 @@ void RenderPass::instanceify(FEngine& engine) noexcept { lhs.primitive.skinningHandle == rhs.primitive.skinningHandle && lhs.primitive.skinningOffset == rhs.primitive.skinningOffset && lhs.primitive.morphWeightBuffer == rhs.primitive.morphWeightBuffer && - lhs.primitive.morphTargetBuffer == rhs.primitive.morphTargetBuffer; + lhs.primitive.morphTargetBuffer == rhs.primitive.morphTargetBuffer && + lhs.primitive.skinningTexture == rhs.primitive.skinningTexture ; }); uint32_t const instanceCount = e - curr; @@ -585,6 +586,8 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, cmdColor.primitive.skinningHandle = skinning.handle; cmdColor.primitive.skinningOffset = skinning.offset; + cmdColor.primitive.skinningTexture = skinning.handleSampler; + cmdColor.primitive.morphWeightBuffer = morphing.handle; cmdColor.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle(); @@ -689,6 +692,8 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags, cmdDepth.primitive.skinningHandle = skinning.handle; cmdDepth.primitive.skinningOffset = skinning.offset; + cmdDepth.primitive.skinningTexture = skinning.handleSampler; + cmdDepth.primitive.morphWeightBuffer = morphing.handle; cmdDepth.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle(); @@ -873,7 +878,12 @@ void RenderPass::Executor::execute(backend::DriverApi& driver, // note: even if only skinning is enabled, binding morphTargetBuffer is needed. driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING, info.morphTargetBuffer); - } + + if (UTILS_UNLIKELY(info.skinningTexture)) { + driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING, + info.skinningTexture); + } + } if (UTILS_UNLIKELY(info.morphWeightBuffer)) { // Instead of using a UBO per primitive, we could also have a single UBO for all diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 92c7b06c0c..4b671a648f 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -232,6 +232,7 @@ public: backend::RasterState rasterState; // 4 bytes backend::Handle primitiveHandle; // 4 bytes backend::Handle skinningHandle; // 4 bytes + backend::Handle skinningTexture; // 4 bytes backend::Handle morphWeightBuffer; // 4 bytes backend::Handle morphTargetBuffer; // 4 bytes backend::Handle instanceBufferHandle; // 4 bytes @@ -239,7 +240,7 @@ public: uint32_t skinningOffset = 0; // 4 bytes uint16_t instanceCount; // 2 bytes [MSb: user] Variant materialVariant; // 1 byte - uint8_t reserved[4] = {}; // 4 bytes +// uint8_t reserved[0] = {}; // 0 bytes static const uint16_t USER_INSTANCE_MASK = 0x8000u; static const uint16_t INSTANCE_COUNT_MASK = 0x7fffu; diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 513c126785..7314f705d5 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -24,11 +24,8 @@ #include "details/VertexBuffer.h" #include "details/IndexBuffer.h" #include "details/InstanceBuffer.h" -#include "details/Texture.h" #include "details/Material.h" -#include - #include "filament/RenderableManager.h" @@ -37,7 +34,7 @@ #include #include #include - +#include using namespace filament::math; using namespace utils; @@ -68,13 +65,23 @@ struct RenderableManager::BuilderDetails { FSkinningBuffer* mSkinningBuffer = nullptr; FInstanceBuffer* mInstanceBuffer = nullptr; uint32_t mSkinningBufferOffset = 0; + utils::FixedCapacityVector mBoneIndicesAndWeights; + size_t mBoneIndicesAndWeightsCount = 0; + + // bone indices and weights defined for primitive index + std::unordered_map>> mBonePairs; explicit BuilderDetails(size_t count) - : mEntries(count), mCulling(true), mCastShadows(false), mReceiveShadows(true), - mScreenSpaceContactShadows(false), mSkinningBufferMode(false), mFogEnabled(true) { + : mEntries(count), mCulling(true), mCastShadows(false), + mReceiveShadows(true), mScreenSpaceContactShadows(false), + mSkinningBufferMode(false), mFogEnabled(true), mBonePairs() { } // this is only needed for the explicit instantiation below BuilderDetails() = default; + + void processBoneIndicesAndWights(Engine& engine, utils::Entity entity); + }; using BuilderType = RenderableManager; @@ -205,6 +212,26 @@ RenderableManager::Builder& RenderableManager::Builder::enableSkinningBuffers(bo return *this; } +RenderableManager::Builder& RenderableManager::Builder::boneIndicesAndWeights(size_t primitiveIndex, + math::float2 const* indicesAndWeights, size_t count, size_t bonesPerVertex) noexcept { + size_t vertexCount = count / bonesPerVertex; + utils::FixedCapacityVector> bonePairs(vertexCount); + for ( size_t iVertex = 0; iVertex < vertexCount; iVertex++) { + utils::FixedCapacityVector vertexData(bonesPerVertex); + std::copy_n(indicesAndWeights + iVertex * bonesPerVertex, + bonesPerVertex, vertexData.data()); + bonePairs[iVertex] = std::move(vertexData); + } + return boneIndicesAndWeights(primitiveIndex, bonePairs); +} + +RenderableManager::Builder& RenderableManager::Builder::boneIndicesAndWeights(size_t primitiveIndex, + utils::FixedCapacityVector< + utils::FixedCapacityVector> indicesAndWeightsVector) noexcept { + mImpl->mBonePairs[primitiveIndex] = std::move(indicesAndWeightsVector); + return *this; +} + RenderableManager::Builder& RenderableManager::Builder::fog(bool enabled) noexcept { mImpl->mFogEnabled = enabled; return *this; @@ -243,25 +270,134 @@ RenderableManager::Builder& RenderableManager::Builder::globalBlendOrderEnabled( return *this; } +UTILS_NOINLINE +void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engine, Entity entity) { + size_t maxPairsCount = 0; //size of texture, number of bone pairs + size_t maxPairsCountPerVertex = 0; //maximum of number of bone per vertex + + for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair){ + auto primitiveIndex = iBonePair->first; + auto entries = mEntries; + ASSERT_PRECONDITION(primitiveIndex < entries.size() && primitiveIndex >= 0, + "[primitive @ %u] primitiveindex is out of size (%u)", primitiveIndex, entries.size()); + auto entry = mEntries[primitiveIndex]; + auto bonePairsForPrimitive = iBonePair->second; + auto vertexCount = entry.vertices->getVertexCount(); + ASSERT_PRECONDITION(bonePairsForPrimitive.size() == vertexCount, + "[primitive @ %u] bone indices and weights pairs count (%u) must be equal to vertex count (%u)", + primitiveIndex, bonePairsForPrimitive.size(), vertexCount); + auto const& declaredAttributes = downcast(entry.vertices)->getDeclaredAttributes(); + ASSERT_PRECONDITION(declaredAttributes[VertexAttribute::BONE_INDICES] + || declaredAttributes[VertexAttribute::BONE_WEIGHTS], + "[entity=%u, primitive @ %u] for advanced skinning set VertexBuffer::Builder::advancedSkinning()", + entity.getId(), primitiveIndex); + for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) { + size_t bonesPerVertex = bonePairsForPrimitive[iVertex].size(); + maxPairsCount += bonesPerVertex; + maxPairsCountPerVertex = std::max(bonesPerVertex, maxPairsCountPerVertex); + } + } + + size_t pairsCount = 0; // counting of number of pairs stored in texture + if (maxPairsCount) { // at least one primitive has bone indices and weights + // final texture data, indices and weights + mBoneIndicesAndWeights = utils::FixedCapacityVector(maxPairsCount); + // temporary indices and weights for one vertex + std::unique_ptr tempPairs = std::make_unique + (maxPairsCountPerVertex); + for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair) { + auto primitiveIndex = iBonePair->first; + auto bonePairsForPrimitive = iBonePair->second; + if (!bonePairsForPrimitive.size()) { + continue; + } + size_t vertexCount = mEntries[primitiveIndex].vertices->getVertexCount(); + std::unique_ptr skinJoints = std::make_unique + (4 * vertexCount); // temporary indices for one vertex + std::unique_ptr skinWeights = std::make_unique + (4 * vertexCount); // temporary weights for one vertex + for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) { + size_t tempPairCount = 0; + float boneWeightsSum = 0; + for (size_t k = 0; k < bonePairsForPrimitive[iVertex].size(); k++) { + auto boneWeight = bonePairsForPrimitive[iVertex][k][1]; + auto boneIndex = bonePairsForPrimitive[iVertex][k][0]; + ASSERT_PRECONDITION(boneWeight >= 0, + "[entity=%u, primitive @ %u] bone weight (%f) of vertex=%u is negative ", + entity.getId(), primitiveIndex, boneWeight, iVertex); + if (boneWeight) { + ASSERT_PRECONDITION(boneIndex >= 0, + "[entity=%u, primitive @ %u] bone index (%i) of vertex=%u is negative ", + entity.getId(), primitiveIndex, (int) boneIndex, iVertex); + ASSERT_PRECONDITION(boneIndex < mSkinningBoneCount, + "[entity=%u, primitive @ %u] bone index (%i) of vertex=%u is bigger then bone count (%u) ", + entity.getId(), primitiveIndex, (int) boneIndex, iVertex, mSkinningBoneCount); + boneWeightsSum += boneWeight; + tempPairs[tempPairCount][0] = boneIndex; + tempPairs[tempPairCount][1] = boneWeight; + tempPairCount++; + } + } + + ASSERT_PRECONDITION(boneWeightsSum > 0, + "[entity=%u, primitive @ %u] sum of bone weights of vertex=%u is %f, it should be positive.", + entity.getId(), primitiveIndex, iVertex, boneWeightsSum); + if (abs(boneWeightsSum - 1.f) > std::numeric_limits::epsilon()) { + utils::slog.w << "Warning of skinning: [entity=%" << entity.getId() + << ", primitive @ %" << primitiveIndex + << "] sum of bone weights of vertex=" << iVertex << " is " << boneWeightsSum + << ", it should be one. Weights will be normalized." << utils::io::endl; + } + // prepare data for vertex attributes + auto offset = iVertex * 4; + // set attributes, indices and weights, for <= 4 pairs + for (size_t j = 0, c = std::min((int) tempPairCount, 4); j < c; j++) { + skinJoints[j + offset] = tempPairs[j][0]; + skinWeights[j + offset] = tempPairs[j][1] / boneWeightsSum; + } + // prepare data for texture + if (tempPairCount > 4) { // set attributes, indices and weights, for > 4 pairs + skinWeights[3 + offset] = -(float) (pairsCount + 1); // negative offset to texture 0..-1, 1..-2 + skinJoints[3 + offset] = (uint16_t) tempPairCount; // number pairs per vertex in texture + for (size_t j = 3; j < tempPairCount; j++) { + mBoneIndicesAndWeights[pairsCount][0] = tempPairs[j][0]; + mBoneIndicesAndWeights[pairsCount][1] = tempPairs[j][1] / boneWeightsSum; + pairsCount++; + } + } + } // for all vertices per primitive + downcast(mEntries[primitiveIndex].vertices) + ->updateBoneIndicesAndWeights(downcast(engine), + std::move(skinJoints), + std::move(skinWeights)); + } // for all primitives + } + mBoneIndicesAndWeightsCount = pairsCount; // only part of mBoneIndicesAndWeights is used for real data +} + RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& engine, Entity entity) { bool isEmpty = true; ASSERT_PRECONDITION(mImpl->mSkinningBoneCount <= CONFIG_MAX_BONE_COUNT, "bone count > %u", CONFIG_MAX_BONE_COUNT); - ASSERT_PRECONDITION( - mImpl->mInstanceCount <= engine.getMaxAutomaticInstances() || !mImpl->mInstanceBuffer, - "instance count is %zu, but instance count is limited to " - "Engine::getMaxAutomaticInstances() (%zu) instances when supplying transforms via an " - "InstanceBuffer.", - mImpl->mInstanceCount, engine.getMaxAutomaticInstances()); + ASSERT_PRECONDITION(mImpl->mInstanceCount <= CONFIG_MAX_INSTANCES || !mImpl->mInstanceBuffer, + "instance count is %zu, but instance count is limited to CONFIG_MAX_INSTANCES (%zu) " + "instances when supplying transforms via an InstanceBuffer.", + mImpl->mInstanceCount, + CONFIG_MAX_INSTANCES); if (mImpl->mInstanceBuffer) { size_t bufferInstanceCount = mImpl->mInstanceBuffer->mInstanceCount; ASSERT_PRECONDITION(mImpl->mInstanceCount <= bufferInstanceCount, "instance count (%zu) must be less than or equal to the InstanceBuffer's instance " - "count (%zu).", + "count " + "(%zu).", mImpl->mInstanceCount, bufferInstanceCount); } + if (UTILS_LIKELY(mImpl->mSkinningBoneCount || mImpl->mSkinningBufferMode)) { + mImpl->processBoneIndicesAndWights(engine, entity); + } + for (size_t i = 0, c = mImpl->mEntries.size(); i < c; i++) { auto& entry = mImpl->mEntries[i]; @@ -287,12 +423,12 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng // reject invalid geometry parameters ASSERT_PRECONDITION(entry.offset + entry.count <= entry.indices->getIndexCount(), "[entity=%u, primitive @ %u] offset (%u) + count (%u) > indexCount (%u)", - i, entity.getId(), + entity.getId(), i, entry.offset, entry.count, entry.indices->getIndexCount()); ASSERT_PRECONDITION(entry.minIndex <= entry.maxIndex, "[entity=%u, primitive @ %u] minIndex (%u) > maxIndex (%u)", - i, entity.getId(), + entity.getId(), i, entry.minIndex, entry.maxIndex); // this can't be an error because (1) those values are not immutable, so the caller @@ -460,6 +596,18 @@ void FRenderableManager::create( } } + if (UTILS_UNLIKELY(boneCount > 0) && (builder->mBoneIndicesAndWeightsCount > 0)){ + // create and set texture for bone indices and weights + Bones& bones = manager[ci].bones; + FSkinningBuffer::HandleIndicesAndWeights handle = downcast(builder->mSkinningBuffer)-> + createIndicesAndWeightsHandle(downcast(engine), builder->mBoneIndicesAndWeightsCount); + bones.handleSamplerGroup = handle.sampler; + bones.handleTexture = handle.texture; + downcast(builder->mSkinningBuffer)-> + setIndicesAndWeightsData(downcast(engine), handle.texture, + builder->mBoneIndicesAndWeights, builder->mBoneIndicesAndWeightsCount); + } + // Create and initialize all needed MorphTargets. // It's required to avoid branches in hot loops. MorphTargets* morphTargets = new MorphTargets[entryCount]; @@ -549,6 +697,10 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept { if (bones.handle && !bones.skinningBufferMode) { driver.destroyBufferObject(bones.handle); } + if (bones.handleSamplerGroup){ + driver.destroySamplerGroup(bones.handleSamplerGroup); + driver.destroyTexture(bones.handleTexture); + } // destroy the weights structures if any MorphWeights const& morphWeights = manager[ci].morphWeights; diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 648ccdaeb5..d79d2ca3ee 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -151,6 +151,7 @@ public: struct SkinningBindingInfo { backend::Handle handle; uint32_t offset; + backend::Handle handleSampler; }; inline SkinningBindingInfo getSkinningBufferInfo(Instance instance) const noexcept; @@ -208,8 +209,10 @@ private: uint16_t count = 0; uint16_t offset = 0; bool skinningBufferMode = false; + backend::Handle handleSamplerGroup; + backend::Handle handleTexture; }; - static_assert(sizeof(Bones) == 12); + static_assert(sizeof(Bones) == 20); struct MorphWeights { backend::Handle handle; @@ -410,7 +413,7 @@ Box const& FRenderableManager::getAABB(Instance instance) const noexcept { FRenderableManager::SkinningBindingInfo FRenderableManager::getSkinningBufferInfo(Instance instance) const noexcept { Bones const& bones = mManager[instance].bones; - return { bones.handle, bones.offset }; + return { bones.handle, bones.offset, bones.handleSamplerGroup }; } inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexcept { diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index a17d30386f..d7adc8d3c8 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -92,7 +92,7 @@ public: RENDERABLE_INSTANCE, // 4 | instance of the Renderable component WORLD_TRANSFORM, // 16 | instance of the Transform component VISIBILITY_STATE, // 2 | visibility data of the component - SKINNING_BUFFER, // 8 | bones uniform buffer handle, offset + SKINNING_BUFFER, // 8 | bones uniform buffer handle, offset, indices and weights MORPHING_BUFFER, // 16 | weights uniform buffer handle, count, morph targets INSTANCES, // 16 | instancing info for this Renderable WORLD_AABB_CENTER, // 12 | world-space bounding box center of the renderable diff --git a/filament/src/details/SkinningBuffer.cpp b/filament/src/details/SkinningBuffer.cpp index 9fd859a264..323a66ee97 100644 --- a/filament/src/details/SkinningBuffer.cpp +++ b/filament/src/details/SkinningBuffer.cpp @@ -18,6 +18,8 @@ #include "components/RenderableManager.h" +#include "private/filament/SibStructs.h" + #include "details/Engine.h" #include "FilamentAPI-impl.h" @@ -162,5 +164,101 @@ void FSkinningBuffer::setBones(FEngine& engine, Handle offset * sizeof(PerRenderableBoneUib::BoneData)); } +// This value is limited by ES3.0, ES3.0 only guarantees 2048. +// When you change this value, you must change MAX_SKINNING_BUFFER_WIDTH at getters.vs +constexpr size_t MAX_SKINNING_BUFFER_WIDTH = 2048; + +static inline size_t getSkinningBufferWidth(size_t pairCount) noexcept { + return std::min(pairCount, MAX_SKINNING_BUFFER_WIDTH); +} + +static inline size_t getSkinningBufferHeight(size_t pairCount) noexcept { + return (pairCount + MAX_SKINNING_BUFFER_WIDTH - 1) / MAX_SKINNING_BUFFER_WIDTH; +} + +inline size_t getSkinningBufferSize(size_t pairCount) noexcept { + const size_t stride = getSkinningBufferWidth(pairCount); + const size_t height = getSkinningBufferHeight(pairCount); + return Texture::PixelBufferDescriptor::computeDataSize( + Texture::PixelBufferDescriptor::PixelDataFormat::RG, + Texture::PixelBufferDescriptor::PixelDataType::FLOAT, + stride, height, 1); +} + +UTILS_NOINLINE +void updateDataAt(backend::DriverApi& driver, + Handle handle, PixelDataFormat format, PixelDataType type, + const utils::FixedCapacityVector& pairs, + size_t count) { + + size_t elementSize = sizeof(float2); + size_t size = getSkinningBufferSize(count); + auto* out = (float2*) malloc(size); + std::memcpy(out, pairs.begin(), size); + + size_t const textureWidth = getSkinningBufferWidth( count); + size_t const lineCount = count / textureWidth; + size_t const lastLineCount = count % textureWidth; + + // 'out' buffer is going to be used up to 2 times, so for simplicity we use a shared_buffer + // to manage its lifetime. One side effect of this is that the callbacks below will allocate + // a small object on the heap. (inspired by MorphTargetBuffered) + std::shared_ptr allocation((void*)out, ::free); + + if (lineCount) { + // update the full-width lines if any + driver.update3DImage(handle, 0, 0, 0, 0, + textureWidth, lineCount, 1, + PixelBufferDescriptor::make( + out, textureWidth * lineCount * elementSize, + format, type, [allocation](void const*, size_t) {} + )); + out += lineCount * textureWidth; + } + + if (lastLineCount) { + // update the last partial line if any + driver.update3DImage(handle, 0, 0, lineCount, 0, + lastLineCount, 1, 1, + PixelBufferDescriptor::make( + out, lastLineCount * elementSize, + format, type, [allocation](void const*, size_t) {} + )); + } +} + +FSkinningBuffer::HandleIndicesAndWeights FSkinningBuffer::createIndicesAndWeightsHandle(FEngine& engine, size_t count) { + backend::Handle samplerHandle; + backend::Handle textureHandle; + + FEngine::DriverApi& driver = engine.getDriverApi(); + // create a texture for skinning pairs data (bone index and weight) + textureHandle = driver.createTexture(SamplerType::SAMPLER_2D, 1, + TextureFormat::RG32F, 1, + getSkinningBufferWidth(count), + getSkinningBufferHeight(count), 1, + TextureUsage::DEFAULT); + samplerHandle = driver.createSamplerGroup(PerRenderPrimitiveSkinningSib::SAMPLER_COUNT); + SamplerGroup samplerGroup(PerRenderPrimitiveSkinningSib::SAMPLER_COUNT); + samplerGroup.setSampler(PerRenderPrimitiveSkinningSib::BONE_INDICES_AND_WEIGHTS, + {textureHandle, {}}); + driver.updateSamplerGroup(samplerHandle, + samplerGroup.toBufferDescriptor(driver)); + return { + .sampler = samplerHandle, + .texture = textureHandle + }; +} + +void FSkinningBuffer::setIndicesAndWeightsData(FEngine& engine, + backend::Handle textureHandle, + const utils::FixedCapacityVector& pairs, size_t count) { + + FEngine::DriverApi& driver = engine.getDriverApi(); + updateDataAt(driver, textureHandle, + Texture::Format::RG, Texture::Type::FLOAT, + pairs, count); +} + } // namespace filament diff --git a/filament/src/details/SkinningBuffer.h b/filament/src/details/SkinningBuffer.h index 8fd1cddd41..23e6a48ce8 100644 --- a/filament/src/details/SkinningBuffer.h +++ b/filament/src/details/SkinningBuffer.h @@ -23,9 +23,12 @@ #include "private/filament/EngineEnums.h" #include "private/filament/UibStructs.h" +#include + #include #include +#include // for gtest class FilamentTest_Bones_Test; @@ -52,6 +55,9 @@ public: return (count + CONFIG_MAX_BONE_COUNT - 1) & ~(CONFIG_MAX_BONE_COUNT - 1); } + backend::Handle setIndicesAndWeights(FEngine& engine, + math::float2 const* pairs, size_t count); + private: friend class ::FilamentTest_Bones_Test; friend class SkinningBuffer; @@ -69,6 +75,17 @@ private: return mHandle; } + struct HandleIndicesAndWeights{ + backend::Handle sampler; + backend::Handle texture; + }; + HandleIndicesAndWeights createIndicesAndWeightsHandle(FEngine& engine, + size_t count); + void setIndicesAndWeightsData(FEngine& engine, + backend::Handle textureHandle, + const utils::FixedCapacityVector& pairs, + size_t count); + backend::Handle mHandle; uint32_t mBoneCount; }; diff --git a/filament/src/details/VertexBuffer.cpp b/filament/src/details/VertexBuffer.cpp index 859f1dbc1c..f557e25d23 100644 --- a/filament/src/details/VertexBuffer.cpp +++ b/filament/src/details/VertexBuffer.cpp @@ -22,6 +22,7 @@ #include "FilamentAPI-impl.h" #include +#include #include @@ -36,6 +37,7 @@ struct VertexBuffer::BuilderDetails { uint32_t mVertexCount = 0; uint8_t mBufferCount = 0; bool mBufferObjectsEnabled = false; + bool mAdvancedSkinningEnabled = false; // TODO: use bits to save memory }; using BuilderType = VertexBuffer; @@ -112,6 +114,11 @@ VertexBuffer::Builder& VertexBuffer::Builder::normalized(VertexAttribute attribu return *this; } +VertexBuffer::Builder& VertexBuffer::Builder::advancedSkinning(bool enabled) noexcept { + mImpl->mAdvancedSkinningEnabled = enabled; + return *this; +} + VertexBuffer* VertexBuffer::Builder::build(Engine& engine) { ASSERT_PRECONDITION(mImpl->mVertexCount > 0, "vertexCount cannot be 0"); ASSERT_PRECONDITION(mImpl->mBufferCount > 0, "bufferCount cannot be 0"); @@ -139,7 +146,8 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) { FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& builder) : mVertexCount(builder->mVertexCount), mBufferCount(builder->mBufferCount), - mBufferObjectsEnabled(builder->mBufferObjectsEnabled) { + mBufferObjectsEnabled(builder->mBufferObjectsEnabled), + mAdvancedSkinningEnabled(builder->mAdvancedSkinningEnabled){ std::copy(std::begin(builder->mAttributes), std::end(builder->mAttributes), mAttributes.begin()); mDeclaredAttributes = builder->mDeclaredAttributes; @@ -153,6 +161,29 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& build static_assert(sizeof(Attribute) == sizeof(AttributeData), "Attribute and Builder::Attribute must match"); + if (mAdvancedSkinningEnabled) { + ASSERT_PRECONDITION(!mDeclaredAttributes[VertexAttribute::BONE_INDICES], + "Vertex buffer attribute BONE_INDICES is already defined, no advanced skinning is allowed"); + ASSERT_PRECONDITION(!mDeclaredAttributes[VertexAttribute::BONE_WEIGHTS], + "Vertex buffer attribute BONE_WEIGHTS is already defined, no advanced skinning is allowed"); + ASSERT_PRECONDITION(mBufferCount < (MAX_VERTEX_BUFFER_COUNT - 2), + "Vertex buffer uses to many buffers (%u)", mBufferCount); + mDeclaredAttributes.set(VertexAttribute::BONE_INDICES); + mAttributes[VertexAttribute::BONE_INDICES].offset = 0; + mAttributes[VertexAttribute::BONE_INDICES].stride = 8; + mAttributes[VertexAttribute::BONE_INDICES].buffer = mBufferCount; + mAttributes[VertexAttribute::BONE_INDICES].type = VertexBuffer::AttributeType::USHORT4; + mAttributes[VertexAttribute::BONE_INDICES].flags = Attribute::FLAG_INTEGER_TARGET; + mBufferCount++; + mDeclaredAttributes.set(VertexAttribute::BONE_WEIGHTS); + mAttributes[VertexAttribute::BONE_WEIGHTS].offset = 0; + mAttributes[VertexAttribute::BONE_WEIGHTS].stride = 16; + mAttributes[VertexAttribute::BONE_WEIGHTS].buffer = mBufferCount; + mAttributes[VertexAttribute::BONE_WEIGHTS].type = VertexBuffer::AttributeType::FLOAT4; + mAttributes[VertexAttribute::BONE_WEIGHTS].flags = 0; + mBufferCount++; + } + size_t bufferSizes[MAX_VERTEX_BUFFER_COUNT] = {}; auto const& declaredAttributes = mDeclaredAttributes; @@ -196,7 +227,18 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& build mBufferObjects[i] = bo; } } + } else { + // add buffer objects for indices and weights + if (mAdvancedSkinningEnabled) { + for (size_t i = mBufferCount - 2; i < mBufferCount; ++i) { + BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], + backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); + driver.setVertexBufferObject(mHandle, i, bo); + mBufferObjects[i] = bo; + } + } } + } void FVertexBuffer::terminate(FEngine& engine) { @@ -233,9 +275,34 @@ void FVertexBuffer::setBufferObjectAt(FEngine& engine, uint8_t bufferIndex, if (bufferIndex < mBufferCount) { auto hwBufferObject = bufferObject->getHwHandle(); engine.getDriverApi().setVertexBufferObject(mHandle, bufferIndex, hwBufferObject); + // store handle to recreate VertexBuffer in the case extra bone indices and weights definition + // used only in buffer object mode + mBufferObjects[bufferIndex] = hwBufferObject; } else { ASSERT_PRECONDITION(bufferIndex < mBufferCount, "bufferIndex must be < bufferCount"); } } +void FVertexBuffer::updateBoneIndicesAndWeights(FEngine& engine, + std::unique_ptr skinJoints, + std::unique_ptr skinWeights) { + + ASSERT_PRECONDITION(mAdvancedSkinningEnabled, "No advanced skinning enabled"); + auto jointsData = skinJoints.release(); + auto bdJoints = BufferDescriptor( + jointsData, mVertexCount * 8, + [](void *buffer, size_t size, void *user) { + delete[] static_cast(buffer); }); + engine.getDriverApi().updateBufferObject(mBufferObjects[mBufferCount - 2], + std::move(bdJoints), 0); + + auto weightsData = skinWeights.release(); + auto bdWeights = BufferDescriptor( + weightsData, mVertexCount * 16, + [](void *buffer, size_t size, void *user) { + delete[] static_cast(buffer); }); + engine.getDriverApi().updateBufferObject(mBufferObjects[mBufferCount - 1], + std::move(bdWeights), 0); + +} } // namespace filament diff --git a/filament/src/details/VertexBuffer.h b/filament/src/details/VertexBuffer.h index d8455bfe4d..d9e7883518 100644 --- a/filament/src/details/VertexBuffer.h +++ b/filament/src/details/VertexBuffer.h @@ -27,7 +27,10 @@ #include #include +#include + #include +#include #include namespace filament { @@ -41,6 +44,7 @@ public: using BufferObjectHandle = backend::BufferObjectHandle; FVertexBuffer(FEngine& engine, const Builder& builder); + FVertexBuffer(FEngine& engine, FVertexBuffer* buffer); // frees driver resources, object becomes invalid void terminate(FEngine& engine); @@ -60,6 +64,9 @@ public: void setBufferObjectAt(FEngine& engine, uint8_t bufferIndex, FBufferObject const * bufferObject); + void updateBoneIndicesAndWeights(FEngine& engine, std::unique_ptr skinJoints, + std::unique_ptr skinWeights); + private: friend class VertexBuffer; @@ -74,6 +81,7 @@ private: uint32_t mVertexCount = 0; uint8_t mBufferCount = 0; bool mBufferObjectsEnabled = false; + bool mAdvancedSkinningEnabled = false; }; FILAMENT_DOWNCAST(VertexBuffer) diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index 7475d9f61a..f72c2ac66a 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -52,6 +52,7 @@ enum class SamplerBindingPoints : uint8_t { PER_VIEW = 0, // samplers updated per view PER_RENDERABLE_MORPHING = 1, // morphing sampler updated per render primitive PER_MATERIAL_INSTANCE = 2, // samplers updates per material + PER_RENDERABLE_SKINNING = 3, // bone indices and weights sampler updated per render primitive // Update utils::Enum::count<>() below when adding values here // These are limited by CONFIG_SAMPLER_BINDING_COUNT (currently 4) }; @@ -132,7 +133,7 @@ struct utils::EnableIntegerOperators template<> inline constexpr size_t utils::Enum::count() { return 9; } template<> -inline constexpr size_t utils::Enum::count() { return 3; } +inline constexpr size_t utils::Enum::count() { return 4; } static_assert(utils::Enum::count() <= filament::backend::CONFIG_UNIFORM_BINDING_COUNT); static_assert(utils::Enum::count() <= filament::backend::CONFIG_SAMPLER_BINDING_COUNT); diff --git a/libs/filabridge/include/private/filament/SibStructs.h b/libs/filabridge/include/private/filament/SibStructs.h index 0fb56891a4..94bdb2b276 100644 --- a/libs/filabridge/include/private/filament/SibStructs.h +++ b/libs/filabridge/include/private/filament/SibStructs.h @@ -42,6 +42,12 @@ struct PerRenderPrimitiveMorphingSib { static constexpr size_t SAMPLER_COUNT = 2; }; +struct PerRenderPrimitiveSkinningSib { + static constexpr size_t BONE_INDICES_AND_WEIGHTS = 0; //bone indices and weights + + static constexpr size_t SAMPLER_COUNT = 1; +}; + } // namespace filament #endif //TNT_FILABRIDGE_SIBSTRUCTS_H diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 4f11b4b69e..2f76bbd745 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -439,7 +439,9 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, cg.generateUniforms(vs, ShaderStage::VERTEX, UniformBindingPoints::PER_RENDERABLE_BONES, UibGenerator::getPerRenderableBonesUib()); - + cg.generateSamplers(vs, SamplerBindingPoints::PER_RENDERABLE_SKINNING, + material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_RENDERABLE_SKINNING), + SibGenerator::getPerRenderPrimitiveBonesSib(variant)); cg.generateUniforms(vs, ShaderStage::VERTEX, UniformBindingPoints::PER_RENDERABLE_MORPHING, UibGenerator::getPerRenderableMorphingUib()); diff --git a/libs/filamat/src/shaders/SibGenerator.cpp b/libs/filamat/src/shaders/SibGenerator.cpp index eb1a5b0f0a..b03a6f3035 100644 --- a/libs/filamat/src/shaders/SibGenerator.cpp +++ b/libs/filamat/src/shaders/SibGenerator.cpp @@ -107,13 +107,28 @@ SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveMorphingSib(Vari return sib; } -SamplerInterfaceBlock const* SibGenerator::getSib( - SamplerBindingPoints bindingPoint, Variant variant) noexcept { +SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveBonesSib(Variant variant) noexcept { + using Type = SamplerInterfaceBlock::Type; + using Format = SamplerInterfaceBlock::Format; + using Precision = SamplerInterfaceBlock::Precision; + + static SamplerInterfaceBlock sib = SamplerInterfaceBlock::Builder() + .name("BonesBuffer") + .stageFlags(backend::ShaderStageFlags::VERTEX) + .add({{"indicesAndWeights", Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }}) + .build(); + + return sib; +} + +SamplerInterfaceBlock const* SibGenerator::getSib(SamplerBindingPoints bindingPoint, Variant variant) noexcept { switch (bindingPoint) { case SamplerBindingPoints::PER_VIEW: return &getPerViewSib(variant); case SamplerBindingPoints::PER_RENDERABLE_MORPHING: return &getPerRenderPrimitiveMorphingSib(variant); + case SamplerBindingPoints::PER_RENDERABLE_SKINNING: + return &getPerRenderPrimitiveBonesSib(variant); default: return nullptr; } diff --git a/libs/filamat/src/shaders/SibGenerator.h b/libs/filamat/src/shaders/SibGenerator.h index 18ae7bb9f2..eb874653c5 100644 --- a/libs/filamat/src/shaders/SibGenerator.h +++ b/libs/filamat/src/shaders/SibGenerator.h @@ -31,6 +31,7 @@ class SibGenerator { public: static SamplerInterfaceBlock const& getPerViewSib(Variant variant) noexcept; static SamplerInterfaceBlock const& getPerRenderPrimitiveMorphingSib(Variant variant) noexcept; + static SamplerInterfaceBlock const& getPerRenderPrimitiveBonesSib(Variant variant) noexcept; static SamplerInterfaceBlock const* getSib(filament::SamplerBindingPoints bindingPoint, Variant variant) noexcept; // When adding a sampler block here, make sure to also update // FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 340cd02a08..00a17efee6 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -231,8 +231,12 @@ if (NOT ANDROID) add_demo(gltf_viewer) add_demo(gltf_instances) add_demo(heightfield) + add_demo(hellomorphing) add_demo(hellopbr) add_demo(hellotriangle) + add_demo(helloskinning) + add_demo(helloskinningbuffer) + add_demo(helloskinningbuffer_morebones) add_demo(image_viewer) add_demo(lightbulb) add_demo(material_sandbox) @@ -243,6 +247,7 @@ if (NOT ANDROID) add_demo(sample_full_pbr) add_demo(sample_normal_map) add_demo(shadowtest) + add_demo(skinningtest) add_demo(strobecolor) add_demo(suzanne) add_demo(texturedquad) diff --git a/samples/hellomorphing.cpp b/samples/hellomorphing.cpp new file mode 100644 index 0000000000..b5886d3b7b --- /dev/null +++ b/samples/hellomorphing.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "generated/resources/resources.h" + +using namespace filament; +using utils::Entity; +using utils::EntityManager; +using namespace filament::math; + +struct App { + VertexBuffer* vb; + IndexBuffer* ib; + Material* mat; + Camera* cam; + Entity camera; + Skybox* skybox; + Entity renderable; + MorphTargetBuffer *mt1; + MorphTargetBuffer *mt2; +}; + +struct Vertex { + float2 position; + uint32_t color; +}; + +static const Vertex TRIANGLE_VERTICES[3] = { + {{1, 0}, 0xffff0000u}, // blue one (ABGR) + {{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u}, // green one + {{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu}, // red one +}; + +static const float3 targets_pos1[9] = { + {-2, 0, 0},{0, 2, 0},{1, 0, 0}, // 1st position for 1st, 2nd and 3rd point of the first primitive + {1, 1, 0},{-1, 0, 0},{-1, 0, 0}, // 2nd ... + {0, 0, 0},{0, 0, 0},{0, 0, 0} // no position change +}; + +static const float3 targets_pos2[9] = { + {0, 2, 0},{-2, 0, 0},{1, 0, 0}, // 1st position for 1st, 2nd and 3rd point of the second primitive + {-1, 0, 0},{1, 1, 0},{-1, 0, 0}, // position of th 3rd point is same for both morph targets + {0, 0, 0},{0, 0, 0}, {0, 0, 0} +}; + +static const short4 targets_tan[9] = { + {0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0}, + {0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0}, + {0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0} +}; + +static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 }; + +int main(int argc, char** argv) { + Config config; + config.title = "helloMorphing"; + + App 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); + static_assert(sizeof(Vertex) == 12, "Strange vertex size."); + app.vb = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .build(*engine); + app.vb->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr)); + app.ib = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr)); + app.mat = Material::Builder() + .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE) + .build(*engine); + + app.mt1 = MorphTargetBuffer::Builder() + .vertexCount(9) + .count(3) + .build(*engine); + + app.mt2 = MorphTargetBuffer::Builder() + .vertexCount(9) + .count(3) + .build(*engine); + + app.mt1->setPositionsAt(*engine,0, targets_pos1, 3, 0); + app.mt1->setPositionsAt(*engine,1, targets_pos1+3, 3, 0); + app.mt1->setPositionsAt(*engine,2, targets_pos1+6, 3, 0); + app.mt1->setTangentsAt(*engine,0, targets_tan, 3, 0); + app.mt1->setTangentsAt(*engine,1, targets_tan+3, 3, 0); + app.mt1->setTangentsAt(*engine,2, targets_tan+6, 3, 0); + + app.mt2->setPositionsAt(*engine,0, targets_pos2, 3, 0); + app.mt2->setPositionsAt(*engine,1, targets_pos2+3, 3, 0); + app.mt2->setPositionsAt(*engine,2, targets_pos2+6, 3, 0); + app.mt2->setTangentsAt(*engine,0, targets_tan, 3, 0); + app.mt2->setTangentsAt(*engine,1, targets_tan+3, 3, 0); + app.mt2->setTangentsAt(*engine,2, targets_tan+6, 3, 0); + + app.renderable = EntityManager::get().create(); + + RenderableManager::Builder(2) + .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) + .material(0, app.mat->getDefaultInstance()) + .material(1, app.mat->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3) + .geometry(1, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .morphing(3) + .morphing(0,0,app.mt1) + .morphing(0,1,app.mt2) + .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.mat); + engine->destroy(app.vb); + engine->destroy(app.ib); + engine->destroy(app.mt1); + engine->destroy(app.mt2); + engine->destroyCameraComponent(app.camera); + utils::EntityManager::get().destroy(app.camera); + }; + + FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { + constexpr float ZOOM = 1.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(); + // morphTarget/blendshapes animation defined for all primitives + float z = (float)(sin(now)/2.f + 0.5f); + float weights[] = {1 - z, z/2, z/2}; + // set global weights of all morph targets + rm.setMorphWeights(rm.getInstance(app.renderable), weights, 3, 0); + }); + + FilamentApp::get().run(config, setup, cleanup); + + return 0; +} diff --git a/samples/helloskinning.cpp b/samples/helloskinning.cpp new file mode 100644 index 0000000000..d15f78ba1f --- /dev/null +++ b/samples/helloskinning.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "generated/resources/resources.h" + +using namespace filament; +using utils::Entity; +using utils::EntityManager; +using namespace filament::math; + +struct App { + VertexBuffer* vb; + VertexBuffer* vb2; + IndexBuffer* ib; + Material* mat; + Camera* cam; + Entity camera; + Skybox* skybox; + Entity renderable; +}; + +struct VertexWithBones { + float2 position; + uint32_t color; + filament::math::ushort4 joints; + filament::math::float4 weighs; +}; + +static const VertexWithBones TRIANGLE_VERTICES_WITHBONES[6] = { + {{1, 0}, 0xffff0000u, {0,1,0,0}, {1.0f,0.f,0.f,0.f}}, + {{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u, {0,1,0,0}, {0.f,1.f,0.f,0.f}}, + {{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu,{0,1,0,0}, {0.5f,0.5f,0.f,0.f}}, + {{1, -1}, 0xffffff00u, {0,2,0,0}, {0.0f,1.f,0.f,0.f}}, + {{-cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ffffu, {0,1,0,0}, {0.f,1.f,0.f,0.f}}, + {{-cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xffff00ffu,{0,1,0,0}, {0.f,0.f,0.5f,0.5f}}, +}; + +static constexpr uint16_t TRIANGLE_INDICES[6] = { 0, 1, 2, 3}; + +mat4f transforms[] = {mat4f(1), + mat4f::translation(float3(1, 0, 0)), + mat4f::translation(float3(1, 1, 0)), + mat4f::translation(float3(0, 1, 0))}; + +int main(int argc, char** argv) { + Config config; + config.title = "hello skinning"; + + App 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); + static_assert(sizeof(VertexWithBones) == 36, "Strange vertex size."); + app.vb = VertexBuffer::Builder() + .vertexCount(4) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 36) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 36) + .normalized(VertexAttribute::COLOR) + .attribute(VertexAttribute::BONE_INDICES, 0, VertexBuffer::AttributeType::USHORT4, 12, 36) + .attribute(VertexAttribute::BONE_WEIGHTS, 0, VertexBuffer::AttributeType::FLOAT4, 20, 36) + .build(*engine); + app.vb2 = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 36) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 36) + .normalized(VertexAttribute::COLOR) + .attribute(VertexAttribute::BONE_INDICES, 0, VertexBuffer::AttributeType::USHORT4, 12, 36) + .attribute(VertexAttribute::BONE_WEIGHTS, 0, VertexBuffer::AttributeType::FLOAT4, 20, 36) + .build(*engine); + app.vb->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES_WITHBONES, 154, nullptr)); + app.vb2->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES_WITHBONES + 3, 108, nullptr)); + app.ib = IndexBuffer::Builder() + .indexCount(4) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 8, nullptr)); + app.mat = Material::Builder() + .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE) + .build(*engine); + + app.renderable = EntityManager::get().create(); + + RenderableManager::Builder(2) + .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) + .material(0, app.mat->getDefaultInstance()) + .material(1, app.mat->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLE_STRIP, app.vb, app.ib, 0, 4) + .geometry(1, RenderableManager::PrimitiveType::TRIANGLES, app.vb2, app.ib, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .skinning(4, transforms) + .enableSkinningBuffers(false) + .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.mat); + engine->destroy(app.vb); + engine->destroy(app.vb2); + engine->destroy(app.ib); + engine->destroyCameraComponent(app.camera); + utils::EntityManager::get().destroy(app.camera); + }; + + FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { + constexpr float ZOOM = 1.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(); + + // Bone skinning animation + float tr = (float)(sin(now)); + mat4f trans[] = {filament::math::mat4f::translation(filament::math::float3{tr, 0, 0}), + filament::math::mat4f::translation(filament::math::float3{-1, tr, 0}), + filament::math::mat4f(1.f)}; + rm.setBones(rm.getInstance(app.renderable), trans, 3, 0); + + + }); + + FilamentApp::get().run(config, setup, cleanup); + + return 0; +} diff --git a/samples/helloskinningbuffer.cpp b/samples/helloskinningbuffer.cpp new file mode 100644 index 0000000000..c69f962855 --- /dev/null +++ b/samples/helloskinningbuffer.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2023 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "generated/resources/resources.h" + +using namespace filament; +using utils::Entity; +using utils::EntityManager; +using namespace filament::math; + +struct App { + VertexBuffer *vb, *vb2; + IndexBuffer* ib; + Material* mat; + Camera* cam; + Entity camera; + Skybox* skybox; + Entity renderable1; + Entity renderable2; + SkinningBuffer *sb; +}; + +struct Vertex { + float2 position; + uint32_t color; +}; + +static const Vertex TRIANGLE_VERTICES[3] = { + {{1, 0}, 0xffff0000u}, + {{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u}, + {{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu}, +}; + +static const uint16_t skinJoints[] = { 0, 1, 2, 3, + 0, 1, 2, 3, + 0, 1, 2, 3 +}; + +static const float skinWeights[] = { 0.25f, 0.25f, 0.25f, 0.25f, + 0.25f, 0.25f, 0.25f, 0.25f, + 0.25f, 0.25f, 0.25f, 0.25f +}; + +static constexpr uint16_t TRIANGLE_INDICES[] = { 0, 1, 2, 3 }; + +mat4f transforms[] = {math::mat4f(1.f), + mat4f::translation(float3(1, 0, 0)), + mat4f::translation(float3(1, 1, 0)), + mat4f::translation(float3(0, 1, 0)), + mat4f::translation(float3(-1, 1, 0)), + mat4f::translation(float3(-1, 0, 0)), + mat4f::translation(float3(-1, -1, 0)), + mat4f::translation(float3(0, -1, 0)), + mat4f::translation(float3(1, -1, 0))}; + +int main(int argc, char** argv) { + Config config; + config.title = "skinning buffer common for two renderables"; + + App 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); + static_assert(sizeof(Vertex) == 12, "Strange vertex size."); + app.vb = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(3) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .attribute(VertexAttribute::BONE_INDICES, 1, VertexBuffer::AttributeType::USHORT4, 0, 8) + .attribute(VertexAttribute::BONE_WEIGHTS, 2, VertexBuffer::AttributeType::FLOAT4, 0, 16) + .build(*engine); + app.vb->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr)); + app.vb->setBufferAt(*engine, 1, + VertexBuffer::BufferDescriptor(skinJoints, 24, nullptr)); + app.vb->setBufferAt(*engine, 2, + VertexBuffer::BufferDescriptor(skinWeights, 48, nullptr)); + + app.vb2 = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .build(*engine); + app.vb2->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr)); + app.ib = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr)); + app.mat = Material::Builder() + .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE) + .build(*engine); + + app.sb = SkinningBuffer::Builder() + .boneCount(9) + .initialize() + .build(*engine); + app.sb->setBones(*engine, transforms,9,0); + + app.renderable1 = EntityManager::get().create(); + app.renderable2 = EntityManager::get().create(); + + RenderableManager::Builder(1) + .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) + .material(0, app.mat->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .enableSkinningBuffers(true) + .skinning(app.sb, 9, 0) + .build(*engine, app.renderable1); + + RenderableManager::Builder(2) + .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) + .material(0, app.mat->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3) + .geometry(1, RenderableManager::PrimitiveType::TRIANGLES, app.vb2, app.ib, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .enableSkinningBuffers(true) + .skinning(app.sb, 9, 0) + .build(*engine, app.renderable2); + + scene->addEntity(app.renderable1); + scene->addEntity(app.renderable2); + 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.renderable1); + engine->destroy(app.renderable2); + engine->destroy(app.mat); + engine->destroy(app.vb); + engine->destroy(app.vb2); + engine->destroy(app.ib); + engine->destroy(app.sb); + engine->destroyCameraComponent(app.camera); + utils::EntityManager::get().destroy(app.camera); + }; + + FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { + constexpr float ZOOM = 1.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& tcm = engine->getTransformManager(); + + // Transformation of both renderables + tcm.setTransform(tcm.getInstance(app.renderable1), + filament::math::mat4f::translation(filament::math::float3{ 0.5, 0, 0 })); + tcm.setTransform(tcm.getInstance(app.renderable2), + filament::math::mat4f::translation(filament::math::float3{ 0, 0.5, 0 })); + + auto& rm = engine->getRenderableManager(); + + // Bone skinning animation + float t = (float)(now - (int)now); + float s = sin(t * f::PI * 2.f); + float c = cos(t * f::PI * 2.f); + + mat4f translate[] = {mat4f::translation(float3(s, c, 0))}; + + mat4f trans1of8[9] = {}; + for (size_t i = 0; i < 9; i++) { + trans1of8[i] = filament::math::mat4f(1); + } + s *= 5; + mat4f transA[] = { + mat4f::translation(float3(s, 0, 0)), + mat4f::translation(float3(s, s, 0)), + mat4f::translation(float3(0, s, 0)), + mat4f::translation(float3(-s, s, 0)), + mat4f::translation(float3(-s, 0, 0)), + mat4f::translation(float3(-s, -s, 0)), + mat4f::translation(float3(0, -s, 0)), + mat4f::translation(float3(s, -s, 0)), + filament::math::mat4f(1)}; + size_t offset = ((size_t)now) % 8; + trans1of8[offset] = transA[offset]; + + // Set transformation of the first bone + app.sb->setBones(*engine, translate, 1, 0); + + // Set transformation of the other bones, only 3 of them can be used, do to limitation + app.sb->setBones(*engine,trans1of8, 8, 1); + }); + + FilamentApp::get().run(config, setup, cleanup); + + return 0; +} diff --git a/samples/helloskinningbuffer_morebones.cpp b/samples/helloskinningbuffer_morebones.cpp new file mode 100644 index 0000000000..48fe174e15 --- /dev/null +++ b/samples/helloskinningbuffer_morebones.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2023 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "generated/resources/resources.h" + +using namespace filament; +using utils::Entity; +using utils::EntityManager; +using namespace filament::math; + +struct App { + VertexBuffer *vb1, *vb2; + IndexBuffer *ib1, *ib2; + Material* mat; + Camera* cam; + Entity camera; + Skybox* skybox; + Entity renderable1, renderable2; + SkinningBuffer *sb; +}; + +struct Vertex { + float2 position; + uint32_t color; +}; + +static const Vertex TRIANGLE_VERTICES[3] = { + {{1, 0}, 0xffff0000u}, + {{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u}, + {{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu}, +}; + +static constexpr uint16_t TRIANGLE_INDICES[] = { 0, 1, 2, 3 }; + +mat4f transforms[] = {math::mat4f(1.f), + mat4f::translation(float3(1, 0, 0)), + mat4f::translation(float3(1, 1, 0)), + mat4f::translation(float3(0, 1, 0)), + mat4f::translation(float3(-1, 1, 0)), + mat4f::translation(float3(-1, 0, 0)), + mat4f::translation(float3(-1, -1, 0)), + mat4f::translation(float3(0, -1, 0)), + mat4f::translation(float3(1, -1, 0))}; + + +utils::FixedCapacityVector> boneDataPerPrimitive(3); + +int main(int argc, char** argv) { + Config config; + config.title = "skinning buffer common for two renderables"; + size_t boneCount = 9; + utils::FixedCapacityVector boneDataPerVertex(9); + float weight = 1.f / boneCount; + for (size_t idx = 0; idx < boneCount; idx++) { + boneDataPerVertex[idx] = float2(idx, weight); + } + auto idx = 0; + boneDataPerPrimitive[idx++] = boneDataPerVertex; + boneDataPerPrimitive[idx++] = boneDataPerVertex; + boneDataPerPrimitive[idx++] = boneDataPerVertex; + + App 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); + static_assert(sizeof(Vertex) == 12, "Strange vertex size."); + app.vb1 = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .advancedSkinning(true) + .build(*engine); + app.vb1->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr)); + app.vb2 = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .advancedSkinning(true) + .build(*engine); + app.vb2->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr)); + + app.ib1 = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib2 = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib1->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr)); + app.ib2->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr)); + app.mat = Material::Builder() + .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE) + .build(*engine); + + app.sb = SkinningBuffer::Builder() + .boneCount(256) + .initialize() + .build(*engine); + app.sb->setBones(*engine, transforms,9,0); + + app.renderable1 = EntityManager::get().create(); + app.renderable2 = EntityManager::get().create(); + + RenderableManager::Builder(1) + .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) + .material(0, app.mat->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb1, app.ib1, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .enableSkinningBuffers(true) + .skinning(app.sb, 9, 0) + // Set bone indices and weight for 3 vertices, 9 bones per vertx + .boneIndicesAndWeights(0, boneDataPerPrimitive) + .build(*engine, app.renderable1); + + RenderableManager::Builder(1) + .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) + .material(0, app.mat->getDefaultInstance()) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb2, app.ib2, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .enableSkinningBuffers(true) + .skinning(app.sb, 9, 0) + // Set bone indices and weight for 3 vertices, 9 bones per vertx + .boneIndicesAndWeights(0, boneDataPerPrimitive) + .build(*engine, app.renderable2); + + scene->addEntity(app.renderable1); + scene->addEntity(app.renderable2); + 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.renderable1); + engine->destroy(app.renderable2); + engine->destroy(app.mat); + engine->destroy(app.vb1); + engine->destroy(app.ib1); + engine->destroy(app.vb2); + engine->destroy(app.ib2); + engine->destroy(app.sb); + engine->destroyCameraComponent(app.camera); + utils::EntityManager::get().destroy(app.camera); + }; + + FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { + constexpr float ZOOM = 1.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& tcm = engine->getTransformManager(); + + // Transformation of both renderables + tcm.setTransform(tcm.getInstance(app.renderable1), + filament::math::mat4f::translation(filament::math::float3{ 0.5, 0, 0 })); + tcm.setTransform(tcm.getInstance(app.renderable2), + filament::math::mat4f::translation(filament::math::float3{ 0, 0.5, 0 })); + + auto& rm = engine->getRenderableManager(); + + // Bone skinning animation + float t = (float)(now - (int)now); + float s = sin(t * f::PI * 2.f); + float c = cos(t * f::PI * 2.f); + + mat4f translate[] = {mat4f::translation(float3(s, c, 0))}; + + mat4f trans[9] = {}; + for (size_t i = 0; i < 9; i++) { + trans[i] = filament::math::mat4f(1); + } + s *= 8; + mat4f transA[] = { + mat4f::translation(float3(s, 0, 0)), + mat4f::translation(float3(s, s, 0)), + mat4f::translation(float3(0, s, 0)), + mat4f::translation(float3(-s, s, 0)), + mat4f::translation(float3(-s, 0, 0)), + mat4f::translation(float3(-s, -s, 0)), + mat4f::translation(float3(0, -s, 0)), + mat4f::translation(float3(s, -s, 0)), + filament::math::mat4f(1)}; + size_t offset = ((size_t)now) % 8; + trans[offset] = transA[offset]; + + // Set transformation of the first bone + app.sb->setBones(*engine, translate, 1, 0); + + // Set transformation of the others bones + app.sb->setBones(*engine,trans, 8, 1); + + }); + + FilamentApp::get().run(config, setup, cleanup); + + return 0; +} diff --git a/samples/skinningtest.cpp b/samples/skinningtest.cpp new file mode 100644 index 0000000000..7d148a14ae --- /dev/null +++ b/samples/skinningtest.cpp @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2023 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "generated/resources/resources.h" + +using namespace filament; +using utils::Entity; +using utils::EntityManager; +using utils::FixedCapacityVector; +using namespace filament::math; + +struct App { + VertexBuffer* vbs[10]; + size_t vbCount = 0; + IndexBuffer *ib, *ib2; + Material* mat; + Camera* cam; + Entity camera; + Skybox* skybox; + Entity renderables[4]; + SkinningBuffer *sb, *sb2; + MorphTargetBuffer *mt; + BufferObject* bos[10]; + size_t boCount = 0; + size_t bonesPerVertex; + FixedCapacityVector> + boneDataPerPrimitive, + boneDataPerPrimitiveMulti; +}; + +struct Vertex { + float2 position; + uint32_t color; +}; + +static const Vertex TRIANGLE_VERTICES_1[6] = { + {{ 1, 0}, 0xff00ff00u}, + {{ cos(M_PI * 1 / 3), sin(M_PI * 1 / 3)}, 0xff330088u}, + {{ cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff880033u}, + {{-1, 0}, 0xff00ff00u}, + {{ cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff330088u}, + {{ cos(M_PI * 5 / 3), sin(M_PI * 5 / 3)}, 0xff880033u}, +}; + +static const Vertex TRIANGLE_VERTICES_2[6] = { + {{ 1, 0}, 0xff0000ffu}, + {{ cos(M_PI * 1 / 3), sin(M_PI * 1 / 3)}, 0xfff055ffu}, + {{ cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff880088u}, + {{-1, 0}, 0xff0000ffu}, + {{ cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xfff055ffu}, + {{ cos(M_PI * 5 / 3), sin(M_PI * 5 / 3)}, 0xff880088u}, +}; + +static const Vertex TRIANGLE_VERTICES_3[6] = { + {{ 1, 0}, 0xfff00f88u}, + {{ cos(M_PI * 1 / 3), sin(M_PI * 1 / 3)}, 0xff00ffaau}, + {{ cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ffffu}, + {{-1, 0}, 0xfff00f88u}, + {{ cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff00ffaau}, + {{ cos(M_PI * 5 / 3), sin(M_PI * 5 / 3)}, 0xff00ffffu}, +}; + + +static const float3 targets_pos[9] = { + { -2, 0, 0},{ 0, 2, 0},{ 1, 0, 0}, + { 1, 1, 0},{ -1, 0, 0},{ -1, 0, 0}, + { 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0} +}; + +static const short4 targets_tan[9] = { + { 0, 0, 0, 0},{ 0, 0, 0, 0},{ 0, 0, 0, 0}, + { 0, 0, 0, 0},{ 0, 0, 0, 0},{ 0, 0, 0, 0}, + { 0, 0, 0, 0},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}; + +static const uint16_t skinJoints[] = { 0, 1, 2, 5, + 0, 2, 3, 5, + 0, 3, 1, 5}; + +static const float skinWeights[] = { 0.5f, 0.0f, 0.0f, 0.5f, + 0.5f, 0.0f, 0.f, 0.5f, + 0.5f, 0.0f, 0.f, 0.5f,}; + +static float2 boneDataArray[48] = {}; //indices and weights for up to 3 vertices with 8 bones + +static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 }, +TRIANGLE_INDICES_2[6] = { 0, 2, 4, 1, 3, 5 }; + +mat4f transforms[] = {math::mat4f(1), + mat4f::translation(float3(1, 0, 0)), + mat4f::translation(float3(1, 1, 0)), + mat4f::translation(float3(0, 1, 0)), + mat4f::translation(float3(-1, 1, 0)), + mat4f::translation(float3(-1, 0, 0)), + mat4f::translation(float3(-1, -1, 0)), + mat4f::translation(float3(0, -1, 0)), + mat4f::translation(float3(1, -1, 0))}; + +int main(int argc, char** argv) { + App app; + + app.boneDataPerPrimitive = FixedCapacityVector>(3); + app.boneDataPerPrimitiveMulti = FixedCapacityVector>(6); + app.bonesPerVertex = 8; + + Config config; + config.title = "skinning test with more than 4 bones per vertex"; + + size_t boneCount = app.bonesPerVertex; + float weight = 1.f / boneCount; + FixedCapacityVector boneDataPerVertex(boneCount); + for (size_t idx = 0; idx < boneCount; idx++) { + boneDataPerVertex[idx] = float2(idx, weight); + boneDataArray[idx] = float2(idx, weight); + boneDataArray[idx + boneCount] = float2(idx, weight); + boneDataArray[idx + 2 * boneCount] = float2(idx, weight); + boneDataArray[idx + 3 * boneCount] = float2(idx, weight); + boneDataArray[idx + 4 * boneCount] = float2(idx, weight); + boneDataArray[idx + 5 * boneCount] = float2(idx, weight); + } + + auto idx = 0; + app.boneDataPerPrimitive[idx++] = boneDataPerVertex; + app.boneDataPerPrimitive[idx++] = boneDataPerVertex; + app.boneDataPerPrimitive[idx++] = boneDataPerVertex; + + for (size_t vertex_idx = 0; vertex_idx < 6; vertex_idx++) { + boneCount = vertex_idx % app.bonesPerVertex + 1; + weight = 1.f / boneCount; + FixedCapacityVector boneDataPerVertex1(boneCount); + for (size_t idx = 0; idx < boneCount; idx++) { + boneDataPerVertex1[idx] = float2(idx, weight); + } + app.boneDataPerPrimitiveMulti[vertex_idx] = boneDataPerVertex1; + } + + 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); + static_assert(sizeof(Vertex) == 12, "Strange vertex size."); + + // primitives for renderable 0 ------------------------- + // primitive 0/1, triangle without skinning + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .build(*engine); + app.vbs[app.vbCount]->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES_1, 36, + nullptr)); + app.vbCount++; + + // primitive 0/2, triangle without skinning, buffer objects enabled + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .enableBufferObjects() + .build(*engine); + app.bos[app.boCount] = BufferObject::Builder() + .size(3 * sizeof(Vertex)) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + TRIANGLE_VERTICES_1 + 3, app.bos[app.boCount]->getByteCount(), + nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 0, + app.bos[app.boCount]); + app.vbCount++; + app.boCount++; + + // primitives for renderable 1 ------------------------- + // primitive 1/1, triangle with skinning vertex attributes (only 4 bones), + // buffer object disabled + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(3) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .attribute(VertexAttribute::BONE_INDICES, 1, + VertexBuffer::AttributeType::USHORT4, 0, 8) + .attribute(VertexAttribute::BONE_WEIGHTS, 2, + VertexBuffer::AttributeType::FLOAT4, 0, 16) + .build(*engine); + app.vbs[app.vbCount]->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES_2, 36, + nullptr)); + app.vbs[app.vbCount]->setBufferAt(*engine, 1, + VertexBuffer::BufferDescriptor(skinJoints, 24, nullptr)); + app.vbs[app.vbCount]->setBufferAt(*engine, 2, + VertexBuffer::BufferDescriptor(skinWeights, 48, nullptr)); + app.vbCount++; + + // primitive 1/2, triangle with skinning vertex attributes (only 4 bones), + // buffer objects enabled + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(3) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .attribute(VertexAttribute::BONE_INDICES, 1, + VertexBuffer::AttributeType::USHORT4, 0, 8) + .attribute(VertexAttribute::BONE_WEIGHTS, 2, + VertexBuffer::AttributeType::FLOAT4, 0, 16) + .enableBufferObjects() + .build(*engine); + app.bos[app.boCount] = BufferObject::Builder() + .size(3 * sizeof(Vertex)) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + TRIANGLE_VERTICES_2 + 2, app.bos[app.boCount]->getByteCount(), + nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 0, + app.bos[app.boCount]); + app.boCount++; + app.bos[app.boCount] = BufferObject::Builder() + .size(24) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + skinJoints, app.bos[app.boCount]->getByteCount(), nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 1, + app.bos[app.boCount]); + app.boCount++; + app.bos[app.boCount] = BufferObject::Builder() + .size(48) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + skinWeights, app.bos[app.boCount]->getByteCount(), nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 2, + app.bos[app.boCount]); + app.boCount++; + app.vbCount++; + + // primitives for renderable 2 ------------------------- + // primitive 2/1, triangle with advanced skinning, buffer objects enabled + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .enableBufferObjects() + .advancedSkinning(true) + .build(*engine); + app.bos[app.boCount] = BufferObject::Builder() + .size(3 * sizeof(Vertex)) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + TRIANGLE_VERTICES_3, app.bos[app.boCount]->getByteCount(), nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 0, + app.bos[app.boCount]); + app.boCount++; + app.vbCount++; + + // primitive 2/2, triangle with advanced skinning, buffer objects enabled, for morph + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .enableBufferObjects() + .advancedSkinning(true) + .build(*engine); + app.bos[app.boCount] = BufferObject::Builder() + .size(3 * sizeof(Vertex)) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + TRIANGLE_VERTICES_3 + 1, app.bos[app.boCount]->getByteCount(), + nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 0, + app.bos[app.boCount]); + app.boCount++; + app.vbCount++; + + // primitive 2/3, triangle with advanced skinning, buffer objects enabled, + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .enableBufferObjects() + .advancedSkinning(true) + .build(*engine); + app.bos[app.boCount] = BufferObject::Builder() + .size(3 * sizeof(Vertex)) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + TRIANGLE_VERTICES_3 + 2, app.bos[app.boCount]->getByteCount(), + nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 0, + app.bos[app.boCount]); + app.boCount++; + app.vbCount++; + + // primitives for renderable 3 ------------------------- + // primitive 3/1, two triangles with advanced skinning, buffer objects enabled, + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(6) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .enableBufferObjects() + .advancedSkinning(true) + .build(*engine); + app.bos[app.boCount] = BufferObject::Builder() + .size(6 * sizeof(Vertex)) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + TRIANGLE_VERTICES_1, app.bos[app.boCount]->getByteCount(), + nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 0, + app.bos[app.boCount]); + app.boCount++; + app.vbCount++; + // primitive 3/2, triangle with advanced skinning and morph, buffer objects enabled, + app.vbs[app.vbCount] = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT2, 0, 12) + .attribute(VertexAttribute::COLOR, 0, + VertexBuffer::AttributeType::UBYTE4, 8, 12) + .normalized(VertexAttribute::COLOR) + .enableBufferObjects() + .advancedSkinning(true) + .build(*engine); + app.bos[app.boCount] = BufferObject::Builder() + .size(3 * sizeof(Vertex)) + .build(*engine); + app.bos[app.boCount]->setBuffer(*engine, BufferObject::BufferDescriptor( + TRIANGLE_VERTICES_3 + 2, app.bos[app.boCount]->getByteCount(), + nullptr)); + app.vbs[app.vbCount]->setBufferObjectAt(*engine, 0, + app.bos[app.boCount]); + app.boCount++; + app.vbCount++; + + // Index buffer data + app.ib = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, + 3 * sizeof(uint16_t),nullptr)); + + app.ib2 = IndexBuffer::Builder() + .indexCount(6) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + app.ib2->setBuffer(*engine, + IndexBuffer::BufferDescriptor(TRIANGLE_INDICES_2, + 6 * sizeof(uint16_t),nullptr)); + + app.mat = Material::Builder() + .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE) + .build(*engine); + +// Skinning buffer for renderable 2 + app.sb = SkinningBuffer::Builder() + .boneCount(9) + .initialize(true) + .build(*engine); + +// Skinning buffer common for renderable 3 + app.sb2 = SkinningBuffer::Builder() + .boneCount(9) + .initialize(true) + .build(*engine); + + app.sb->setBones(*engine, transforms,9,0); + +// Morph target definition to check combination bone skinning and blend shapes + app.mt = MorphTargetBuffer::Builder() + .vertexCount(9) + .count(3) + .build( *engine); + + app.mt->setPositionsAt(*engine,0, targets_pos, 3, 0); + app.mt->setPositionsAt(*engine,1, targets_pos+3, 3, 0); + app.mt->setPositionsAt(*engine,2, targets_pos+6, 3, 0); + app.mt->setTangentsAt(*engine,0, targets_tan, 9, 0); + app.mt->setTangentsAt(*engine,1, targets_tan, 9, 0); + app.mt->setTangentsAt(*engine,2, targets_tan, 9, 0); + +// renderable 0: no skinning +// primitive 0 = triangle, no skinning, no morph target +// primitive 1 = triangle, no skinning, morph target +// primitive 2 = triangle, no skinning, no morph target, buffer objects enabled +// primitive 3 = triangle, no skinning, morph target, buffer objects enabled + app.renderables[0] = EntityManager::get().create(); + RenderableManager::Builder(4) + .boundingBox({{ -1, -1, -1}, { 1, 1, 1}}) + .material(0, app.mat->getDefaultInstance()) + .material(1, app.mat->getDefaultInstance()) + .material(2, app.mat->getDefaultInstance()) + .material(3, app.mat->getDefaultInstance()) + .geometry(0,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[0],app.ib,0,3) + .geometry(1,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[0],app.ib,0,3) + .geometry(2,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[1],app.ib,0,3) + .geometry(3,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[1],app.ib,0,3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .morphing(3) + .morphing(0,1,app.mt) + .morphing(0,3,app.mt) + .build(*engine, app.renderables[0]); + +// renderable 1: attribute bone data definitions skinning +// primitive 0 = triangle with skinning and with morphing, bone data defined as vertex attributes (buffer object) +// primitive 1 = trinagle with skinning, bone data defined as vertex attributes +// primitive 3 = triangle with skinning, bone data defined as vertex attributes (buffer object) +// primitive 2 = triangle with skinning and with morphing, bone data defined as vertex attributes + app.renderables[1] = EntityManager::get().create(); + RenderableManager::Builder(4) + .boundingBox({{ -1, -1, -1}, { 1, 1, 1}}) + .material(0, app.mat->getDefaultInstance()) + .material(1, app.mat->getDefaultInstance()) + .material(2, app.mat->getDefaultInstance()) + .material(3, app.mat->getDefaultInstance()) + .geometry(1,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[2],app.ib,0,3) + .geometry(2,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[2],app.ib,0,3) + .geometry(0,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[3],app.ib,0,3) + .geometry(3,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[3],app.ib,0,3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .enableSkinningBuffers(true) + .skinning(app.sb, 9, 0) + .morphing(3) + .morphing(0,2,app.mt) + .morphing(0,0,app.mt) + .build(*engine, app.renderables[1]); + +// renderable 2: various ways of skinning definitions +// primitive 0 = skinned triangle, advanced bone data defined as array per primitive, +// primitive 1 = skinned triangle, advanced bone data defined as vector per primitive, +// primitive 2 = triangle with skinning and with morphing, advanced bone data +// defined as vector per primitive + app.renderables[2] = EntityManager::get().create(); + RenderableManager::Builder(3) + .boundingBox({{ -1, -1, -1}, { 1, 1, 1}}) + .material(0, app.mat->getDefaultInstance()) + .material(1, app.mat->getDefaultInstance()) + .material(2, app.mat->getDefaultInstance()) + .geometry(0,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[4], app.ib, 0, 3) + .geometry(1,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[5], app.ib, 0, 3) + .geometry(2,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[6], app.ib, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .enableSkinningBuffers(true) + .skinning(app.sb, 9, 0) + + .boneIndicesAndWeights(0, boneDataArray, + 3 * app.bonesPerVertex, app.bonesPerVertex) + .boneIndicesAndWeights(1, app.boneDataPerPrimitive) + .boneIndicesAndWeights(2, app.boneDataPerPrimitive) + + .morphing(3) + .morphing(0, 2, app.mt) + .build(*engine, app.renderables[2]); + +// renderable 3: combination attribute and advance bone data +// primitive 0 = triangle with skinning and morphing, bone data defined as vertex attributes +// primitive 1 = skinning of two triangles, advanced bone data defined as vector per primitive, +// various number of bones per vertex 1, 2, ... 6 +// primitive 2 = triangle with skinning and morphing, advanced bone data defined +// as vector per primitive + app.renderables[3] = EntityManager::get().create(); + RenderableManager::Builder(3) + .boundingBox({{ -1, -1, -1}, { 1, 1, 1}}) + .material(0, app.mat->getDefaultInstance()) + .material(1, app.mat->getDefaultInstance()) + .material(2, app.mat->getDefaultInstance()) + .geometry(0,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[2], app.ib, 0, 3) + .geometry(1,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[7], app.ib2, 0, 6) + .geometry(2,RenderableManager::PrimitiveType::TRIANGLES, + app.vbs[8], app.ib, 0, 3) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .enableSkinningBuffers(true) + .skinning(app.sb, 9, 0) + .boneIndicesAndWeights(1, app.boneDataPerPrimitiveMulti) + .boneIndicesAndWeights(2, app.boneDataPerPrimitive) + .morphing(3) + .morphing(0,0,app.mt) + .morphing(0,2,app.mt) + .build(*engine, app.renderables[3]); + + scene->addEntity(app.renderables[0]); + scene->addEntity(app.renderables[1]); + scene->addEntity(app.renderables[2]); + scene->addEntity(app.renderables[3]); + app.camera = 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.mat); + engine->destroy(app.ib); + engine->destroy(app.ib2); + engine->destroy(app.sb); + engine->destroy(app.sb2); + engine->destroy(app.mt); + engine->destroyCameraComponent(app.camera); + EntityManager::get().destroy(app.camera); + for (auto i = 0; i < app.vbCount; i++) { + engine->destroy(app.vbs[i]); + } + for ( auto i = 0; i < app.boCount; i++) { + engine->destroy(app.bos[i]); + } + for ( auto i = 0; i < 4; i++) { + engine->destroy(app.renderables[i]); + } + }; + + FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { + constexpr float ZOOM = 1.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(); + + // Bone skinning animation for more than four bones per vertex + float t = (float)(now - (int)now); + size_t offset = ((size_t)now) % 9; + float s = sin(t * f::PI * 2.f) * 10; + mat4f trans[9] = {}; + for (size_t i = 0; i < 9; i++) { + trans[i] = filament::math::mat4f(1); + } + mat4f trans2[9] = {}; + for (size_t i = 0; i < 9; i++) { + trans2[i] = filament::math::mat4f(1); + } + mat4f transA[] = { + mat4f::scaling(float3(s / 10.f,s / 10.f, 1.f)), + mat4f::translation(float3(s, 0, 0)), + mat4f::translation(float3(s, s, 0)), + mat4f::translation(float3(0, s, 0)), + mat4f::translation(float3(-s, s, 0)), + mat4f::translation(float3(-s, 0, 0)), + mat4f::translation(float3(-s, -s, 0)), + mat4f::translation(float3(0, -s, 0)), + mat4f::translation(float3(s, -s, 0)), + filament::math::mat4f(1)}; + trans[offset] = transA[offset]; + trans2[offset] = transA[(offset + 3) % 9]; + + app.sb->setBones(*engine,trans, 9, 0); + app.sb2->setBones(*engine,trans2, 9, 0); + + // Morph targets (blendshapes) animation + float z = (float)(sin(now)/2.f + 0.5f); + float weights[] = { 1 - z, 0, z}; + rm.setMorphWeights(rm.getInstance(app.renderables[0]), weights, 3, 0); + rm.setMorphWeights(rm.getInstance(app.renderables[1]), weights, 3, 0); + rm.setMorphWeights(rm.getInstance(app.renderables[2]), weights, 3, 0); + rm.setMorphWeights(rm.getInstance(app.renderables[3]), weights, 3, 0); + }); + + FilamentApp::get().run(config, setup, cleanup); + + return 0; +} diff --git a/shaders/src/getters.vs b/shaders/src/getters.vs index cc06a19f5c..b5186c4021 100644 --- a/shaders/src/getters.vs +++ b/shaders/src/getters.vs @@ -33,6 +33,7 @@ int getVertexIndex() { #endif #if defined(VARIANT_HAS_SKINNING_OR_MORPHING) +#define MAX_SKINNING_BUFFER_WIDTH 2048u vec3 mulBoneNormal(vec3 n, uint i) { highp mat3 cof; @@ -62,18 +63,84 @@ vec3 mulBoneVertex(vec3 v, uint i) { return v.x * m[0].xyz + (v.y * m[1].xyz + (v.z * m[2].xyz + m[3].xyz)); } -void skinNormal(inout vec3 n, const uvec4 ids, const vec4 weights) { - n = mulBoneNormal(n, ids.x) * weights.x - + mulBoneNormal(n, ids.y) * weights.y - + mulBoneNormal(n, ids.z) * weights.z - + mulBoneNormal(n, ids.w) * weights.w; +void skinPosition(inout vec3 p, const uvec4 ids, const vec4 weights) { + // standard skinning for 4 weights, some of them could be zero + if (weights.w >= 0.0) { + p = weights.x * mulBoneVertex(p, uint(ids.x)) + + weights.y * mulBoneVertex(p, uint(ids.y)) + + weights.z * mulBoneVertex(p, uint(ids.z)) + + weights.w * mulBoneVertex(p, uint(ids.w)); + return; + } + // skinning for >4 weights + vec3 posSum = weights.x * mulBoneVertex(p, uint(ids.x)); + posSum += weights.y * mulBoneVertex(p, uint(ids.y)); + posSum += weights.z * mulBoneVertex(p, uint(ids.z)); + uint pairIndex = uint(-weights.w - 1.); + uint pairStop = pairIndex + uint(ids.w - 3u); + for (uint i = pairIndex; i < pairStop; ++i) { + ivec2 texcoord = ivec2(i % MAX_SKINNING_BUFFER_WIDTH, i / MAX_SKINNING_BUFFER_WIDTH); + vec2 indexWeight = texelFetch(bonesBuffer_indicesAndWeights, texcoord, 0).rg; + posSum += mulBoneVertex(p, uint(indexWeight.r)) * indexWeight.g; + } + p = posSum; } -void skinPosition(inout vec3 p, const uvec4 ids, const vec4 weights) { - p = mulBoneVertex(p, ids.x) * weights.x - + mulBoneVertex(p, ids.y) * weights.y - + mulBoneVertex(p, ids.z) * weights.z - + mulBoneVertex(p, ids.w) * weights.w; +void skinNormal(inout vec3 n, const uvec4 ids, const vec4 weights) { + // standard skinning for 4 weights, some of them could be zero + if (weights.w >= 0.0) { + n = weights.x * mulBoneNormal(n, uint(ids.x)) + + weights.y * mulBoneNormal(n, uint(ids.y)) + + weights.z * mulBoneNormal(n, uint(ids.z)) + + weights.w * mulBoneNormal(n, uint(ids.w)); + return; + } + // skinning for >4 weights + vec3 normSum = weights.x * mulBoneNormal(n, uint(ids.x)); + normSum += weights.y * mulBoneNormal(n, uint(ids.y)); + normSum += weights.z * mulBoneNormal(n, uint(ids.z)); + uint pairIndex = uint(-weights.w - 1.); + uint pairStop = pairIndex + uint(ids.w - 3u); + for (uint i = pairIndex; i < pairStop; i = i + 1u) { + ivec2 texcoord = ivec2(i % MAX_SKINNING_BUFFER_WIDTH, i / MAX_SKINNING_BUFFER_WIDTH); + vec2 indexWeight = texelFetch(bonesBuffer_indicesAndWeights, texcoord, 0).rg; + + normSum += mulBoneNormal(n, uint(indexWeight.r)) * indexWeight.g; + } + n = normSum; +} + +void skinNormalTangent(inout vec3 n, inout vec3 t, const uvec4 ids, const vec4 weights) { + // standard skinning for 4 weights, some of them could be zero + if (weights.w >= 0.0) { + n = weights.x * mulBoneNormal(n, uint(ids.x)) + + weights.y * mulBoneNormal(n, uint(ids.y)) + + weights.z * mulBoneNormal(n, uint(ids.z)) + + weights.w * mulBoneNormal(n, uint(ids.w)); + t = weights.x * mulBoneNormal(t, uint(ids.x)) + + weights.y * mulBoneNormal(t, uint(ids.y)) + + weights.z * mulBoneNormal(t, uint(ids.z)) + + weights.w * mulBoneNormal(t, uint(ids.w)); + return; + } + // skinning for >4 weights + vec3 normSum = weights.x * mulBoneNormal(n, uint(ids.x)); + normSum += weights.y * mulBoneNormal(n, uint(ids.y)) ; + normSum += weights.z * mulBoneNormal(n, uint(ids.z)); + vec3 tangSum = weights.x * mulBoneNormal(t, uint(ids.x)); + tangSum += weights.y * mulBoneNormal(t, uint(ids.y)); + tangSum += weights.z * mulBoneNormal(t, uint(ids.z)); + uint pairIndex = uint(-weights.w - 1.); + uint pairStop = pairIndex + uint(ids.w - 3u); + for (uint i = pairIndex; i < pairStop; i = i + 1u) { + ivec2 texcoord = ivec2(i % MAX_SKINNING_BUFFER_WIDTH, i / MAX_SKINNING_BUFFER_WIDTH); + vec2 indexWeight = texelFetch(bonesBuffer_indicesAndWeights, texcoord, 0).rg; + + normSum += mulBoneNormal(n, uint(indexWeight.r)) * indexWeight.g; + tangSum += mulBoneNormal(t, uint(indexWeight.r)) * indexWeight.g; + } + n = normSum; + t = tangSum; } #define MAX_MORPH_TARGET_BUFFER_WIDTH 2048 diff --git a/shaders/src/main.vs b/shaders/src/main.vs index 8183b23519..a52bc527d1 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -81,8 +81,7 @@ void main() { } if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0) { - skinNormal(material.worldNormal, mesh_bone_indices, mesh_bone_weights); - skinNormal(vertex_worldTangent.xyz, mesh_bone_indices, mesh_bone_weights); + skinNormalTangent(material.worldNormal, vertex_worldTangent.xyz, mesh_bone_indices, mesh_bone_weights); } #endif