diff --git a/CMakeLists.txt b/CMakeLists.txt index dd4740c..963cecd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,7 @@ target_sources(vk-gltf-viewer PRIVATE interface/gltf/AssetSceneGpuBuffers.cppm interface/gltf/AssetSceneHierarchy.cppm interface/gltf/MaterialVariantsMapping.cppm + interface/gltf/MeshWeights.cppm interface/helpers/AggregateHasher.cppm interface/helpers/concepts.cppm interface/helpers/fastgltf.cppm diff --git a/impl/MainApp.cpp b/impl/MainApp.cpp index 91197ad..1872a83 100644 --- a/impl/MainApp.cpp +++ b/impl/MainApp.cpp @@ -657,7 +657,7 @@ vk_gltf_viewer::MainApp::Gltf::Gltf( gpu { gpu }, assetGpuBuffers { asset, gpu, threadPool, assetExternalBuffers }, assetGpuTextures { asset, directory, gpu, threadPool, assetExternalBuffers }, - sceneGpuBuffers { asset, scene, sceneHierarchy, gpu, assetExternalBuffers }, + sceneGpuBuffers { asset, scene, sceneHierarchy, meshWeights, gpu, assetExternalBuffers }, sceneMiniball { gltf::algorithm::getMiniball(asset, scene, [this](std::size_t nodeIndex, std::size_t instanceIndex) { return cast(sceneGpuBuffers.getMeshNodeWorldTransform(nodeIndex, instanceIndex)); }) } { } @@ -665,7 +665,7 @@ vk_gltf_viewer::MainApp::Gltf::Gltf( void vk_gltf_viewer::MainApp::Gltf::setScene(std::size_t sceneIndex) { scene = asset.scenes[sceneIndex]; sceneHierarchy = { asset, scene }; - sceneGpuBuffers = { asset, scene, sceneHierarchy, gpu, assetExternalBuffers }; + sceneGpuBuffers = { asset, scene, sceneHierarchy, meshWeights, gpu, assetExternalBuffers }; sceneMiniball = gltf::algorithm::getMiniball(asset, scene, [this](std::size_t nodeIndex, std::size_t instanceIndex) { return cast(sceneGpuBuffers.getMeshNodeWorldTransform(nodeIndex, instanceIndex)); }); diff --git a/impl/gltf/AssetGpuBuffers.cpp b/impl/gltf/AssetGpuBuffers.cpp index 7a026e8..64b1030 100644 --- a/impl/gltf/AssetGpuBuffers.cpp +++ b/impl/gltf/AssetGpuBuffers.cpp @@ -7,6 +7,7 @@ module vk_gltf_viewer; import :gltf.AssetGpuBuffers; import std; +import :helpers.concepts import :helpers.fastgltf; import :helpers.functional; import :helpers.ranges; @@ -163,38 +164,20 @@ std::variant vk_gltf_viewer::gltf::Asse .pPositionMorphTargetAttributeMappingInfoBuffer = primitiveInfo.positionMorphTargetInfos.pMappingBuffer, .pTexcoordAttributeMappingInfoBuffer = primitiveInfo.texcoordsInfo.pMappingBuffer, .positionByteStride = primitiveInfo.positionInfo.byteStride, - .morphTargetCount = std::numeric_limits::max(), .materialIndex = primitiveInfo.materialIndex.transform(padMaterialIndex).value_or(0U), }; - if (primitiveInfo.positionMorphTargetInfos.pMappingBuffer) { - gpuPrimitive.pPositionMorphTargetAttributeMappingInfoBuffer - = primitiveInfo.positionMorphTargetInfos.pMappingBuffer; - gpuPrimitive.morphTargetCount = std::min( - gpuPrimitive.morphTargetCount, - primitiveInfo.positionMorphTargetInfos.attributeInfos.size()); - } if (primitiveInfo.normalInfo) { gpuPrimitive.pNormalBuffer = primitiveInfo.normalInfo->address; gpuPrimitive.normalByteStride = primitiveInfo.normalInfo->byteStride; - if (primitiveInfo.normalMorphTargetInfos.pMappingBuffer) { - gpuPrimitive.pPositionMorphTargetAttributeMappingInfoBuffer - = primitiveInfo.positionMorphTargetInfos.pMappingBuffer; - gpuPrimitive.morphTargetCount = std::min( - gpuPrimitive.morphTargetCount, - primitiveInfo.normalMorphTargetInfos.attributeInfos.size()); - } + gpuPrimitive.pNormalMorphTargetAttributeMappingInfoBuffer + = primitiveInfo.normalMorphTargetInfos.pMappingBuffer; } if (primitiveInfo.tangentInfo) { gpuPrimitive.pTangentBuffer = primitiveInfo.tangentInfo->address; gpuPrimitive.tangentByteStride = primitiveInfo.tangentInfo->byteStride; - if (primitiveInfo.tangentMorphTargetInfos.pMappingBuffer) { - gpuPrimitive.pPositionMorphTargetAttributeMappingInfoBuffer - = primitiveInfo.positionMorphTargetInfos.pMappingBuffer; - gpuPrimitive.morphTargetCount = std::min( - gpuPrimitive.morphTargetCount, - primitiveInfo.tangentMorphTargetInfos.attributeInfos.size()); - } + gpuPrimitive.pTangentMorphTargetAttributeMappingInfoBuffer + = primitiveInfo.tangentMorphTargetInfos.pMappingBuffer; } if (primitiveInfo.colorInfo) { gpuPrimitive.pColorBuffer = primitiveInfo.colorInfo->address; diff --git a/impl/gltf/AssetSceneGpuBuffers.cpp b/impl/gltf/AssetSceneGpuBuffers.cpp index 5e8557c..a1a3d5e 100644 --- a/impl/gltf/AssetSceneGpuBuffers.cpp +++ b/impl/gltf/AssetSceneGpuBuffers.cpp @@ -44,13 +44,24 @@ std::vector vk_gltf_viewer::gltf::AssetSceneGpuBuffers::createIns return result; } -vku::AllocatedBuffer vk_gltf_viewer::gltf::AssetSceneGpuBuffers::createNodeBuffer(const vulkan::Gpu &gpu) const { +vku::AllocatedBuffer vk_gltf_viewer::gltf::AssetSceneGpuBuffers::createNodeBuffer( + const fastgltf::Asset &asset, + const MeshWeights &meshWeights, + const vulkan::Gpu &gpu +) const { const vk::DeviceAddress nodeTransformBufferStartAddress = gpu.device.getBufferAddress({ meshNodeWorldTransformBuffer }); vku::AllocatedBuffer stagingBuffer = vku::MappedBuffer { gpu.allocator, - std::from_range, instanceOffsets | std::views::transform([=](std::uint32_t offset) { - return nodeTransformBufferStartAddress + sizeof(fastgltf::math::fmat4x4) * offset; + std::from_range, ranges::views::upto(asset.nodes.size()) | std::views::transform([&](std::size_t nodeIndex) { + return std::array { + to_optional(asset.nodes[nodeIndex].meshIndex) + .transform([&](std::size_t meshIndex) { + return meshWeights.segments[meshIndex].startAddress; + }) + .value_or(vk::DeviceAddress { 0 }), + nodeTransformBufferStartAddress + sizeof(fastgltf::math::fmat4x4) * instanceOffsets[nodeIndex], + }; }), gpu.isUmaDevice ? vk::BufferUsageFlagBits::eStorageBuffer : vk::BufferUsageFlagBits::eTransferSrc, }.unmap(); diff --git a/interface/MainApp.cppm b/interface/MainApp.cppm index b5e4c7c..e027d74 100644 --- a/interface/MainApp.cppm +++ b/interface/MainApp.cppm @@ -10,6 +10,7 @@ import :gltf.AssetGpuFallbackTexture; import :gltf.AssetSceneGpuBuffers; import :gltf.AssetSceneHierarchy; import :gltf.MaterialVariantsMapping; +import :gltf.MeshWeights; import :vulkan.dsl.Asset; import :vulkan.dsl.ImageBasedLighting; import :vulkan.dsl.Scene; @@ -72,6 +73,8 @@ namespace vk_gltf_viewer { gltf::AssetGpuBuffers assetGpuBuffers; gltf::AssetGpuTextures assetGpuTextures; + gltf::MeshWeights meshWeights { asset, gpu }; + /** * @brief The glTF scene that is currently used by. * diff --git a/interface/gltf/AssetGpuBuffers.cppm b/interface/gltf/AssetGpuBuffers.cppm index 7e6ea9b..aeaf066 100644 --- a/interface/gltf/AssetGpuBuffers.cppm +++ b/interface/gltf/AssetGpuBuffers.cppm @@ -165,8 +165,8 @@ namespace vk_gltf_viewer::gltf { std::uint8_t colorComponentType; std::uint8_t colorComponentCount; char _padding0_[2]; - std::uint32_t morphTargetCount; std::uint32_t materialIndex; + char _padding1_[4]; }; static_assert(sizeof(GpuPrimitive) == 80); static_assert(offsetof(GpuPrimitive, positionByteStride) == 64); @@ -175,8 +175,7 @@ namespace vk_gltf_viewer::gltf { static_assert(offsetof(GpuPrimitive, colorByteStride) == 67); static_assert(offsetof(GpuPrimitive, colorComponentType) == 68); static_assert(offsetof(GpuPrimitive, colorComponentCount) == 69); - static_assert(offsetof(GpuPrimitive, morphTargetCount) == 72); - static_assert(offsetof(GpuPrimitive, materialIndex) == 76); + static_assert(offsetof(GpuPrimitive, materialIndex) == 72); std::unordered_map primitiveInfos = createPrimitiveInfos(); diff --git a/interface/gltf/AssetSceneGpuBuffers.cppm b/interface/gltf/AssetSceneGpuBuffers.cppm index 44351c0..b624f01 100644 --- a/interface/gltf/AssetSceneGpuBuffers.cppm +++ b/interface/gltf/AssetSceneGpuBuffers.cppm @@ -5,6 +5,7 @@ export import fastgltf; import :gltf.algorithm.traversal; export import :gltf.AssetPrimitiveInfo; export import :gltf.AssetSceneHierarchy; +export import :gltf.MeshWeights; import :helpers.concepts; import :helpers.fastgltf; import :helpers.functional; @@ -49,12 +50,13 @@ namespace vk_gltf_viewer::gltf { const fastgltf::Asset &asset [[clang::lifetimebound]], const fastgltf::Scene &scene [[clang::lifetimebound]], const AssetSceneHierarchy &sceneHierarchy, + const MeshWeights &meshWeights, const vulkan::Gpu &gpu [[clang::lifetimebound]], const BufferDataAdapter &adapter = {} ) : pAsset { &asset }, instanceCounts { createInstanceCounts(scene) }, meshNodeWorldTransformBuffer { createMeshNodeWorldTransformBuffer(scene, sceneHierarchy, gpu.allocator, adapter) }, - nodeBuffer { createNodeBuffer(gpu) } { } + nodeBuffer { createNodeBuffer(asset, meshWeights, gpu) } { } /** * @brief Get world transform matrix of \p nodeIndex-th mesh node's \p instanceIndex-th instance in the scene. @@ -215,6 +217,10 @@ namespace vk_gltf_viewer::gltf { }; } - [[nodiscard]] vku::AllocatedBuffer createNodeBuffer(const vulkan::Gpu &gpu) const; + [[nodiscard]] vku::AllocatedBuffer createNodeBuffer( + const fastgltf::Asset &asset, + const MeshWeights &meshWeights, + const vulkan::Gpu &gpu + ) const; }; } \ No newline at end of file diff --git a/interface/gltf/MeshWeights.cppm b/interface/gltf/MeshWeights.cppm new file mode 100644 index 0000000..730d7b2 --- /dev/null +++ b/interface/gltf/MeshWeights.cppm @@ -0,0 +1,107 @@ +export module vk_gltf_viewer:gltf.MeshWeights; + +import std; +export import fastgltf; +import :helpers.ranges; +export import :vulkan.Gpu; + +#define FWD(...) static_cast(__VA_ARGS__) +#define LIFT(...) [&](auto &&...xs) { return __VA_ARGS__(FWD(xs)...); } + +template +class writeonly { + T volatile *addr; + +public: + explicit writeonly(std::uintptr_t addr) + : addr { reinterpret_cast(addr) } { } + + void operator=(const T &t) volatile { *addr = t; } + + [[nodiscard]] std::uintptr_t address() const noexcept { return addr; } +}; + +/** + * @brief Create a combined buffer from given segments (a range of byte data) and return each segments' start offsets. + * + * Example: Two segments { 0xAA, 0xBB, 0xCC } and { 0xDD, 0xEE } will be combined to { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }, and their start offsets are { 0, 3 }. + * + * @tparam R Range type of data segments. + * @param allocator VMA allocator to allocate the buffer. + * @param segments Range of data segments. Each segment will be converted to std::span, therefore segment's elements must be trivially copyable. + * @param usage Usage flags of the result buffer. + * @return Pair of buffer and each segments' start offsets vector. + */ +template + requires ( + std::ranges::sized_range> + && std::same_as>, std::byte>) +[[nodiscard]] std::pair> createCombinedBuffer( + vma::Allocator allocator, + R &&segments, + vk::BufferUsageFlags usage +) { + // Calculate each segments' copy destination offsets. + std::vector copyOffsets { std::from_range, segments | std::views::transform(LIFT(std::size)) }; + vk::DeviceSize sizeTotal = copyOffsets.back(); + std::exclusive_scan(copyOffsets.begin(), copyOffsets.end(), copyOffsets.begin(), vk::DeviceSize { 0 }); + sizeTotal += copyOffsets.back(); + + if (sizeTotal == 0) { + throw std::invalid_argument { "No data to write" }; + } + + // Create buffer. + vku::MappedBuffer buffer { allocator, vk::BufferCreateInfo { {}, sizeTotal, usage } }; + + // Copy segments to the buffer. + std::byte *mapped = static_cast(buffer.data); + for (auto &&segment : FWD(segments)) { + mapped = std::ranges::copy(FWD(segment), mapped).out; + } + + return { std::move(buffer), std::move(copyOffsets) }; +} + +namespace vk_gltf_viewer::gltf { + class MeshWeights { + public: + struct Segment { + vk::DeviceAddress startAddress; + writeonly count; + std::span> weights; + }; + + std::vector segments; + + MeshWeights(const fastgltf::Asset &asset, const vulkan::Gpu &gpu [[clang::lifetimebound]]) + : buffer { createBuffer(asset, gpu) } { } + + private: + vku::MappedBuffer buffer; + + [[nodiscard]] vku::MappedBuffer createBuffer(const fastgltf::Asset &asset, const vulkan::Gpu &gpu) { + auto [buffer, copyOffsets] = createCombinedBuffer( + gpu.allocator, + asset.meshes | std::views::transform([](const fastgltf::Mesh &mesh) { + // [count, weight0, weight1, ..., weight(count-1)] + return ranges::views::concat( + std::bit_cast>(static_cast(mesh.weights.size())), + as_bytes(std::span { mesh.weights })); + }), + vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eShaderDeviceAddress); + + segments.reserve(copyOffsets.size()); + const vk::DeviceAddress bufferAddress = gpu.device.getBufferAddress({ buffer }); + for (const auto &[mesh, copyOffset] : std::views::zip(asset.meshes, copyOffsets)) { + const std::uintptr_t segmentStart = reinterpret_cast(buffer.data) + copyOffset; + segments.emplace_back( + bufferAddress + copyOffset, + writeonly { segmentStart }, + std::span { reinterpret_cast*>(segmentStart + sizeof(float)), mesh.weights.size() }); + } + + return std::move(buffer); + } + }; +} \ No newline at end of file diff --git a/shaders/depth.vert b/shaders/depth.vert index 73d8b70..1006e51 100644 --- a/shaders/depth.vert +++ b/shaders/depth.vert @@ -12,7 +12,13 @@ #include "indexing.glsl" #include "types.glsl" -layout (std430, buffer_reference, buffer_reference_align = 64) readonly buffer Node { mat4 transforms[]; }; +layout (buffer_reference, buffer_reference_align = 4) readonly buffer MeshWeights { uint count; float[] weights; }; +layout (buffer_reference, buffer_reference_align = 64) readonly buffer InstanceTransforms { mat4 data[]; }; + +struct Node { + MeshWeights meshWeights; + InstanceTransforms instanceTransforms; +}; layout (location = 0) flat out uint outNodeIndex; diff --git a/shaders/indexing.glsl b/shaders/indexing.glsl index eb41dc2..e12acee 100644 --- a/shaders/indexing.glsl +++ b/shaders/indexing.glsl @@ -10,7 +10,7 @@ #define PRIMITIVE_INDEX gl_BaseInstance & 0xFFFFU #define PRIMITIVE primitives[PRIMITIVE_INDEX] #define NODE_INDEX gl_BaseInstance >> 16U -#define TRANSFORM Node(nodes[NODE_INDEX]).transforms[gl_InstanceIndex - gl_BaseInstance] +#define TRANSFORM nodes[NODE_INDEX].instanceTransforms.data[gl_InstanceIndex - gl_BaseInstance] #define MATERIAL_INDEX PRIMITIVE.materialIndex #define MATERIAL materials[MATERIAL_INDEX] diff --git a/shaders/jump_flood_seed.vert b/shaders/jump_flood_seed.vert index 4203169..88759df 100644 --- a/shaders/jump_flood_seed.vert +++ b/shaders/jump_flood_seed.vert @@ -12,7 +12,13 @@ #include "indexing.glsl" #include "types.glsl" -layout (std430, buffer_reference, buffer_reference_align = 64) readonly buffer Node { mat4 transforms[]; }; +layout (buffer_reference, buffer_reference_align = 4) readonly buffer MeshWeights { uint count; float[] weights; }; +layout (buffer_reference, buffer_reference_align = 64) readonly buffer InstanceTransforms { mat4 data[]; }; + +struct Node { + MeshWeights meshWeights; + InstanceTransforms instanceTransforms; +}; layout (set = 0, binding = 0) readonly buffer PrimitiveBuffer { Primitive primitives[]; diff --git a/shaders/mask_depth.vert b/shaders/mask_depth.vert index e896b90..effa7dd 100644 --- a/shaders/mask_depth.vert +++ b/shaders/mask_depth.vert @@ -14,7 +14,13 @@ #define HAS_VARIADIC_OUT HAS_BASE_COLOR_TEXTURE || HAS_COLOR_ALPHA_ATTRIBUTE -layout (std430, buffer_reference, buffer_reference_align = 64) readonly buffer Node { mat4 transforms[]; }; +layout (buffer_reference, buffer_reference_align = 4) readonly buffer MeshWeights { uint count; float[] weights; }; +layout (buffer_reference, buffer_reference_align = 64) readonly buffer InstanceTransforms { mat4 data[]; }; + +struct Node { + MeshWeights meshWeights; + InstanceTransforms instanceTransforms; +}; layout (location = 0) flat out uint outNodeIndex; layout (location = 1) flat out uint outMaterialIndex; diff --git a/shaders/mask_jump_flood_seed.vert b/shaders/mask_jump_flood_seed.vert index 1c38469..4619615 100644 --- a/shaders/mask_jump_flood_seed.vert +++ b/shaders/mask_jump_flood_seed.vert @@ -14,7 +14,13 @@ #define HAS_VARIADIC_OUT HAS_BASE_COLOR_TEXTURE || HAS_COLOR_ALPHA_ATTRIBUTE -layout (std430, buffer_reference, buffer_reference_align = 64) readonly buffer Node { mat4 transforms[]; }; +layout (buffer_reference, buffer_reference_align = 4) readonly buffer MeshWeights { uint count; float[] weights; }; +layout (buffer_reference, buffer_reference_align = 64) readonly buffer InstanceTransforms { mat4 data[]; }; + +struct Node { + MeshWeights meshWeights; + InstanceTransforms instanceTransforms; +}; layout (location = 0) flat out uint outMaterialIndex; #if HAS_VARIADIC_OUT diff --git a/shaders/primitive.vert b/shaders/primitive.vert index aa3f812..f68ed6f 100644 --- a/shaders/primitive.vert +++ b/shaders/primitive.vert @@ -14,7 +14,13 @@ #define HAS_VARIADIC_OUT !FRAGMENT_SHADER_GENERATED_TBN || TEXCOORD_COUNT >= 1 || HAS_COLOR_ATTRIBUTE -layout (std430, buffer_reference, buffer_reference_align = 64) readonly buffer Node { mat4 transforms[]; }; +layout (buffer_reference, buffer_reference_align = 4) readonly buffer MeshWeights { uint count; float[] weights; }; +layout (buffer_reference, buffer_reference_align = 64) readonly buffer InstanceTransforms { mat4 data[]; }; + +struct Node { + MeshWeights meshWeights; + InstanceTransforms instanceTransforms; +}; layout (location = 0) out vec3 outPosition; layout (location = 1) flat out uint outMaterialIndex; diff --git a/shaders/types.glsl b/shaders/types.glsl index a811a0e..87fafae 100644 --- a/shaders/types.glsl +++ b/shaders/types.glsl @@ -63,8 +63,8 @@ struct Primitive { uint8_t colorComponentType; uint8_t colorComponentCount; uint8_t _padding0_[2]; - uint morphTargetCount; uint materialIndex; + float _padding1_; }; #endif diff --git a/shaders/unlit_primitive.vert b/shaders/unlit_primitive.vert index 5878fe7..1513f42 100644 --- a/shaders/unlit_primitive.vert +++ b/shaders/unlit_primitive.vert @@ -14,7 +14,13 @@ #define HAS_VARIADIC_OUT HAS_BASE_COLOR_TEXTURE || HAS_COLOR_ATTRIBUTE -layout (std430, buffer_reference, buffer_reference_align = 64) readonly buffer Node { mat4 transforms[]; }; +layout (buffer_reference, buffer_reference_align = 4) readonly buffer MeshWeights { uint count; float[] weights; }; +layout (buffer_reference, buffer_reference_align = 64) readonly buffer InstanceTransforms { mat4 data[]; }; + +struct Node { + MeshWeights meshWeights; + InstanceTransforms instanceTransforms; +}; layout (location = 0) flat out uint outMaterialIndex; #if HAS_VARIADIC_OUT diff --git a/shaders/vertex_pulling.glsl b/shaders/vertex_pulling.glsl index 58e47f9..a2fcc0e 100644 --- a/shaders/vertex_pulling.glsl +++ b/shaders/vertex_pulling.glsl @@ -24,9 +24,10 @@ vec3 getPosition() { vec3 position = Vec3Ref(PRIMITIVE.pPositionBuffer + uint(PRIMITIVE.positionByteStride) * uint(gl_VertexIndex)).data; // Morph target computation. if (uint64_t(PRIMITIVE.positionMorphTargetAttributeMappingInfos) != 0) { - for (uint i = 0U; i < PRIMITIVE.morphTargetCount; ++i) { + MeshWeights meshWeights = nodes[NODE_INDEX].meshWeights; + for (uint i = 0U; i < meshWeights.count; ++i) { IndexedAttributeMappingInfo mappingInfo = PRIMITIVE.positionMorphTargetAttributeMappingInfos.data[i]; - position += /* TODO: weight */ Vec3Ref(mappingInfo.bytesPtr + mappingInfo.stride * uint(gl_VertexIndex)).data; + position += meshWeights.weights[i] * Vec3Ref(mappingInfo.bytesPtr + mappingInfo.stride * uint(gl_VertexIndex)).data; } } return position; @@ -36,9 +37,10 @@ vec3 getNormal() { vec3 normal = Vec3Ref(PRIMITIVE.pNormalBuffer + uint(PRIMITIVE.normalByteStride) * uint(gl_VertexIndex)).data; // Morph target computation. if (uint64_t(PRIMITIVE.normalMorphTargetAttributeMappingInfos) != 0) { - for (uint i = 0U; i < PRIMITIVE.morphTargetCount; ++i) { + MeshWeights meshWeights = nodes[NODE_INDEX].meshWeights; + for (uint i = 0U; i < meshWeights.count; ++i) { IndexedAttributeMappingInfo mappingInfo = PRIMITIVE.normalMorphTargetAttributeMappingInfos.data[i]; - normal += /* TODO: weight */ Vec3Ref(mappingInfo.bytesPtr + mappingInfo.stride * uint(gl_VertexIndex)).data; + normal += meshWeights.weights[i] * Vec3Ref(mappingInfo.bytesPtr + mappingInfo.stride * uint(gl_VertexIndex)).data; } } return normal; @@ -48,9 +50,10 @@ vec4 getTangent() { vec4 tangent = Vec4Ref(PRIMITIVE.pTangentBuffer + uint(PRIMITIVE.tangentByteStride) * uint(gl_VertexIndex)).data; // Morph target computation. if (uint64_t(PRIMITIVE.tangentMorphTargetAttributeMappingInfos) != 0) { - for (uint i = 0U; i < PRIMITIVE.morphTargetCount; ++i) { + MeshWeights meshWeights = nodes[NODE_INDEX].meshWeights; + for (uint i = 0U; i < meshWeights.count; ++i) { IndexedAttributeMappingInfo mappingInfo = PRIMITIVE.tangentMorphTargetAttributeMappingInfos.data[i]; - tangent.xyz += /* TODO: weight */ Vec3Ref(mappingInfo.bytesPtr + mappingInfo.stride * uint(gl_VertexIndex)).data; + tangent.xyz += meshWeights.weights[i] * Vec3Ref(mappingInfo.bytesPtr + mappingInfo.stride * uint(gl_VertexIndex)).data; } } return tangent;