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