no message
This commit is contained in:
1485
cocos/3d/assets/Mesh.cpp
Normal file
1485
cocos/3d/assets/Mesh.cpp
Normal file
File diff suppressed because it is too large
Load Diff
500
cocos/3d/assets/Mesh.h
Normal file
500
cocos/3d/assets/Mesh.h
Normal file
@@ -0,0 +1,500 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "3d/assets/Morph.h"
|
||||
#include "3d/assets/MorphRendering.h"
|
||||
#include "base/std/optional.h"
|
||||
#include "core/assets/Asset.h"
|
||||
#include "core/geometry/AABB.h"
|
||||
#include "math/Mat4.h"
|
||||
#include "math/Vec3.h"
|
||||
#include "primitive/PrimitiveDefine.h"
|
||||
#include "renderer/gfx-base/GFXDef.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class Skeleton;
|
||||
class RenderingSubMesh;
|
||||
|
||||
/**
|
||||
* @en Mesh asset
|
||||
* @zh 网格资源。
|
||||
*/
|
||||
class Mesh : public Asset {
|
||||
public:
|
||||
using Super = Asset;
|
||||
|
||||
using IBufferView = IMeshBufferView;
|
||||
|
||||
/**
|
||||
* @en Vertex bundle, it describes a set of interleaved vertex attributes and their values.
|
||||
* @zh 顶点块。顶点块描述了一组**交错排列**(interleaved)的顶点属性并存储了顶点属性的实际数据。<br>
|
||||
* 交错排列是指在实际数据的缓冲区中,每个顶点的所有属性总是依次排列,并总是出现在下一个顶点的所有属性之前。
|
||||
*/
|
||||
struct IVertexBundle {
|
||||
ccstd::optional<uint8_t> _padding; // NOTE: avoid jsb cache map
|
||||
/**
|
||||
* @en The actual value for all vertex attributes.
|
||||
* You must use DataView to access the data.
|
||||
* @zh 所有顶点属性的实际数据块。
|
||||
* 你必须使用 DataView 来读取数据。
|
||||
* 因为不能保证所有属性的起始偏移都按 TypedArray 要求的字节对齐。
|
||||
*/
|
||||
IBufferView view;
|
||||
|
||||
/**
|
||||
* @en All attributes included in the bundle
|
||||
* @zh 包含的所有顶点属性。
|
||||
*/
|
||||
gfx::AttributeList attributes;
|
||||
};
|
||||
|
||||
struct IMeshCluster {
|
||||
IBufferView clusterView;
|
||||
IBufferView triangleView;
|
||||
IBufferView vertexView;
|
||||
IBufferView coneView;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en Sub mesh contains a list of primitives with the same type (Point, Line or Triangle)
|
||||
* @zh 子网格。子网格由一系列相同类型的图元组成(例如点、线、面等)。
|
||||
*/
|
||||
struct ISubMesh {
|
||||
/**
|
||||
* @en The vertex bundle references used by the sub mesh.
|
||||
* @zh 此子网格引用的顶点块,索引至网格的顶点块数组。
|
||||
*/
|
||||
ccstd::vector<uint32_t> vertexBundelIndices;
|
||||
|
||||
/**
|
||||
* @en The primitive mode of the sub mesh
|
||||
* @zh 此子网格的图元类型。
|
||||
*/
|
||||
gfx::PrimitiveMode primitiveMode;
|
||||
|
||||
/**
|
||||
* @en The index data of the sub mesh
|
||||
* @zh 此子网格使用的索引数据。
|
||||
*/
|
||||
ccstd::optional<IBufferView> indexView;
|
||||
|
||||
/**
|
||||
* @en The joint map index in [[IStruct.jointMaps]]. Could be absent
|
||||
* @zh 此子网格使用的关节索引映射表在 [[IStruct.jointMaps]] 中的索引。
|
||||
* 如未定义或指向的映射表不存在,则默认 VB 内所有关节索引数据直接对应骨骼资源数据。
|
||||
*/
|
||||
ccstd::optional<uint32_t> jointMapIndex;
|
||||
|
||||
ccstd::optional<IMeshCluster> cluster;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en The info use to create dynamic mesh.
|
||||
* @zh 描述了创建动态网格需要的预分配信息。
|
||||
*/
|
||||
struct IDynamicInfo {
|
||||
/**
|
||||
* @en max submesh count
|
||||
* @zh 最大子模型个数。
|
||||
*/
|
||||
uint32_t maxSubMeshes{0U};
|
||||
|
||||
/**
|
||||
* @en max submesh vertex count
|
||||
* @zh 子模型最大顶点个数。
|
||||
*/
|
||||
uint32_t maxSubMeshVertices{0U};
|
||||
|
||||
/**
|
||||
* @en max submesh index count
|
||||
* @zh 子模型最大索引个数。
|
||||
*/
|
||||
uint32_t maxSubMeshIndices{0U};
|
||||
};
|
||||
|
||||
/**
|
||||
* @en The structure use to create dynamic mesh.
|
||||
* @zh 描述了创建动态网格的结构。
|
||||
*/
|
||||
struct IDynamicStruct {
|
||||
/**
|
||||
* @en dynamic mesh info
|
||||
* @zh 动态模型信息。
|
||||
*/
|
||||
IDynamicInfo info;
|
||||
|
||||
/**
|
||||
* @en dynamic submesh bounds
|
||||
* @zh 动态子模型包围盒。
|
||||
*/
|
||||
ccstd::vector<geometry::AABB> bounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en The structure of the mesh
|
||||
* @zh 描述了网格的结构。
|
||||
*/
|
||||
struct IStruct {
|
||||
/**
|
||||
* @en All vertex bundles of the mesh
|
||||
* @zh 此网格所有的顶点块。
|
||||
*/
|
||||
ccstd::vector<IVertexBundle> vertexBundles;
|
||||
|
||||
/**
|
||||
* @en All sub meshes
|
||||
* @zh 此网格的所有子网格。
|
||||
*/
|
||||
ccstd::vector<ISubMesh> primitives;
|
||||
|
||||
/**
|
||||
* @en The minimum position of all vertices in the mesh
|
||||
* @zh (各分量都)小于等于此网格任何顶点位置的最大位置。
|
||||
*/
|
||||
ccstd::optional<Vec3> minPosition;
|
||||
inline const ccstd::optional<Vec3> &getMinPosition() const { return minPosition; } // For JSB binding only
|
||||
inline void setMinPosition(const ccstd::optional<Vec3> &v) { minPosition = v; } // For JSB binding only
|
||||
|
||||
/**
|
||||
* @en The maximum position of all vertices in the mesh
|
||||
* @zh (各分量都)大于等于此网格任何顶点位置的最小位置。
|
||||
*/
|
||||
ccstd::optional<Vec3> maxPosition;
|
||||
inline const ccstd::optional<Vec3> &getMaxPosition() const { return maxPosition; } // For JSB binding only
|
||||
inline void setMaxPosition(const ccstd::optional<Vec3> &v) { maxPosition = v; } // For JSB binding only
|
||||
|
||||
/**
|
||||
* @en The joint index map list.
|
||||
* @zh 此网格使用的关节索引映射关系列表,数组长度应为子模型中实际使用到的所有关节,
|
||||
* 每个元素都对应一个原骨骼资源里的索引,按子模型 VB 内的实际索引排列。
|
||||
*/
|
||||
ccstd::optional<ccstd::vector<ccstd::vector<index_t>>> jointMaps;
|
||||
|
||||
/**
|
||||
* @en The morph information of the mesh
|
||||
* @zh 网格的形变数据
|
||||
*/
|
||||
ccstd::optional<Morph> morph;
|
||||
|
||||
/**
|
||||
* @en The specific data of the dynamic mesh
|
||||
* @zh 动态网格特有数据
|
||||
*/
|
||||
ccstd::optional<IDynamicStruct> dynamic;
|
||||
|
||||
ccstd::optional<bool> encoded;
|
||||
|
||||
ccstd::optional<bool> compressed;
|
||||
|
||||
ccstd::optional<bool> quantized;
|
||||
};
|
||||
|
||||
struct ICreateInfo {
|
||||
/**
|
||||
* @en Mesh structure
|
||||
* @zh 网格结构。
|
||||
*/
|
||||
IStruct structInfo;
|
||||
|
||||
/**
|
||||
* @en Mesh binary data
|
||||
* @zh 网格二进制数据。
|
||||
*/
|
||||
Uint8Array data;
|
||||
};
|
||||
|
||||
Mesh() = default;
|
||||
~Mesh() override;
|
||||
|
||||
ccstd::any getNativeAsset() const override;
|
||||
void setNativeAsset(const ccstd::any &obj) override;
|
||||
|
||||
void setAssetData(ArrayBuffer *data) {
|
||||
_data = Uint8Array(data);
|
||||
}
|
||||
|
||||
ArrayBuffer *getAssetData() const {
|
||||
return _data.buffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @en The sub meshes count of the mesh.
|
||||
* @zh 此网格的子网格数量。
|
||||
* @deprecated Please use [[renderingSubMeshes.length]] instead
|
||||
*/
|
||||
uint32_t getSubMeshCount() const;
|
||||
|
||||
/**
|
||||
* @en The minimum position of all vertices in the mesh
|
||||
* @zh (各分量都)小于等于此网格任何顶点位置的最大位置。
|
||||
* @deprecated Please use [[struct.minPosition]] instead
|
||||
*/
|
||||
const Vec3 *getMinPosition() const;
|
||||
|
||||
/**
|
||||
* @en The maximum position of all vertices in the mesh
|
||||
* @zh (各分量都)大于等于此网格任何顶点位置的最大位置。
|
||||
* @deprecated Please use [[struct.maxPosition]] instead
|
||||
*/
|
||||
const Vec3 *getMaxPosition() const;
|
||||
|
||||
/**
|
||||
* @en The struct of the mesh
|
||||
* @zh 此网格的结构。
|
||||
*/
|
||||
inline const IStruct &getStruct() const {
|
||||
return _struct;
|
||||
}
|
||||
|
||||
inline void setStruct(const IStruct &input) {
|
||||
_struct = input;
|
||||
}
|
||||
|
||||
inline Uint8Array &getData() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
inline void setData(const Uint8Array &data) {
|
||||
_data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @en The hash of the mesh
|
||||
* @zh 此网格的哈希值。
|
||||
*/
|
||||
ccstd::hash_t getHash();
|
||||
|
||||
/**
|
||||
* @en Set the hash of the mesh
|
||||
* @zh 设置此网格的哈希值。
|
||||
*/
|
||||
void setHash(ccstd::hash_t hash) { _hash = hash; }
|
||||
|
||||
using JointBufferIndicesType = ccstd::vector<index_t>;
|
||||
/**
|
||||
* The index of the joint buffer of all sub meshes in the joint map buffers
|
||||
*/
|
||||
const JointBufferIndicesType &getJointBufferIndices();
|
||||
|
||||
using RenderingSubMeshList = ccstd::vector<IntrusivePtr<RenderingSubMesh>>;
|
||||
/**
|
||||
* @en The sub meshes for rendering. Mesh could be split into different sub meshes for rendering.
|
||||
* @zh 此网格创建的渲染网格。
|
||||
*/
|
||||
inline const RenderingSubMeshList &getRenderingSubMeshes() {
|
||||
initialize();
|
||||
return _renderingSubMeshes;
|
||||
}
|
||||
|
||||
void onLoaded() override {
|
||||
initialize();
|
||||
}
|
||||
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* @en Destroy the mesh and release all related GPU resources
|
||||
* @zh 销毁此网格,并释放它占有的所有 GPU 资源。
|
||||
*/
|
||||
bool destroy() override {
|
||||
destroyRenderingMesh();
|
||||
return Super::destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @en Release all related GPU resources
|
||||
* @zh 释放此网格占有的所有 GPU 资源。
|
||||
*/
|
||||
void destroyRenderingMesh();
|
||||
|
||||
/**
|
||||
* @en Reset the struct and data of the mesh
|
||||
* @zh 重置此网格的结构和数据。
|
||||
* @param struct The new struct
|
||||
* @param data The new data
|
||||
* @deprecated Will be removed in v3.0.0, please use [[reset]] instead
|
||||
*/
|
||||
void assign(const IStruct &structInfo, const Uint8Array &data);
|
||||
|
||||
/**
|
||||
* @en Reset the mesh with mesh creation information
|
||||
* @zh 重置此网格。
|
||||
* @param info Mesh creation information including struct and data
|
||||
*/
|
||||
void reset(ICreateInfo &&info);
|
||||
|
||||
using BoneSpaceBounds = ccstd::vector<IntrusivePtr<geometry::AABB>>;
|
||||
/**
|
||||
* @en Get [[AABB]] bounds in the skeleton's bone space
|
||||
* @zh 获取骨骼变换空间内下的 [[AABB]] 包围盒
|
||||
* @param skeleton
|
||||
*/
|
||||
BoneSpaceBounds getBoneSpaceBounds(Skeleton *skeleton);
|
||||
|
||||
/**
|
||||
* @en Merge the given mesh into the current mesh
|
||||
* @zh 合并指定的网格到此网格中。
|
||||
* @param mesh The mesh to be merged
|
||||
* @param worldMatrix The world matrix of the given mesh
|
||||
* @param [validate=false] Whether to validate the mesh
|
||||
* @returns Check the mesh state and return the validation result.
|
||||
*/
|
||||
bool merge(Mesh *mesh, const Mat4 *worldMatrix = nullptr, bool validate = false);
|
||||
|
||||
/**
|
||||
* @en Validation for whether the given mesh can be merged into the current mesh.
|
||||
* To pass the validation, it must satisfy either of these two requirements:
|
||||
* - When the current mesh have no data
|
||||
* - When the two mesh have the same vertex bundle count, the same sub meshes count, and the same sub mesh layout.
|
||||
*
|
||||
* Same mesh layout means:
|
||||
* - They have the same primitive type and reference to the same amount vertex bundle with the same indices.
|
||||
* - And they all have or don't have index view
|
||||
* @zh 验证指定网格是否可以合并至当前网格。
|
||||
*
|
||||
* 当满足以下条件之一时,指定网格可以合并至当前网格:
|
||||
* - 当前网格无数据而待合并网格有数据;
|
||||
* - 它们的顶点块数目相同且对应顶点块的布局一致,并且它们的子网格数目相同且对应子网格的布局一致。
|
||||
*
|
||||
* 两个顶点块布局一致当且仅当:
|
||||
* - 它们具有相同数量的顶点属性且对应的顶点属性具有相同的属性格式。
|
||||
*
|
||||
* 两个子网格布局一致,当且仅当:
|
||||
* - 它们具有相同的图元类型并且引用相同数量、相同索引的顶点块;并且,
|
||||
* - 要么都需要索引绘制,要么都不需要索引绘制。
|
||||
* @param mesh The other mesh to be validated
|
||||
*/
|
||||
bool validateMergingMesh(Mesh *mesh);
|
||||
|
||||
/**
|
||||
* @en Read the requested attribute of the given sub mesh
|
||||
* @zh 读取子网格的指定属性。
|
||||
* @param primitiveIndex Sub mesh index
|
||||
* @param attributeName Attribute name
|
||||
* @returns Return null if not found or can't read, otherwise, will create a large enough typed array to contain all data of the attribute,
|
||||
* the array type will match the data type of the attribute.
|
||||
*/
|
||||
TypedArray readAttribute(index_t primitiveIndex, const char *attributeName);
|
||||
|
||||
/**
|
||||
* @en Read the requested attribute of the given sub mesh and fill into the given buffer.
|
||||
* @zh 读取子网格的指定属性到目标缓冲区中。
|
||||
* @param primitiveIndex Sub mesh index
|
||||
* @param attributeName Attribute name
|
||||
* @param buffer The target array buffer
|
||||
* @param stride Byte distance between two attributes in the target buffer
|
||||
* @param offset The offset of the first attribute in the target buffer
|
||||
* @returns Return false if failed to access attribute, return true otherwise.
|
||||
*/
|
||||
bool copyAttribute(index_t primitiveIndex, const char *attributeName, ArrayBuffer *buffer, uint32_t stride, uint32_t offset);
|
||||
|
||||
/**
|
||||
* @en Read the indices data of the given sub mesh
|
||||
* @zh 读取子网格的索引数据。
|
||||
* @param primitiveIndex Sub mesh index
|
||||
* @returns Return null if not found or can't read, otherwise, will create a large enough typed array to contain all indices data,
|
||||
* the array type will use the corresponding stride size.
|
||||
*/
|
||||
IBArray readIndices(index_t primitiveIndex);
|
||||
|
||||
/**
|
||||
* @en Read the indices data of the given sub mesh and fill into the given array
|
||||
* @zh 读取子网格的索引数据到目标数组中。
|
||||
* @param primitiveIndex Sub mesh index
|
||||
* @param outputArray The target output array
|
||||
* @returns Return false if failed to access the indices data, return true otherwise.
|
||||
*/
|
||||
bool copyIndices(index_t primitiveIndex, TypedArray &outputArray);
|
||||
|
||||
/**
|
||||
* @en Read the format by attributeName of submesh
|
||||
* @zh 根据属性名读取子网格的属性信息。
|
||||
* @param primitiveIndex @en Sub mesh index @zh 子网格索引
|
||||
* @param attributeName @en Attribute name @zh 属性名称
|
||||
* @returns @en Return null if failed to read format, return the format otherwise. @zh 读取失败返回 null, 否则返回 format
|
||||
*/
|
||||
const gfx::FormatInfo *readAttributeFormat(index_t primitiveIndex, const char *attributeName);
|
||||
|
||||
/**
|
||||
* @en update dynamic sub mesh geometry
|
||||
* @zh 更新动态子网格的几何数据
|
||||
* @param primitiveIndex: sub mesh index
|
||||
* @param geometry: sub mesh geometry data
|
||||
*/
|
||||
void updateSubMesh(index_t primitiveIndex, const IDynamicGeometry &geometry);
|
||||
|
||||
/**
|
||||
* @en Set whether the data of this mesh could be accessed (read or wrote), it could be used only for static mesh
|
||||
* @zh 设置此网格的数据是否可被存取,此接口只针对静态网格资源生效
|
||||
* @param allowDataAccess @en Indicate whether the data of this mesh could be accessed (read or wrote) @zh 是否允许存取网格数据
|
||||
*/
|
||||
void setAllowDataAccess(bool allowDataAccess);
|
||||
|
||||
/**
|
||||
* @en Get whether the data of this mesh could be read or wrote
|
||||
* @zh 获取此网格的数据是否可被存取
|
||||
* @return @en whether the data of this mesh could be accessed (read or wrote) @zh 此网格的数据是否可被存取
|
||||
*/
|
||||
inline bool isAllowDataAccess() const { return _allowDataAccess; }
|
||||
|
||||
private:
|
||||
using AccessorType = std::function<void(const IVertexBundle &vertexBundle, int32_t iAttribute)>;
|
||||
|
||||
void accessAttribute(index_t primitiveIndex, const char *attributeName, const AccessorType &accessor);
|
||||
|
||||
gfx::BufferList createVertexBuffers(gfx::Device *gfxDevice, ArrayBuffer *data);
|
||||
void tryConvertVertexData();
|
||||
|
||||
void initDefault(const ccstd::optional<ccstd::string> &uuid) override;
|
||||
void releaseData();
|
||||
|
||||
static TypedArray createTypedArrayWithGFXFormat(gfx::Format format, uint32_t count);
|
||||
|
||||
public:
|
||||
IntrusivePtr<MorphRendering> morphRendering;
|
||||
|
||||
private:
|
||||
IStruct _struct;
|
||||
ccstd::hash_t _hash{0U};
|
||||
Uint8Array _data;
|
||||
|
||||
bool _initialized{false};
|
||||
bool _allowDataAccess{true};
|
||||
bool _isMeshDataUploaded{false};
|
||||
|
||||
RenderingSubMeshList _renderingSubMeshes;
|
||||
|
||||
ccstd::unordered_map<uint64_t, BoneSpaceBounds> _boneSpaceBounds;
|
||||
|
||||
JointBufferIndicesType _jointBufferIndices;
|
||||
|
||||
friend class MeshDeserializer;
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(Mesh);
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
98
cocos/3d/assets/Morph.h
Normal file
98
cocos/3d/assets/Morph.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "3d/assets/Types.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
struct IMeshBufferView {
|
||||
uint32_t offset{0};
|
||||
uint32_t length{0};
|
||||
uint32_t count{0};
|
||||
uint32_t stride{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* @en Morph target contains all displacements data of each vertex attribute like position and normal.
|
||||
* @zh 形变目标数据包含网格顶点属性在形变下的变化值,可能包含位移、法线等属性
|
||||
*/
|
||||
struct MorphTarget {
|
||||
/**
|
||||
* Displacement of each target attribute.
|
||||
*/
|
||||
ccstd::vector<IMeshBufferView> displacements;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en Sub mesh morph data describes all morph targets for one sub mesh,
|
||||
* including attributes in each morph target, morph targets data and weights corresponding each targets.
|
||||
* @zh 子网格形变数据描述一个子网格下所有的形变目标数据,包含顶点形变属性,形变目标数据和对应每个形变目标的权重。
|
||||
*/
|
||||
struct SubMeshMorph {
|
||||
/**
|
||||
* Attributes to morph.
|
||||
*/
|
||||
ccstd::vector<ccstd::string> attributes;
|
||||
|
||||
/**
|
||||
* Targets.
|
||||
*/
|
||||
ccstd::vector<MorphTarget> targets;
|
||||
|
||||
/**
|
||||
* Initial weights of each target.
|
||||
*/
|
||||
ccstd::optional<MeshWeightsType> weights;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en Mesh morph data structure to describe the sub meshes data of all sub meshes,
|
||||
* it also contains all sub mesh morphs, global weights configuration and target names.
|
||||
* Normally the global weights configuration should be identical to the sub mesh morph weights,
|
||||
* but if not, the global weights in morph is less prioritized.
|
||||
* @zh 网格的形变数据结构,包含所有子网格形变数据,全局的权重配置和所有形变目标名称。
|
||||
* 一般来说,全局权重配置和子网格形变数据中保持一致,但如果有差异,以子网格形变数据中的权重配置为准。
|
||||
*/
|
||||
|
||||
struct Morph {
|
||||
/**
|
||||
* Morph data of each sub-mesh.
|
||||
*/
|
||||
ccstd::vector<ccstd::optional<SubMeshMorph>> subMeshMorphs;
|
||||
|
||||
/**
|
||||
* Common initial weights of each sub-mesh.
|
||||
*/
|
||||
ccstd::optional<MeshWeightsType> weights;
|
||||
|
||||
/**
|
||||
* Name of each target of each sub-mesh morph.
|
||||
* This field is only meaningful if every sub-mesh has the same number of targets.
|
||||
*/
|
||||
ccstd::optional<ccstd::vector<ccstd::string>> targetNames;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
731
cocos/3d/assets/MorphRendering.cpp
Normal file
731
cocos/3d/assets/MorphRendering.cpp
Normal file
@@ -0,0 +1,731 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "3d/assets/MorphRendering.h"
|
||||
|
||||
#include <memory>
|
||||
#include "3d/assets/Mesh.h"
|
||||
#include "3d/assets/Morph.h"
|
||||
#include "base/RefCounted.h"
|
||||
#include "core/DataView.h"
|
||||
#include "core/TypedArray.h"
|
||||
#include "core/assets/ImageAsset.h"
|
||||
#include "core/assets/RenderingSubMesh.h"
|
||||
#include "core/assets/Texture2D.h"
|
||||
#include "platform/Image.h"
|
||||
#include "renderer/pipeline/Define.h"
|
||||
#include "scene/Pass.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
MorphRendering *createMorphRendering(Mesh *mesh, gfx::Device *gfxDevice) {
|
||||
return ccnew StdMorphRendering(mesh, gfxDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of once sub-mesh morph rendering.
|
||||
*/
|
||||
class SubMeshMorphRenderingInstance : public RefCounted {
|
||||
public:
|
||||
~SubMeshMorphRenderingInstance() override = default;
|
||||
/**
|
||||
* Set weights of each morph target.
|
||||
* @param weights The weights.
|
||||
*/
|
||||
virtual void setWeights(const ccstd::vector<float> &weights) = 0;
|
||||
|
||||
/**
|
||||
* Asks the define overrides needed to do the rendering.
|
||||
*/
|
||||
virtual ccstd::vector<scene::IMacroPatch> requiredPatches() = 0;
|
||||
|
||||
/**
|
||||
* Adapts the pipelineState to apply the rendering.
|
||||
* @param pipelineState
|
||||
*/
|
||||
virtual void adaptPipelineState(gfx::DescriptorSet *descriptorSet) = 0;
|
||||
|
||||
/**
|
||||
* Destroy this instance.
|
||||
*/
|
||||
virtual void destroy() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes how to render a sub-mesh morph.
|
||||
*/
|
||||
class SubMeshMorphRendering : public RefCounted {
|
||||
public:
|
||||
~SubMeshMorphRendering() override = default;
|
||||
/**
|
||||
* Creates a rendering instance.
|
||||
*/
|
||||
virtual SubMeshMorphRenderingInstance *createInstance() = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* True if force to use cpu computing based sub-mesh rendering.
|
||||
*/
|
||||
const bool PREFER_CPU_COMPUTING = false;
|
||||
|
||||
class MorphTexture final : public RefCounted {
|
||||
public:
|
||||
MorphTexture() = default;
|
||||
|
||||
~MorphTexture() override = default;
|
||||
/**
|
||||
* Gets the GFX texture.
|
||||
*/
|
||||
gfx::Texture *getTexture() {
|
||||
return _textureAsset->getGFXTexture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the GFX sampler.
|
||||
*/
|
||||
gfx::Sampler *getSampler() {
|
||||
return _sampler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value view.
|
||||
*/
|
||||
Float32Array &getValueView() {
|
||||
return _valueView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the texture. Release its GPU resources.
|
||||
*/
|
||||
void destroy() {
|
||||
_textureAsset->destroy();
|
||||
// Samplers allocated from `samplerLib` are not required and
|
||||
// should not be destroyed.
|
||||
// _sampler.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the pixels content to `valueView`.
|
||||
*/
|
||||
void updatePixels() {
|
||||
_textureAsset->uploadData(_arrayBuffer->getData());
|
||||
}
|
||||
|
||||
void initialize(gfx::Device *gfxDevice, uint32_t width, uint32_t height, uint32_t pixelBytes, bool /*useFloat32Array*/, PixelFormat pixelFormat) {
|
||||
_arrayBuffer = ccnew ArrayBuffer(width * height * pixelBytes);
|
||||
_valueView = Float32Array(_arrayBuffer);
|
||||
|
||||
auto *imageAsset = ccnew ImageAsset();
|
||||
IMemoryImageSource source{_arrayBuffer, false, width, height, pixelFormat};
|
||||
imageAsset->setNativeAsset(source);
|
||||
|
||||
_textureAsset = ccnew Texture2D();
|
||||
_textureAsset->setFilters(Texture2D::Filter::NEAREST, Texture2D::Filter::NEAREST);
|
||||
_textureAsset->setMipFilter(Texture2D::Filter::NONE);
|
||||
_textureAsset->setWrapMode(Texture2D::WrapMode::CLAMP_TO_EDGE, Texture2D::WrapMode::CLAMP_TO_EDGE, Texture2D::WrapMode::CLAMP_TO_EDGE);
|
||||
_textureAsset->setImage(imageAsset);
|
||||
|
||||
if (nullptr == _textureAsset->getGFXTexture()) {
|
||||
CC_LOG_WARNING("Unexpected: failed to create morph texture?");
|
||||
}
|
||||
_sampler = gfxDevice->getSampler(_textureAsset->getSamplerInfo());
|
||||
}
|
||||
|
||||
private:
|
||||
IntrusivePtr<Texture2D> _textureAsset;
|
||||
gfx::Sampler *_sampler{nullptr};
|
||||
ArrayBuffer::Ptr _arrayBuffer;
|
||||
Float32Array _valueView;
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(MorphTexture);
|
||||
};
|
||||
|
||||
struct GpuMorphAttribute {
|
||||
ccstd::string attributeName;
|
||||
IntrusivePtr<MorphTexture> morphTexture;
|
||||
};
|
||||
|
||||
struct CpuMorphAttributeTarget {
|
||||
Float32Array displacements;
|
||||
};
|
||||
|
||||
using CpuMorphAttributeTargetList = ccstd::vector<CpuMorphAttributeTarget>;
|
||||
|
||||
struct CpuMorphAttribute {
|
||||
ccstd::string name;
|
||||
CpuMorphAttributeTargetList targets;
|
||||
};
|
||||
|
||||
struct Vec4TextureFactory {
|
||||
uint32_t width{0};
|
||||
uint32_t height{0};
|
||||
std::function<MorphTexture *()> create{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* Decides a best texture size to have the specified pixel capacity at least.
|
||||
* The decided width and height has the following characteristics:
|
||||
* - the width and height are both power of 2;
|
||||
* - if the width and height are different, the width would be set to the larger once;
|
||||
* - the width is ensured to be multiple of 4.
|
||||
* @param nPixels Least pixel capacity.
|
||||
*/
|
||||
bool bestSizeToHavePixels(uint32_t nPixels, uint32_t *pWidth, uint32_t *pHeight) {
|
||||
if (pWidth == nullptr || pHeight == nullptr) {
|
||||
if (pWidth != nullptr) {
|
||||
*pWidth = 0;
|
||||
}
|
||||
|
||||
if (pHeight != nullptr) {
|
||||
*pHeight = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nPixels < 5) {
|
||||
nPixels = 5;
|
||||
}
|
||||
const uint32_t aligned = pipeline::nextPow2(nPixels);
|
||||
const auto epxSum = static_cast<uint32_t>(std::log2(aligned));
|
||||
const uint32_t h = epxSum >> 1;
|
||||
const uint32_t w = (epxSum & 1) ? (h + 1) : h;
|
||||
|
||||
*pWidth = 1 << w;
|
||||
*pHeight = 1 << h;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When use vertex-texture-fetch technique, we do need
|
||||
* `gl_vertexId` when we sample per-vertex data.
|
||||
* WebGL 1.0 does not have `gl_vertexId`; WebGL 2.0, however, does.
|
||||
* @param mesh
|
||||
* @param subMeshIndex
|
||||
* @param gfxDevice
|
||||
*/
|
||||
void enableVertexId(Mesh *mesh, uint32_t subMeshIndex, gfx::Device *gfxDevice) {
|
||||
mesh->getRenderingSubMeshes()[subMeshIndex]->enableVertexIdChannel(gfxDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param gfxDevice
|
||||
* @param vec4Capacity Capacity of vec4.
|
||||
*/
|
||||
Vec4TextureFactory createVec4TextureFactory(gfx::Device *gfxDevice, uint32_t vec4Capacity) {
|
||||
bool hasFeatureFloatTexture = static_cast<uint32_t>(gfxDevice->getFormatFeatures(gfx::Format::RGBA32F) & gfx::FormatFeature::SAMPLED_TEXTURE) != 0;
|
||||
|
||||
uint32_t pixelRequired = 0;
|
||||
PixelFormat pixelFormat = PixelFormat::RGBA8888;
|
||||
uint32_t pixelBytes = 4;
|
||||
bool useFloat32Array = false;
|
||||
if (hasFeatureFloatTexture) {
|
||||
pixelRequired = vec4Capacity;
|
||||
pixelBytes = 16;
|
||||
pixelFormat = Texture2D::PixelFormat::RGBA32F;
|
||||
useFloat32Array = true;
|
||||
} else {
|
||||
pixelRequired = 4 * vec4Capacity;
|
||||
pixelBytes = 4;
|
||||
pixelFormat = Texture2D::PixelFormat::RGBA8888;
|
||||
useFloat32Array = false;
|
||||
}
|
||||
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
bestSizeToHavePixels(pixelRequired, &width, &height);
|
||||
CC_ASSERT_GE(width * height, pixelRequired);
|
||||
|
||||
Vec4TextureFactory ret;
|
||||
ret.width = width;
|
||||
ret.height = height;
|
||||
ret.create = [=]() -> MorphTexture * {
|
||||
auto *texture = ccnew MorphTexture(); // texture will be held by IntrusivePtr in GpuMorphAttribute
|
||||
texture->initialize(gfxDevice, width, height, pixelBytes, useFloat32Array, pixelFormat);
|
||||
return texture;
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the access to morph related uniforms.
|
||||
*/
|
||||
class MorphUniforms final : public RefCounted {
|
||||
public:
|
||||
MorphUniforms(gfx::Device *gfxDevice, uint32_t targetCount) {
|
||||
_targetCount = targetCount;
|
||||
_localBuffer = ccnew DataView(ccnew ArrayBuffer(pipeline::UBOMorph::SIZE));
|
||||
|
||||
_remoteBuffer = gfxDevice->createBuffer(gfx::BufferInfo{
|
||||
gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST,
|
||||
gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE,
|
||||
pipeline::UBOMorph::SIZE,
|
||||
pipeline::UBOMorph::SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
~MorphUniforms() override {
|
||||
delete _localBuffer;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
_remoteBuffer->destroy();
|
||||
}
|
||||
|
||||
gfx::Buffer *getBuffer() const {
|
||||
return _remoteBuffer;
|
||||
}
|
||||
|
||||
void setWeights(const ccstd::vector<float> &weights) {
|
||||
CC_ASSERT_EQ(weights.size(), _targetCount);
|
||||
for (size_t iWeight = 0; iWeight < weights.size(); ++iWeight) {
|
||||
_localBuffer->setFloat32(static_cast<uint32_t>(pipeline::UBOMorph::OFFSET_OF_WEIGHTS + 4 * iWeight), weights[iWeight]);
|
||||
}
|
||||
}
|
||||
|
||||
void setMorphTextureInfo(float width, float height) {
|
||||
_localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_DISPLACEMENT_TEXTURE_WIDTH, width);
|
||||
_localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_DISPLACEMENT_TEXTURE_HEIGHT, height);
|
||||
}
|
||||
|
||||
void setVerticesCount(uint32_t count) {
|
||||
_localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_VERTICES_COUNT, static_cast<float>(count));
|
||||
}
|
||||
|
||||
void commit() {
|
||||
ArrayBuffer *buffer = _localBuffer->buffer();
|
||||
_remoteBuffer->update(buffer->getData(), buffer->byteLength());
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t _targetCount{0};
|
||||
DataView *_localBuffer{nullptr};
|
||||
IntrusivePtr<gfx::Buffer> _remoteBuffer;
|
||||
};
|
||||
|
||||
class CpuComputing final : public SubMeshMorphRendering {
|
||||
public:
|
||||
explicit CpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice);
|
||||
|
||||
SubMeshMorphRenderingInstance *createInstance() override;
|
||||
const ccstd::vector<CpuMorphAttribute> &getData() const;
|
||||
|
||||
private:
|
||||
ccstd::vector<CpuMorphAttribute> _attributes;
|
||||
gfx::Device *_gfxDevice{nullptr};
|
||||
};
|
||||
|
||||
class GpuComputing final : public SubMeshMorphRendering {
|
||||
public:
|
||||
explicit GpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice);
|
||||
SubMeshMorphRenderingInstance *createInstance() override;
|
||||
|
||||
void destroy();
|
||||
|
||||
private:
|
||||
gfx::Device *_gfxDevice{nullptr};
|
||||
const SubMeshMorph *_subMeshMorph{nullptr};
|
||||
uint32_t _textureWidth{0};
|
||||
uint32_t _textureHeight{0};
|
||||
ccstd::vector<GpuMorphAttribute> _attributes;
|
||||
uint32_t _verticesCount{0};
|
||||
|
||||
friend class GpuComputingRenderingInstance;
|
||||
};
|
||||
|
||||
class CpuComputingRenderingInstance final : public SubMeshMorphRenderingInstance {
|
||||
public:
|
||||
explicit CpuComputingRenderingInstance(CpuComputing *owner, uint32_t nVertices, gfx::Device *gfxDevice) {
|
||||
_owner = owner; //NOTE: release by mesh`s destroy, it`ll call current instance`s destroy method
|
||||
_morphUniforms = ccnew MorphUniforms(gfxDevice, 0 /* TODO? */);
|
||||
|
||||
auto vec4TextureFactory = createVec4TextureFactory(gfxDevice, nVertices);
|
||||
_morphUniforms->setMorphTextureInfo(static_cast<float>(vec4TextureFactory.width), static_cast<float>(vec4TextureFactory.height));
|
||||
_morphUniforms->commit();
|
||||
for (const auto &attributeMorph : _owner->getData()) {
|
||||
auto *morphTexture = vec4TextureFactory.create();
|
||||
_attributes.emplace_back(GpuMorphAttribute{attributeMorph.name, morphTexture});
|
||||
}
|
||||
}
|
||||
|
||||
void setWeights(const ccstd::vector<float> &weights) override {
|
||||
for (size_t iAttribute = 0; iAttribute < _attributes.size(); ++iAttribute) {
|
||||
const auto &myAttribute = _attributes[iAttribute];
|
||||
Float32Array &valueView = myAttribute.morphTexture->getValueView();
|
||||
const auto &attributeMorph = _owner->getData()[iAttribute];
|
||||
CC_ASSERT(weights.size() == attributeMorph.targets.size());
|
||||
for (size_t iTarget = 0; iTarget < attributeMorph.targets.size(); ++iTarget) {
|
||||
const auto &targetDisplacements = attributeMorph.targets[iTarget].displacements;
|
||||
const float weight = weights[iTarget];
|
||||
const uint32_t nVertices = targetDisplacements.length() / 3;
|
||||
if (iTarget == 0) {
|
||||
for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) {
|
||||
valueView[4 * iVertex + 0] = targetDisplacements[3 * iVertex + 0] * weight;
|
||||
valueView[4 * iVertex + 1] = targetDisplacements[3 * iVertex + 1] * weight;
|
||||
valueView[4 * iVertex + 2] = targetDisplacements[3 * iVertex + 2] * weight;
|
||||
}
|
||||
} else if (std::fabs(weight) >= std::numeric_limits<float>::epsilon()) {
|
||||
for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) {
|
||||
valueView[4 * iVertex + 0] += targetDisplacements[3 * iVertex + 0] * weight;
|
||||
valueView[4 * iVertex + 1] += targetDisplacements[3 * iVertex + 1] * weight;
|
||||
valueView[4 * iVertex + 2] += targetDisplacements[3 * iVertex + 2] * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myAttribute.morphTexture->updatePixels();
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> requiredPatches() override {
|
||||
return {
|
||||
{"CC_MORPH_TARGET_USE_TEXTURE", true},
|
||||
{"CC_MORPH_PRECOMPUTED", true},
|
||||
};
|
||||
}
|
||||
|
||||
void adaptPipelineState(gfx::DescriptorSet *descriptorSet) override {
|
||||
for (const auto &attribute : _attributes) {
|
||||
const auto &attributeName = attribute.attributeName;
|
||||
ccstd::optional<uint32_t> binding;
|
||||
if (attributeName == gfx::ATTR_NAME_POSITION) {
|
||||
binding = uint32_t{pipeline::POSITIONMORPH::BINDING};
|
||||
} else if (attributeName == gfx::ATTR_NAME_NORMAL) {
|
||||
binding = uint32_t{pipeline::NORMALMORPH::BINDING};
|
||||
} else if (attributeName == gfx::ATTR_NAME_TANGENT) {
|
||||
binding = uint32_t{pipeline::TANGENTMORPH::BINDING};
|
||||
} else {
|
||||
CC_LOG_WARNING("Unexpected attribute!");
|
||||
}
|
||||
|
||||
if (binding.has_value()) {
|
||||
descriptorSet->bindSampler(binding.value(), attribute.morphTexture->getSampler());
|
||||
descriptorSet->bindTexture(binding.value(), attribute.morphTexture->getTexture());
|
||||
}
|
||||
}
|
||||
descriptorSet->bindBuffer(pipeline::UBOMorph::BINDING, _morphUniforms->getBuffer());
|
||||
descriptorSet->update();
|
||||
}
|
||||
|
||||
void destroy() override {
|
||||
CC_SAFE_DESTROY(_morphUniforms);
|
||||
for (auto &myAttribute : _attributes) {
|
||||
CC_SAFE_DESTROY(myAttribute.morphTexture);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ccstd::vector<GpuMorphAttribute> _attributes;
|
||||
IntrusivePtr<CpuComputing> _owner;
|
||||
IntrusivePtr<MorphUniforms> _morphUniforms;
|
||||
};
|
||||
|
||||
class GpuComputingRenderingInstance final : public SubMeshMorphRenderingInstance {
|
||||
public:
|
||||
explicit GpuComputingRenderingInstance(GpuComputing *owner, gfx::Device *gfxDevice) {
|
||||
_owner = owner;
|
||||
_morphUniforms = ccnew MorphUniforms(gfxDevice, static_cast<uint32_t>(_owner->_subMeshMorph->targets.size()));
|
||||
_morphUniforms->setMorphTextureInfo(static_cast<float>(_owner->_textureWidth), static_cast<float>(_owner->_textureHeight));
|
||||
_morphUniforms->setVerticesCount(_owner->_verticesCount);
|
||||
_morphUniforms->commit();
|
||||
_attributes = &_owner->_attributes;
|
||||
}
|
||||
|
||||
void setWeights(const ccstd::vector<float> &weights) override {
|
||||
_morphUniforms->setWeights(weights);
|
||||
_morphUniforms->commit();
|
||||
}
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> requiredPatches() override {
|
||||
return {
|
||||
{"CC_MORPH_TARGET_USE_TEXTURE", true},
|
||||
};
|
||||
}
|
||||
|
||||
void adaptPipelineState(gfx::DescriptorSet *descriptorSet) override {
|
||||
for (const auto &attribute : *_attributes) {
|
||||
const auto &attributeName = attribute.attributeName;
|
||||
ccstd::optional<uint32_t> binding;
|
||||
if (attributeName == gfx::ATTR_NAME_POSITION) {
|
||||
binding = uint32_t{pipeline::POSITIONMORPH::BINDING};
|
||||
} else if (attributeName == gfx::ATTR_NAME_NORMAL) {
|
||||
binding = uint32_t{pipeline::NORMALMORPH::BINDING};
|
||||
} else if (attributeName == gfx::ATTR_NAME_TANGENT) {
|
||||
binding = uint32_t{pipeline::TANGENTMORPH::BINDING};
|
||||
} else {
|
||||
CC_LOG_WARNING("Unexpected attribute!");
|
||||
}
|
||||
|
||||
if (binding.has_value()) {
|
||||
descriptorSet->bindSampler(binding.value(), attribute.morphTexture->getSampler());
|
||||
descriptorSet->bindTexture(binding.value(), attribute.morphTexture->getTexture());
|
||||
}
|
||||
}
|
||||
descriptorSet->bindBuffer(pipeline::UBOMorph::BINDING, _morphUniforms->getBuffer());
|
||||
descriptorSet->update();
|
||||
}
|
||||
|
||||
void destroy() override {
|
||||
}
|
||||
|
||||
private:
|
||||
ccstd::vector<GpuMorphAttribute> *_attributes{nullptr};
|
||||
IntrusivePtr<GpuComputing> _owner;
|
||||
IntrusivePtr<MorphUniforms> _morphUniforms;
|
||||
};
|
||||
|
||||
CpuComputing::CpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice) {
|
||||
_gfxDevice = gfxDevice;
|
||||
const auto &subMeshMorph = morph->subMeshMorphs[subMeshIndex].value();
|
||||
enableVertexId(mesh, subMeshIndex, gfxDevice);
|
||||
|
||||
for (size_t attributeIndex = 0, len = subMeshMorph.attributes.size(); attributeIndex < len; ++attributeIndex) {
|
||||
const auto &attributeName = subMeshMorph.attributes[attributeIndex];
|
||||
|
||||
CpuMorphAttribute attr;
|
||||
attr.name = attributeName;
|
||||
attr.targets.resize(subMeshMorph.targets.size());
|
||||
|
||||
uint32_t i = 0;
|
||||
for (const auto &attributeDisplacement : subMeshMorph.targets) {
|
||||
const Mesh::IBufferView &displacementsView = attributeDisplacement.displacements[attributeIndex];
|
||||
attr.targets[i].displacements = Float32Array(mesh->getData().buffer(),
|
||||
mesh->getData().byteOffset() + displacementsView.offset,
|
||||
attributeDisplacement.displacements[attributeIndex].count);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
_attributes.emplace_back(attr);
|
||||
}
|
||||
}
|
||||
|
||||
SubMeshMorphRenderingInstance *CpuComputing::createInstance() {
|
||||
return ccnew CpuComputingRenderingInstance(
|
||||
this,
|
||||
_attributes[0].targets[0].displacements.length() / 3,
|
||||
_gfxDevice);
|
||||
}
|
||||
|
||||
const ccstd::vector<CpuMorphAttribute> &CpuComputing::getData() const {
|
||||
return _attributes;
|
||||
}
|
||||
|
||||
GpuComputing::GpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice) {
|
||||
_gfxDevice = gfxDevice;
|
||||
const auto &subMeshMorph = morph->subMeshMorphs[subMeshIndex].value();
|
||||
|
||||
_subMeshMorph = &subMeshMorph;
|
||||
// assertIsNonNullable(subMeshMorph);
|
||||
|
||||
enableVertexId(mesh, subMeshIndex, gfxDevice);
|
||||
|
||||
uint32_t nVertices = mesh->getStruct().vertexBundles[mesh->getStruct().primitives[subMeshIndex].vertexBundelIndices[0]].view.count;
|
||||
_verticesCount = nVertices;
|
||||
auto nTargets = static_cast<uint32_t>(subMeshMorph.targets.size());
|
||||
uint32_t vec4Required = nVertices * nTargets;
|
||||
|
||||
auto vec4TextureFactory = createVec4TextureFactory(gfxDevice, vec4Required);
|
||||
_textureWidth = vec4TextureFactory.width;
|
||||
_textureHeight = vec4TextureFactory.height;
|
||||
|
||||
// Creates texture for each attribute.
|
||||
uint32_t attributeIndex = 0;
|
||||
_attributes.reserve(subMeshMorph.attributes.size());
|
||||
for (const auto &attributeName : subMeshMorph.attributes) {
|
||||
auto *vec4Tex = vec4TextureFactory.create();
|
||||
Float32Array &valueView = vec4Tex->getValueView();
|
||||
// if (DEV) { // Make it easy to view texture in profilers...
|
||||
// for (let i = 0; i < valueView.length / 4; ++i) {
|
||||
// valueView[i * 4 + 3] = 1.0;
|
||||
// }
|
||||
// }
|
||||
|
||||
uint32_t morphTargetIndex = 0;
|
||||
for (const auto &morphTarget : subMeshMorph.targets) {
|
||||
const auto &displacementsView = morphTarget.displacements[attributeIndex];
|
||||
Float32Array displacements(mesh->getData().buffer(),
|
||||
mesh->getData().byteOffset() + displacementsView.offset,
|
||||
displacementsView.count);
|
||||
const uint32_t displacementsOffset = (nVertices * morphTargetIndex) * 4;
|
||||
for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) {
|
||||
valueView[displacementsOffset + 4 * iVertex + 0] = displacements[3 * iVertex + 0];
|
||||
valueView[displacementsOffset + 4 * iVertex + 1] = displacements[3 * iVertex + 1];
|
||||
valueView[displacementsOffset + 4 * iVertex + 2] = displacements[3 * iVertex + 2];
|
||||
}
|
||||
|
||||
++morphTargetIndex;
|
||||
}
|
||||
|
||||
vec4Tex->updatePixels();
|
||||
|
||||
_attributes.emplace_back(GpuMorphAttribute{attributeName, vec4Tex});
|
||||
|
||||
++attributeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
SubMeshMorphRenderingInstance *GpuComputing::createInstance() {
|
||||
return ccnew GpuComputingRenderingInstance(this, _gfxDevice);
|
||||
}
|
||||
|
||||
void GpuComputing::destroy() {
|
||||
for (auto &attribute : _attributes) {
|
||||
attribute.morphTexture->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class StdMorphRenderingInstance : public MorphRenderingInstance {
|
||||
public:
|
||||
explicit StdMorphRenderingInstance(StdMorphRendering *owner) {
|
||||
_owner = owner;
|
||||
size_t nSubMeshes = _owner->_mesh->getStruct().primitives.size();
|
||||
_subMeshInstances.resize(nSubMeshes, nullptr);
|
||||
|
||||
for (size_t iSubMesh = 0; iSubMesh < nSubMeshes; ++iSubMesh) {
|
||||
if (_owner->_subMeshRenderings[iSubMesh] != nullptr) {
|
||||
_subMeshInstances[iSubMesh] = _owner->_subMeshRenderings[iSubMesh]->createInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~StdMorphRenderingInstance() override = default;
|
||||
|
||||
void setWeights(index_t subMeshIndex, const MeshWeightsType &weights) override {
|
||||
if (_subMeshInstances[subMeshIndex]) {
|
||||
_subMeshInstances[subMeshIndex]->setWeights(weights);
|
||||
}
|
||||
}
|
||||
|
||||
void adaptPipelineState(index_t subMeshIndex, gfx::DescriptorSet *descriptorSet) override {
|
||||
if (_subMeshInstances[subMeshIndex]) {
|
||||
_subMeshInstances[subMeshIndex]->adaptPipelineState(descriptorSet);
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> requiredPatches(index_t subMeshIndex) override {
|
||||
CC_ASSERT(_owner->_mesh->getStruct().morph.has_value());
|
||||
const auto &subMeshMorphOpt = _owner->_mesh->getStruct().morph.value().subMeshMorphs[subMeshIndex];
|
||||
auto *subMeshRenderingInstance = _subMeshInstances[subMeshIndex].get();
|
||||
if (subMeshRenderingInstance == nullptr || !subMeshMorphOpt.has_value()) {
|
||||
return {};
|
||||
}
|
||||
const auto &subMeshMorph = subMeshMorphOpt.value();
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> patches{
|
||||
{"CC_USE_MORPH", true},
|
||||
{"CC_MORPH_TARGET_COUNT", static_cast<int32_t>(subMeshMorph.targets.size())}};
|
||||
|
||||
auto posIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_POSITION);
|
||||
if (posIter != subMeshMorph.attributes.end()) {
|
||||
patches.emplace_back(scene::IMacroPatch{
|
||||
"CC_MORPH_TARGET_HAS_POSITION",
|
||||
true,
|
||||
});
|
||||
}
|
||||
|
||||
auto normalIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_NORMAL);
|
||||
if (normalIter != subMeshMorph.attributes.end()) {
|
||||
patches.emplace_back(scene::IMacroPatch{
|
||||
"CC_MORPH_TARGET_HAS_NORMAL",
|
||||
true,
|
||||
});
|
||||
}
|
||||
|
||||
auto tangentIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_TANGENT);
|
||||
if (tangentIter != subMeshMorph.attributes.end()) {
|
||||
patches.emplace_back(scene::IMacroPatch{
|
||||
"CC_MORPH_TARGET_HAS_TANGENT",
|
||||
true,
|
||||
});
|
||||
}
|
||||
|
||||
auto renderingInstancePatches = subMeshRenderingInstance->requiredPatches();
|
||||
for (auto &renderingInstancePatch : renderingInstancePatches) {
|
||||
patches.emplace_back(renderingInstancePatch);
|
||||
}
|
||||
|
||||
return patches;
|
||||
}
|
||||
|
||||
void destroy() override {
|
||||
for (auto &subMeshInstance : _subMeshInstances) {
|
||||
if (subMeshInstance != nullptr) {
|
||||
subMeshInstance->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
IntrusivePtr<StdMorphRendering> _owner;
|
||||
ccstd::vector<IntrusivePtr<SubMeshMorphRenderingInstance>> _subMeshInstances;
|
||||
};
|
||||
|
||||
StdMorphRendering::StdMorphRendering(Mesh *mesh, gfx::Device *gfxDevice) {
|
||||
_mesh = mesh;
|
||||
const auto &structInfo = _mesh->getStruct();
|
||||
if (!structInfo.morph.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t nSubMeshes = structInfo.primitives.size();
|
||||
_subMeshRenderings.resize(nSubMeshes, nullptr);
|
||||
const auto &morph = structInfo.morph.value();
|
||||
for (size_t iSubMesh = 0; iSubMesh < nSubMeshes; ++iSubMesh) {
|
||||
const auto &subMeshMorphHolder = morph.subMeshMorphs[iSubMesh];
|
||||
if (!subMeshMorphHolder.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto &subMeshMorph = subMeshMorphHolder.value();
|
||||
|
||||
if (PREFER_CPU_COMPUTING || subMeshMorph.targets.size() > pipeline::UBOMorph::MAX_MORPH_TARGET_COUNT) {
|
||||
_subMeshRenderings[iSubMesh] = ccnew CpuComputing(
|
||||
_mesh,
|
||||
static_cast<uint32_t>(iSubMesh),
|
||||
&morph,
|
||||
gfxDevice);
|
||||
} else {
|
||||
_subMeshRenderings[iSubMesh] = ccnew GpuComputing(
|
||||
_mesh,
|
||||
static_cast<uint32_t>(iSubMesh),
|
||||
&morph,
|
||||
gfxDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StdMorphRendering::~StdMorphRendering() = default;
|
||||
|
||||
MorphRenderingInstance *StdMorphRendering::createInstance() {
|
||||
auto *ret = ccnew StdMorphRenderingInstance(this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
114
cocos/3d/assets/MorphRendering.h
Normal file
114
cocos/3d/assets/MorphRendering.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "3d/assets/MorphRendering.h"
|
||||
#include "3d/assets/Types.h"
|
||||
#include "base/Ptr.h"
|
||||
#include "scene/Define.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class SubMeshMorphRendering;
|
||||
class Mesh;
|
||||
|
||||
namespace gfx {
|
||||
class Device;
|
||||
class DescriptorSet;
|
||||
} // namespace gfx
|
||||
|
||||
/**
|
||||
* @en The instance of [[MorphRendering]] for dedicated control in the mesh renderer.
|
||||
* The root [[MorphRendering]] is owned by [[Mesh]] asset, each [[MeshRenderer]] can have its own morph rendering instance.
|
||||
* @zh 用于网格渲染器中独立控制 [[MorphRendering]] 的实例。原始 [[MorphRendering]] 被 [[Mesh]] 资源持有,每个 [[MeshRenderer]] 都持有自己的形变网格渲染实例。
|
||||
*/
|
||||
class MorphRenderingInstance : public RefCounted {
|
||||
public:
|
||||
~MorphRenderingInstance() override = default;
|
||||
/**
|
||||
* Sets weights of targets of specified sub mesh.
|
||||
* @param subMeshIndex
|
||||
* @param weights
|
||||
*/
|
||||
virtual void setWeights(index_t subMeshIndex, const MeshWeightsType &weights) = 0;
|
||||
|
||||
/**
|
||||
* Adapts pipeline state to do the rendering.
|
||||
* @param subMeshIndex
|
||||
* @param pipelineState
|
||||
*/
|
||||
virtual void adaptPipelineState(index_t subMeshIndex, gfx::DescriptorSet *descriptorSet) = 0;
|
||||
|
||||
virtual ccstd::vector<scene::IMacroPatch> requiredPatches(index_t subMeshIndex) = 0;
|
||||
|
||||
/**
|
||||
* Destroy the rendering instance.
|
||||
*/
|
||||
virtual void destroy() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en Interface for classes which control the rendering of morph resources.
|
||||
* @zh 支持形变网格渲染的基类。
|
||||
*/
|
||||
class MorphRendering : public RefCounted {
|
||||
public:
|
||||
~MorphRendering() override = default;
|
||||
virtual MorphRenderingInstance *createInstance() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en Standard morph rendering class, it supports both GPU and CPU based morph blending.
|
||||
* If sub mesh morph targets count is less than [[pipeline.UBOMorph.MAX_MORPH_TARGET_COUNT]], then GPU based blending is enabled.
|
||||
* Each of the sub-mesh morph has its own [[MorphRenderingInstance]],
|
||||
* its morph target weights, render pipeline state and strategy of morph blending are controlled separately.
|
||||
* @zh 标准形变网格渲染类,它同时支持 CPU 和 GPU 的形变混合计算。
|
||||
* 如果子网格形变目标数量少于 [[pipeline.UBOMorph.MAX_MORPH_TARGET_COUNT]],那么就会使用基于 GPU 的形变混合计算。
|
||||
* 每个子网格形变都使用自己独立的 [[MorphRenderingInstance]],它的形变目标权重、渲染管线状态和形变混合计算策略都是独立控制的。
|
||||
*/
|
||||
class StdMorphRendering final : public MorphRendering {
|
||||
public:
|
||||
explicit StdMorphRendering(Mesh *mesh, gfx::Device *gfxDevice);
|
||||
~StdMorphRendering() override;
|
||||
MorphRenderingInstance *createInstance() override;
|
||||
|
||||
private:
|
||||
Mesh *_mesh{nullptr};
|
||||
ccstd::vector<IntrusivePtr<SubMeshMorphRendering>> _subMeshRenderings;
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(StdMorphRendering);
|
||||
|
||||
friend class StdMorphRenderingInstance;
|
||||
};
|
||||
|
||||
/**
|
||||
* @en Create morph rendering from mesh which contains morph targets data.
|
||||
* @zh 从包含形变对象的网格资源中创建形变网格渲染对象。
|
||||
* @param mesh @en The mesh to create morph rendering from. @zh 用于创建形变网格渲染对象的原始网格资源。
|
||||
* @param gfxDevice @en The device instance acquired from [[Root]]. @zh 设备对象实例,可以从 [[Root]] 获取。
|
||||
*/
|
||||
MorphRendering *createMorphRendering(Mesh *mesh, gfx::Device *gfxDevice);
|
||||
|
||||
} // namespace cc
|
||||
72
cocos/3d/assets/Skeleton.cpp
Normal file
72
cocos/3d/assets/Skeleton.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "3d/assets/Skeleton.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "base/std/hash/hash.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
const ccstd::vector<Mat4> &Skeleton::getInverseBindposes() {
|
||||
if (!_invBindposes.has_value()) {
|
||||
_invBindposes = ccstd::vector<Mat4>{};
|
||||
for (const auto &bindpose : _bindposes) {
|
||||
_invBindposes.value().emplace_back(bindpose.getInversed());
|
||||
}
|
||||
}
|
||||
return *_invBindposes;
|
||||
}
|
||||
|
||||
ccstd::hash_t Skeleton::getHash() {
|
||||
// hashes should already be computed offline, but if not, make one
|
||||
if (!_hash) {
|
||||
std::stringstream sstr;
|
||||
for (const auto &ibm : _bindposes) {
|
||||
sstr << std::fixed << std::setprecision(2)
|
||||
<< ibm.m[0] << " " << ibm.m[1] << " " << ibm.m[2] << " " << ibm.m[3] << " "
|
||||
<< ibm.m[4] << " " << ibm.m[5] << " " << ibm.m[6] << " " << ibm.m[7] << " "
|
||||
<< ibm.m[8] << " " << ibm.m[9] << " " << ibm.m[10] << " " << ibm.m[11] << " "
|
||||
<< ibm.m[12] << " " << ibm.m[13] << " " << ibm.m[14] << " " << ibm.m[15] << "\n";
|
||||
}
|
||||
ccstd::string str{sstr.str()};
|
||||
ccstd::hash_t seed = 666;
|
||||
ccstd::hash_range(seed, str.begin(), str.end());
|
||||
_hash = seed;
|
||||
}
|
||||
return _hash;
|
||||
}
|
||||
|
||||
bool Skeleton::destroy() {
|
||||
//cjh TODO: (legacyCC.director.root?.dataPoolManager as DataPoolManager)?.releaseSkeleton(this);
|
||||
return Super::destroy();
|
||||
}
|
||||
|
||||
bool Skeleton::validate() const {
|
||||
return !_joints.empty() && !_bindposes.empty();
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
87
cocos/3d/assets/Skeleton.h
Normal file
87
cocos/3d/assets/Skeleton.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/assets/Asset.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
/**
|
||||
* @en The skeleton asset. It stores the path related to [[SkinnedMeshRenderer.skinningRoot]] of all bones and its bind pose matrix.
|
||||
* @zh 骨骼资源。骨骼资源记录了每个关节(相对于 [[SkinnedMeshRenderer.skinningRoot]])的路径以及它的绑定姿势矩阵。
|
||||
*/
|
||||
class Skeleton final : public Asset {
|
||||
public:
|
||||
using Super = Asset;
|
||||
Skeleton() = default;
|
||||
~Skeleton() override = default;
|
||||
/**
|
||||
* @en The path of all bones, the length always equals the length of [[bindposes]]
|
||||
* @zh 所有关节的路径。该数组的长度始终与 [[bindposes]] 的长度相同。
|
||||
*/
|
||||
inline const ccstd::vector<ccstd::string> &getJoints() const {
|
||||
return _joints;
|
||||
}
|
||||
|
||||
inline void setJoints(const ccstd::vector<ccstd::string> &value) {
|
||||
_joints = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @en The bind poses matrix of all bones, the length always equals the length of [[joints]]
|
||||
* @zh 所有关节的绑定姿势矩阵。该数组的长度始终与 [[joints]] 的长度相同。
|
||||
*/
|
||||
const ccstd::vector<Mat4> &getBindposes() const {
|
||||
return _bindposes;
|
||||
}
|
||||
|
||||
void setBindposes(const ccstd::vector<Mat4> &value) {
|
||||
_bindposes = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @en Gets the inverse bind poses matrix
|
||||
* @zh 获取反向绑定姿势矩阵
|
||||
*/
|
||||
const ccstd::vector<Mat4> &getInverseBindposes();
|
||||
|
||||
/**
|
||||
* @en Gets the hash of the skeleton asset
|
||||
* @zh 获取骨骼资源的哈希值
|
||||
*/
|
||||
ccstd::hash_t getHash();
|
||||
void setHash(ccstd::hash_t hash) { _hash = hash; }
|
||||
|
||||
bool destroy() override;
|
||||
bool validate() const override;
|
||||
|
||||
private:
|
||||
ccstd::vector<ccstd::string> _joints;
|
||||
ccstd::vector<Mat4> _bindposes;
|
||||
ccstd::optional<ccstd::vector<Mat4>> _invBindposes;
|
||||
ccstd::hash_t _hash{0U};
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
60
cocos/3d/assets/Types.h
Normal file
60
cocos/3d/assets/Types.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "base/std/container/string.h"
|
||||
#include "base/std/optional.h"
|
||||
#include "core/TypedArray.h"
|
||||
namespace cc {
|
||||
|
||||
using MeshWeightsType = ccstd::vector<float>;
|
||||
|
||||
/**
|
||||
* @en Array views for index buffer
|
||||
* @zh 允许存储索引的数组视图
|
||||
*/
|
||||
using IBArray = ccstd::variant<ccstd::monostate, Uint8Array, Uint16Array, Uint32Array>;
|
||||
|
||||
template <typename T>
|
||||
T getIBArrayValue(const IBArray &arr, uint32_t idx) {
|
||||
#define IBARRAY_GET_VALUE(type) \
|
||||
do { \
|
||||
auto *p = ccstd::get_if<type>(&arr); \
|
||||
if (p != nullptr) { \
|
||||
return static_cast<T>((*p)[idx]); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
IBARRAY_GET_VALUE(Uint16Array);
|
||||
IBARRAY_GET_VALUE(Uint32Array);
|
||||
IBARRAY_GET_VALUE(Uint8Array);
|
||||
|
||||
#undef IBARRAY_GET_VALUE
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
125
cocos/3d/misc/Buffer.cpp
Normal file
125
cocos/3d/misc/Buffer.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "3d/misc/Buffer.h"
|
||||
#include "base/std/variant.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
namespace {
|
||||
ccstd::unordered_map<gfx::FormatType, ccstd::string> typeMap{
|
||||
{gfx::FormatType::UNORM, "Uint"},
|
||||
{gfx::FormatType::SNORM, "Int"},
|
||||
{gfx::FormatType::UINT, "Uint"},
|
||||
{gfx::FormatType::INT, "Int"},
|
||||
{gfx::FormatType::UFLOAT, "Float"},
|
||||
{gfx::FormatType::FLOAT, "Float"},
|
||||
};
|
||||
|
||||
ccstd::string getDataViewType(const gfx::FormatInfo &info) {
|
||||
ccstd::string type;
|
||||
auto iter = typeMap.find(info.type);
|
||||
if (iter != typeMap.end()) {
|
||||
type = iter->second;
|
||||
} else {
|
||||
type = "Uint";
|
||||
}
|
||||
|
||||
const uint32_t bytes = info.size / info.count * 8;
|
||||
return type + std::to_string(bytes);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using DataVariant = ccstd::variant<ccstd::monostate, int32_t, float>;
|
||||
using MapBufferCallback = std::function<DataVariant(const DataVariant &cur, uint32_t idx, const DataView &view)>;
|
||||
|
||||
DataView mapBuffer(DataView &target,
|
||||
const MapBufferCallback &callback,
|
||||
ccstd::optional<gfx::Format> aFormat,
|
||||
ccstd::optional<uint32_t> aOffset,
|
||||
ccstd::optional<uint32_t> aLength,
|
||||
ccstd::optional<uint32_t> aStride,
|
||||
DataView *out) {
|
||||
gfx::Format format = aFormat.has_value() ? aFormat.value() : gfx::Format::R32F;
|
||||
uint32_t offset = aOffset.has_value() ? aOffset.value() : 0;
|
||||
uint32_t length = aLength.has_value() ? aLength.value() : target.byteLength() - offset;
|
||||
uint32_t stride = aStride.has_value() ? aStride.value() : 0;
|
||||
|
||||
DataView dataView;
|
||||
if (out == nullptr) {
|
||||
out = &dataView;
|
||||
dataView.assign(target.buffer()->slice(target.byteOffset(), target.byteOffset() + target.byteLength()));
|
||||
}
|
||||
|
||||
const auto &info = gfx::GFX_FORMAT_INFOS[static_cast<int32_t>(format)];
|
||||
if (stride == 0) {
|
||||
stride = info.size;
|
||||
}
|
||||
|
||||
static const ccstd::string SET_PREFIX{"set"};
|
||||
static const ccstd::string GET_PREFIX{"get"};
|
||||
|
||||
bool isFloat = info.type == gfx::FormatType::FLOAT || info.type == gfx::FormatType::UFLOAT;
|
||||
DataView::IntWritter intWritter = nullptr;
|
||||
if (!isFloat) {
|
||||
intWritter = DataView::intWritterMap[SET_PREFIX + getDataViewType(info)];
|
||||
}
|
||||
|
||||
DataView::ReaderVariant intReader;
|
||||
if (!isFloat) {
|
||||
intReader = DataView::intReaderMap[GET_PREFIX + getDataViewType(info)];
|
||||
}
|
||||
|
||||
const uint32_t componentBytesLength = info.size / info.count;
|
||||
const uint32_t nSeg = floor(length / stride);
|
||||
|
||||
for (uint32_t iSeg = 0; iSeg < nSeg; ++iSeg) {
|
||||
const uint32_t x = offset + stride * iSeg;
|
||||
for (uint32_t iComponent = 0; iComponent < info.count; ++iComponent) {
|
||||
const uint32_t y = x + componentBytesLength * iComponent;
|
||||
if (isFloat) {
|
||||
float cur = target.getFloat32(y);
|
||||
auto dataVariant = callback(cur, iComponent, target);
|
||||
if (ccstd::holds_alternative<float>(dataVariant)) {
|
||||
out->setFloat32(y, ccstd::get<float>(dataVariant));
|
||||
} else {
|
||||
CC_LOG_ERROR("mapBuffer, wrong data type, expect float");
|
||||
}
|
||||
} else {
|
||||
int32_t cur = target.readInt(intReader, y);
|
||||
// iComponent is usually more useful than y
|
||||
auto dataVariant = callback(cur, iComponent, target);
|
||||
if (ccstd::holds_alternative<int32_t>(dataVariant)) {
|
||||
(target.*intWritter)(y, ccstd::get<int32_t>(dataVariant));
|
||||
} else {
|
||||
CC_LOG_ERROR("mapBuffer, wrong data type, expect int32_t");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataView;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
125
cocos/3d/misc/Buffer.h
Normal file
125
cocos/3d/misc/Buffer.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include "base/std/optional.h"
|
||||
#include "core/DataView.h"
|
||||
#include "renderer/gfx-base/GFXDef.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
// default params behaviors just like on an plain, compact Float32Array
|
||||
template <typename T>
|
||||
void writeBuffer(DataView &target,
|
||||
const ccstd::vector<T> &data,
|
||||
const gfx::Format &format = gfx::Format::R32F,
|
||||
uint32_t offset = 0,
|
||||
uint32_t stride = 0) {
|
||||
const gfx::FormatInfo &info = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(format)];
|
||||
if (stride == 0) {
|
||||
stride = info.size;
|
||||
}
|
||||
const uint32_t componentBytesLength = info.size / info.count;
|
||||
const auto nSeg = static_cast<uint32_t>(floor(data.size() / info.count));
|
||||
|
||||
const uint32_t bytes = info.size / info.count * 8;
|
||||
|
||||
for (uint32_t iSeg = 0; iSeg < nSeg; ++iSeg) {
|
||||
uint32_t x = offset + stride * iSeg;
|
||||
for (uint32_t iComponent = 0; iComponent < info.count; ++iComponent) {
|
||||
const uint32_t y = x + componentBytesLength * iComponent;
|
||||
// default Little-Endian
|
||||
switch (info.type) {
|
||||
case gfx::FormatType::UINT:
|
||||
case gfx::FormatType::UNORM:
|
||||
switch (bytes) {
|
||||
case 8:
|
||||
target.setUint8(y, static_cast<uint8_t>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
case 16:
|
||||
target.setUint16(y, static_cast<uint16_t>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
case 32:
|
||||
target.setUint32(y, static_cast<uint32_t>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
default:
|
||||
CC_ABORT();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case gfx::FormatType::INT:
|
||||
case gfx::FormatType::SNORM:
|
||||
switch (bytes) {
|
||||
case 8:
|
||||
target.setInt8(y, static_cast<int8_t>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
case 16:
|
||||
target.setInt16(y, static_cast<int16_t>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
case 32:
|
||||
target.setInt32(y, static_cast<int32_t>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
default:
|
||||
CC_ABORT();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case gfx::FormatType::UFLOAT:
|
||||
case gfx::FormatType::FLOAT:
|
||||
switch (bytes) {
|
||||
case 8:
|
||||
target.setFloat32(y, static_cast<float>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
case 16:
|
||||
target.setFloat32(y, static_cast<float>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
case 32:
|
||||
target.setFloat32(y, static_cast<float>(data[info.count * iSeg + iComponent]));
|
||||
break;
|
||||
default:
|
||||
CC_ABORT();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
CC_ABORT();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using DataVariant = ccstd::variant<ccstd::monostate, int32_t, float>;
|
||||
using MapBufferCallback = std::function<DataVariant(const DataVariant &cur, uint32_t idx, const DataView &view)>;
|
||||
|
||||
DataView mapBuffer(DataView &target,
|
||||
const MapBufferCallback &callback,
|
||||
ccstd::optional<gfx::Format> aFormat,
|
||||
ccstd::optional<uint32_t> aOffset,
|
||||
ccstd::optional<uint32_t> aLength,
|
||||
ccstd::optional<uint32_t> aStride,
|
||||
DataView *out);
|
||||
|
||||
} // namespace cc
|
||||
63
cocos/3d/misc/BufferBlob.cpp
Normal file
63
cocos/3d/misc/BufferBlob.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#include "3d/misc/BufferBlob.h"
|
||||
#include "core/TypedArray.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
void BufferBlob::setNextAlignment(uint32_t align) {
|
||||
if (align != 0) {
|
||||
const uint32_t remainder = _length % align;
|
||||
if (remainder != 0) {
|
||||
const uint32_t padding = align - remainder;
|
||||
_arrayBufferOrPaddings.emplace_back(padding);
|
||||
_length += padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t BufferBlob::addBuffer(ArrayBuffer *arrayBuffer) {
|
||||
const uint32_t result = _length;
|
||||
_arrayBufferOrPaddings.emplace_back(arrayBuffer);
|
||||
_length += arrayBuffer->byteLength();
|
||||
return result;
|
||||
}
|
||||
|
||||
ArrayBuffer::Ptr BufferBlob::getCombined() {
|
||||
Int8Array result(_length);
|
||||
uint32_t counter = 0;
|
||||
|
||||
for (const auto &arrayBufferOrPadding : _arrayBufferOrPaddings) {
|
||||
if (const auto *p = ccstd::get_if<uint32_t>(&arrayBufferOrPadding)) {
|
||||
counter += *p;
|
||||
} else if (const auto *p = ccstd::get_if<ArrayBuffer::Ptr>(&arrayBufferOrPadding)) {
|
||||
result.set(*p, counter);
|
||||
counter += (*p)->byteLength();
|
||||
}
|
||||
}
|
||||
|
||||
return result.buffer();
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
47
cocos/3d/misc/BufferBlob.h
Normal file
47
cocos/3d/misc/BufferBlob.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "base/std/variant.h"
|
||||
#include "cocos/base/std/container/vector.h"
|
||||
#include "core/ArrayBuffer.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class BufferBlob {
|
||||
public:
|
||||
void setNextAlignment(uint32_t align);
|
||||
|
||||
uint32_t addBuffer(ArrayBuffer *arrayBuffer);
|
||||
|
||||
inline uint32_t getLength() const { return _length; }
|
||||
|
||||
ArrayBuffer::Ptr getCombined();
|
||||
|
||||
private:
|
||||
ccstd::vector<ccstd::variant<ccstd::monostate, ArrayBuffer::Ptr, uint32_t>> _arrayBufferOrPaddings;
|
||||
uint32_t _length{0};
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
483
cocos/3d/misc/CreateMesh.cpp
Normal file
483
cocos/3d/misc/CreateMesh.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
https://www.cocos.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "3d/misc/CreateMesh.h"
|
||||
#include <zlib.h>
|
||||
#include <algorithm>
|
||||
#include "3d/misc/Buffer.h"
|
||||
#include "3d/misc/BufferBlob.h"
|
||||
#include "core/ArrayBuffer.h"
|
||||
#include "core/DataView.h"
|
||||
#include "core/assets/RenderingSubMesh.h"
|
||||
#include "meshopt/meshoptimizer.h"
|
||||
#include "renderer/gfx-base/GFXDef-common.h"
|
||||
|
||||
namespace cc {
|
||||
namespace {
|
||||
gfx::AttributeList defAttrs = {
|
||||
gfx::Attribute{gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F},
|
||||
gfx::Attribute{gfx::ATTR_NAME_NORMAL, gfx::Format::RGB32F},
|
||||
gfx::Attribute{gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F},
|
||||
gfx::Attribute{gfx::ATTR_NAME_TANGENT, gfx::Format::RGBA32F},
|
||||
gfx::Attribute{gfx::ATTR_NAME_COLOR, gfx::Format::RGBA32F},
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Mesh *MeshUtils::createMesh(const IGeometry &geometry, Mesh *out /*= nullptr*/, const ICreateMeshOptions &options /*= {}*/) {
|
||||
if (!out) {
|
||||
out = ccnew Mesh();
|
||||
}
|
||||
|
||||
out->reset(createMeshInfo(geometry, options));
|
||||
return out;
|
||||
}
|
||||
|
||||
Mesh::ICreateInfo MeshUtils::createMeshInfo(const IGeometry &geometry, const ICreateMeshOptions &options /* = {}*/) {
|
||||
// Collect attributes and calculate length of result vertex buffer.
|
||||
gfx::AttributeList attributes;
|
||||
uint32_t stride = 0;
|
||||
struct Channel {
|
||||
uint32_t offset{0};
|
||||
ccstd::vector<float> data; // float?
|
||||
gfx::Attribute attribute;
|
||||
};
|
||||
ccstd::vector<Channel> channels;
|
||||
uint32_t vertCount = 0;
|
||||
|
||||
const gfx::Attribute *attr = nullptr;
|
||||
|
||||
ccstd::vector<float> positions(geometry.positions);
|
||||
|
||||
if (!positions.empty()) {
|
||||
attr = nullptr;
|
||||
if (geometry.attributes.has_value()) {
|
||||
for (const auto &att : geometry.attributes.value()) {
|
||||
if (att.name == gfx::ATTR_NAME_POSITION) {
|
||||
attr = &att;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == nullptr) {
|
||||
attr = &defAttrs[0];
|
||||
}
|
||||
|
||||
attributes.emplace_back(*attr);
|
||||
const auto &info = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(attr->format)];
|
||||
vertCount = std::max(vertCount, static_cast<uint32_t>(std::floor(positions.size() / info.count)));
|
||||
channels.emplace_back(Channel{stride, positions, *attr});
|
||||
stride += info.size;
|
||||
}
|
||||
|
||||
if (geometry.normals.has_value() && !geometry.normals.value().empty()) {
|
||||
attr = nullptr;
|
||||
if (geometry.attributes.has_value()) {
|
||||
for (const auto &att : geometry.attributes.value()) {
|
||||
if (att.name == gfx::ATTR_NAME_NORMAL) {
|
||||
attr = &att;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == nullptr) {
|
||||
attr = &defAttrs[1];
|
||||
}
|
||||
|
||||
attributes.emplace_back(*attr);
|
||||
const auto &info = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(attr->format)];
|
||||
vertCount = std::max(vertCount, static_cast<uint32_t>(std::floor(geometry.normals->size() / info.count)));
|
||||
channels.emplace_back(Channel{stride, geometry.normals.value(), *attr});
|
||||
stride += info.size;
|
||||
}
|
||||
|
||||
if (geometry.uvs.has_value() && !geometry.uvs.value().empty()) {
|
||||
attr = nullptr;
|
||||
if (geometry.attributes.has_value()) {
|
||||
for (const auto &att : geometry.attributes.value()) {
|
||||
if (att.name == gfx::ATTR_NAME_TEX_COORD) {
|
||||
attr = &att;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == nullptr) {
|
||||
attr = &defAttrs[2];
|
||||
}
|
||||
|
||||
attributes.emplace_back(*attr);
|
||||
const auto &info = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(attr->format)];
|
||||
vertCount = std::max(vertCount, static_cast<uint32_t>(std::floor(geometry.uvs->size() / info.count)));
|
||||
channels.emplace_back(Channel{stride, geometry.uvs.value(), *attr});
|
||||
stride += info.size;
|
||||
}
|
||||
|
||||
if (geometry.tangents.has_value() && !geometry.tangents.value().empty()) {
|
||||
attr = nullptr;
|
||||
if (geometry.attributes.has_value()) {
|
||||
for (const auto &att : geometry.attributes.value()) {
|
||||
if (att.name == gfx::ATTR_NAME_TANGENT) {
|
||||
attr = &att;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == nullptr) {
|
||||
attr = &defAttrs[3];
|
||||
}
|
||||
|
||||
attributes.emplace_back(*attr);
|
||||
const auto &info = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(attr->format)];
|
||||
vertCount = std::max(vertCount, static_cast<uint32_t>(std::floor(geometry.tangents->size() / info.count)));
|
||||
channels.emplace_back(Channel{stride, geometry.tangents.value(), *attr});
|
||||
stride += info.size;
|
||||
}
|
||||
|
||||
if (geometry.colors.has_value() && !geometry.colors.value().empty()) {
|
||||
attr = nullptr;
|
||||
if (geometry.attributes.has_value()) {
|
||||
for (const auto &att : geometry.attributes.value()) {
|
||||
if (att.name == gfx::ATTR_NAME_COLOR) {
|
||||
attr = &att;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == nullptr) {
|
||||
attr = &defAttrs[4];
|
||||
}
|
||||
|
||||
attributes.emplace_back(*attr);
|
||||
const auto &info = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(attr->format)];
|
||||
vertCount = std::max(vertCount, static_cast<uint32_t>(std::floor(geometry.colors->size() / info.count)));
|
||||
channels.emplace_back(Channel{stride, geometry.colors.value(), *attr});
|
||||
stride += info.size;
|
||||
}
|
||||
|
||||
if (geometry.customAttributes.has_value()) {
|
||||
for (const auto &ca : geometry.customAttributes.value()) {
|
||||
const auto &info = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(attr->format)];
|
||||
attributes.emplace_back(ca.attr);
|
||||
vertCount = std::max(vertCount, static_cast<uint32_t>(std::floor(ca.values.size() / info.count)));
|
||||
channels.emplace_back(Channel{stride, ca.values, ca.attr});
|
||||
stride += info.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Use this to generate final merged buffer.
|
||||
BufferBlob bufferBlob;
|
||||
|
||||
// Fill vertex buffer.
|
||||
auto *vertexBuffer = ccnew ArrayBuffer(vertCount * stride);
|
||||
DataView vertexBufferView(vertexBuffer);
|
||||
for (const auto &channel : channels) {
|
||||
writeBuffer(vertexBufferView, channel.data, channel.attribute.format, channel.offset, stride);
|
||||
}
|
||||
bufferBlob.setNextAlignment(0);
|
||||
Mesh::IVertexBundle vertexBundle;
|
||||
Mesh::IBufferView buffferView;
|
||||
|
||||
buffferView.offset = bufferBlob.getLength();
|
||||
buffferView.length = static_cast<uint32_t>(vertexBuffer->byteLength());
|
||||
buffferView.count = vertCount;
|
||||
buffferView.stride = stride;
|
||||
vertexBundle.attributes = attributes;
|
||||
vertexBundle.view = buffferView;
|
||||
|
||||
bufferBlob.addBuffer(vertexBuffer);
|
||||
|
||||
// Fill index buffer.
|
||||
ArrayBuffer::Ptr indexBuffer;
|
||||
uint32_t idxCount = 0;
|
||||
const uint32_t idxStride = 2;
|
||||
if (geometry.indices.has_value()) {
|
||||
const ccstd::vector<uint32_t> &indices = geometry.indices.value();
|
||||
idxCount = static_cast<uint32_t>(indices.size());
|
||||
indexBuffer = ccnew ArrayBuffer(idxStride * idxCount);
|
||||
DataView indexBufferView(indexBuffer);
|
||||
writeBuffer(indexBufferView, indices, gfx::Format::R16UI);
|
||||
}
|
||||
|
||||
// Create primitive.
|
||||
Mesh::ISubMesh primitive;
|
||||
primitive.vertexBundelIndices = {0};
|
||||
primitive.primitiveMode = geometry.primitiveMode.has_value() ? geometry.primitiveMode.value() : gfx::PrimitiveMode::TRIANGLE_LIST;
|
||||
|
||||
if (indexBuffer) {
|
||||
bufferBlob.setNextAlignment(idxStride);
|
||||
Mesh::IBufferView bufferView;
|
||||
bufferView.offset = bufferBlob.getLength();
|
||||
bufferView.length = indexBuffer->byteLength();
|
||||
bufferView.count = idxCount;
|
||||
bufferView.stride = idxStride;
|
||||
primitive.indexView = bufferView;
|
||||
bufferBlob.addBuffer(indexBuffer);
|
||||
}
|
||||
|
||||
ccstd::optional<Vec3> minPosition = geometry.minPos;
|
||||
if (!minPosition.has_value() && options.calculateBounds.has_value() && options.calculateBounds.value()) {
|
||||
minPosition = Vec3(std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity());
|
||||
for (uint32_t iVertex = 0; iVertex < vertCount; ++iVertex) {
|
||||
Vec3::min(minPosition.value(), Vec3(positions[iVertex * 3 + 0], positions[iVertex * 3 + 1], positions[iVertex * 3 + 2]), &minPosition.value());
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::optional<Vec3> maxPosition = geometry.maxPos;
|
||||
if (!maxPosition.has_value() && options.calculateBounds.has_value() && options.calculateBounds.value()) {
|
||||
maxPosition = Vec3(-std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity());
|
||||
for (uint32_t iVertex = 0; iVertex < vertCount; ++iVertex) {
|
||||
Vec3::max(maxPosition.value(), Vec3(positions[iVertex * 3 + 0], positions[iVertex * 3 + 1], positions[iVertex * 3 + 2]), &maxPosition.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Create mesh struct
|
||||
Mesh::IStruct meshStruct;
|
||||
meshStruct.vertexBundles = {vertexBundle};
|
||||
meshStruct.primitives = {primitive};
|
||||
|
||||
if (minPosition.has_value()) {
|
||||
meshStruct.minPosition = minPosition.value();
|
||||
}
|
||||
if (maxPosition.has_value()) {
|
||||
meshStruct.maxPosition = maxPosition.value();
|
||||
}
|
||||
|
||||
Mesh::ICreateInfo createInfo;
|
||||
createInfo.structInfo = std::move(meshStruct);
|
||||
createInfo.data = Uint8Array(bufferBlob.getCombined());
|
||||
return createInfo;
|
||||
}
|
||||
|
||||
static inline uint32_t getPadding(uint32_t length, uint32_t align) {
|
||||
if (align > 0U) {
|
||||
const uint32_t remainder = length % align;
|
||||
if (remainder != 0U) {
|
||||
const uint32_t padding = align - remainder;
|
||||
return padding;
|
||||
}
|
||||
}
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
Mesh *MeshUtils::createDynamicMesh(index_t primitiveIndex, const IDynamicGeometry &geometry, Mesh *out /*= nullptr*/, const ICreateDynamicMeshOptions &options /*= {}*/) {
|
||||
if (!out) {
|
||||
out = ccnew Mesh();
|
||||
}
|
||||
|
||||
out->reset(MeshUtils::createDynamicMeshInfo(geometry, options));
|
||||
out->initialize();
|
||||
out->updateSubMesh(primitiveIndex, geometry);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Mesh::ICreateInfo MeshUtils::createDynamicMeshInfo(const IDynamicGeometry &geometry, const ICreateDynamicMeshOptions &options /* = {}*/) {
|
||||
gfx::AttributeList attributes;
|
||||
uint32_t stream = 0U;
|
||||
|
||||
if (!geometry.positions.empty()) {
|
||||
attributes.push_back({gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F, false, stream++, false, 0U});
|
||||
}
|
||||
|
||||
if (geometry.normals.has_value() && !geometry.normals.value().empty()) {
|
||||
attributes.push_back({gfx::ATTR_NAME_NORMAL, gfx::Format::RGB32F, false, stream++, false, 0U});
|
||||
}
|
||||
|
||||
if (geometry.uvs.has_value() && !geometry.uvs.value().empty()) {
|
||||
attributes.push_back({gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F, false, stream++, false, 0U});
|
||||
}
|
||||
|
||||
if (geometry.tangents.has_value() && !geometry.tangents.value().empty()) {
|
||||
attributes.push_back({gfx::ATTR_NAME_TANGENT, gfx::Format::RGBA32F, false, stream++, false, 0U});
|
||||
}
|
||||
|
||||
if (geometry.colors.has_value() && !geometry.colors.value().empty()) {
|
||||
attributes.push_back({gfx::ATTR_NAME_COLOR, gfx::Format::RGBA32F, false, stream++, false, 0U});
|
||||
}
|
||||
|
||||
if (geometry.customAttributes.has_value()) {
|
||||
for (const auto &ca : geometry.customAttributes.value()) {
|
||||
auto attr = ca.attr;
|
||||
attr.stream = stream++;
|
||||
attributes.emplace_back(attr);
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<Mesh::IVertexBundle> vertexBundles;
|
||||
ccstd::vector<Mesh::ISubMesh> primitives;
|
||||
uint32_t dataSize = 0U;
|
||||
|
||||
for (auto i = 0U; i < options.maxSubMeshes; i++) {
|
||||
Mesh::ISubMesh primitive;
|
||||
primitive.primitiveMode = geometry.primitiveMode.has_value() ? geometry.primitiveMode.value() : gfx::PrimitiveMode::TRIANGLE_LIST;
|
||||
|
||||
// add vertex buffers
|
||||
for (const auto &attr : attributes) {
|
||||
const auto &formatInfo = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(attr.format)];
|
||||
uint32_t vertexBufferSize = options.maxSubMeshVertices * formatInfo.size;
|
||||
|
||||
Mesh::IBufferView vertexView = {
|
||||
dataSize,
|
||||
vertexBufferSize,
|
||||
0U,
|
||||
formatInfo.size};
|
||||
|
||||
Mesh::IVertexBundle vertexBundle = {
|
||||
0U,
|
||||
vertexView,
|
||||
{attr}};
|
||||
|
||||
const auto vertexBundleIndex = static_cast<uint32_t>(vertexBundles.size());
|
||||
primitive.vertexBundelIndices.emplace_back(vertexBundleIndex);
|
||||
vertexBundles.emplace_back(vertexBundle);
|
||||
dataSize += vertexBufferSize;
|
||||
}
|
||||
|
||||
// add index buffer
|
||||
uint32_t stride = 0U;
|
||||
if (geometry.indices16.has_value() && !geometry.indices16.value().empty()) {
|
||||
stride = sizeof(uint16_t);
|
||||
} else if (geometry.indices32.has_value() && !geometry.indices32.value().empty()) {
|
||||
stride = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
if (stride > 0U) {
|
||||
dataSize += getPadding(dataSize, stride);
|
||||
uint32_t indexBufferSize = options.maxSubMeshIndices * stride;
|
||||
|
||||
Mesh::IBufferView indexView = {
|
||||
dataSize,
|
||||
indexBufferSize,
|
||||
0U,
|
||||
stride};
|
||||
|
||||
primitive.indexView = indexView;
|
||||
dataSize += indexBufferSize;
|
||||
}
|
||||
|
||||
primitives.emplace_back(primitive);
|
||||
}
|
||||
|
||||
Mesh::IDynamicInfo dynamicInfo = {options.maxSubMeshes,
|
||||
options.maxSubMeshVertices,
|
||||
options.maxSubMeshIndices};
|
||||
|
||||
Mesh::IDynamicStruct dynamicStruct;
|
||||
dynamicStruct.info = dynamicInfo;
|
||||
dynamicStruct.bounds.resize(options.maxSubMeshes);
|
||||
for (auto &bound : dynamicStruct.bounds) {
|
||||
bound.setValid(false);
|
||||
}
|
||||
|
||||
Mesh::IStruct meshStruct;
|
||||
meshStruct.vertexBundles = vertexBundles;
|
||||
meshStruct.primitives = primitives;
|
||||
meshStruct.dynamic = std::move(dynamicStruct);
|
||||
|
||||
Mesh::ICreateInfo createInfo;
|
||||
createInfo.structInfo = std::move(meshStruct);
|
||||
createInfo.data = Uint8Array(dataSize);
|
||||
return createInfo;
|
||||
}
|
||||
|
||||
void MeshUtils::inflateMesh(const Mesh::IStruct &structInfo, Uint8Array &data) {
|
||||
uLongf uncompressedSize = 0U;
|
||||
for (const auto &prim : structInfo.primitives) {
|
||||
if (prim.indexView.has_value()) {
|
||||
uncompressedSize += prim.indexView->length + prim.indexView->stride;
|
||||
}
|
||||
if (prim.cluster.has_value()) {
|
||||
uncompressedSize += prim.cluster->vertexView.length + prim.cluster->vertexView.stride;
|
||||
uncompressedSize += prim.cluster->triangleView.length + prim.cluster->triangleView.stride;
|
||||
uncompressedSize += prim.cluster->clusterView.length + prim.cluster->clusterView.stride;
|
||||
uncompressedSize += prim.cluster->coneView.length + prim.cluster->coneView.stride;
|
||||
}
|
||||
}
|
||||
for (const auto &vb : structInfo.vertexBundles) {
|
||||
uncompressedSize += vb.view.length + vb.view.stride;
|
||||
}
|
||||
auto uncompressedData = Uint8Array(static_cast<uint32_t>(uncompressedSize));
|
||||
auto res = uncompress(uncompressedData.buffer()->getData(), &uncompressedSize, data.buffer()->getData(), data.byteLength());
|
||||
data = Uint8Array(uncompressedData.buffer(), 0, static_cast<uint32_t>(uncompressedSize));
|
||||
}
|
||||
|
||||
void MeshUtils::decodeMesh(Mesh::IStruct &structInfo, Uint8Array &data) {
|
||||
BufferBlob bufferBlob;
|
||||
|
||||
for (auto &bundle : structInfo.vertexBundles) {
|
||||
auto &view = bundle.view;
|
||||
auto bound = view.count * view.stride;
|
||||
auto *buffer = ccnew ArrayBuffer(bound);
|
||||
auto vertex = Uint8Array(data.buffer(), view.offset, view.length);
|
||||
int res = meshopt_decodeVertexBuffer(buffer->getData(), view.count, view.stride, vertex.buffer()->getData() + vertex.byteOffset(), view.length);
|
||||
if (res < 0) {
|
||||
assert(false && "failed to decode vertex buffer");
|
||||
}
|
||||
|
||||
bufferBlob.setNextAlignment(view.stride);
|
||||
Mesh::IVertexBundle vertexBundle;
|
||||
Mesh::IBufferView buffferView;
|
||||
buffferView.offset = bufferBlob.getLength();
|
||||
buffferView.length = bound;
|
||||
buffferView.count = view.count;
|
||||
buffferView.stride = view.stride;
|
||||
bufferBlob.addBuffer(buffer);
|
||||
|
||||
bundle.view = buffferView;
|
||||
}
|
||||
|
||||
for (auto &primitive : structInfo.primitives) {
|
||||
if (!primitive.indexView.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto view = *primitive.indexView;
|
||||
auto bound = view.count * view.stride;
|
||||
auto *buffer = ccnew ArrayBuffer(bound);
|
||||
auto index = DataView(data.buffer(), view.offset, view.length);
|
||||
int res = meshopt_decodeIndexBuffer(buffer->getData(), view.count, view.stride, index.buffer()->getData() + index.byteOffset(), view.length);
|
||||
if (res < 0) {
|
||||
assert(false && "failed to decode index buffer");
|
||||
}
|
||||
|
||||
bufferBlob.setNextAlignment(view.stride);
|
||||
Mesh::IBufferView buffferView;
|
||||
buffferView.offset = bufferBlob.getLength();
|
||||
buffferView.length = bound;
|
||||
buffferView.count = view.count;
|
||||
buffferView.stride = view.stride;
|
||||
bufferBlob.addBuffer(buffer);
|
||||
|
||||
primitive.indexView = buffferView;
|
||||
}
|
||||
|
||||
data = Uint8Array(bufferBlob.getCombined());
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
101
cocos/3d/misc/CreateMesh.h
Normal file
101
cocos/3d/misc/CreateMesh.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "base/std/optional.h"
|
||||
|
||||
#include "3d/assets/Mesh.h"
|
||||
#include "primitive/PrimitiveDefine.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
struct ICreateMeshOptions {
|
||||
/**
|
||||
* @en calculate mesh's aabb or not
|
||||
* @zh 是否计算模型的包围盒。
|
||||
*/
|
||||
ccstd::optional<bool> calculateBounds;
|
||||
};
|
||||
|
||||
struct ICreateDynamicMeshOptions {
|
||||
/**
|
||||
* @en max submesh count
|
||||
* @zh 最大子模型个数。
|
||||
*/
|
||||
uint32_t maxSubMeshes{1U};
|
||||
|
||||
/**
|
||||
* @en max submesh vertex count
|
||||
* @zh 子模型最大顶点个数。
|
||||
*/
|
||||
uint32_t maxSubMeshVertices{1024U};
|
||||
|
||||
/**
|
||||
* @en max submesh index count
|
||||
* @zh 子模型最大索引个数。
|
||||
*/
|
||||
uint32_t maxSubMeshIndices{1024U};
|
||||
};
|
||||
|
||||
/**
|
||||
* @en mesh utility class, use to create mesh.
|
||||
* @zh 网格工具类,用于创建网格。
|
||||
*/
|
||||
class MeshUtils {
|
||||
public:
|
||||
/**
|
||||
* @en create a static mesh.
|
||||
* @zh 创建一个静态网格。
|
||||
*/
|
||||
static Mesh *createMesh(const IGeometry &geometry, Mesh *out = nullptr, const ICreateMeshOptions &options = {});
|
||||
|
||||
/**
|
||||
* @en create a static mesh ICreateInfo.
|
||||
* @zh 创建一个静态网格ICreateInfo。
|
||||
*/
|
||||
static Mesh::ICreateInfo createMeshInfo(const IGeometry &geometry, const ICreateMeshOptions &options = {});
|
||||
|
||||
/**
|
||||
* @en create a dynamic mesh.
|
||||
* @zh 创建一个动态网格。
|
||||
*/
|
||||
static Mesh *createDynamicMesh(index_t primitiveIndex, const IDynamicGeometry &geometry, Mesh *out = nullptr, const ICreateDynamicMeshOptions &options = {});
|
||||
|
||||
/**
|
||||
* @en create a dynamic mesh ICreateInfo.
|
||||
* @zh 创建一个动态网格ICreateInfo。
|
||||
*/
|
||||
static Mesh::ICreateInfo createDynamicMeshInfo(const IDynamicGeometry &geometry, const ICreateDynamicMeshOptions &options = {});
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void inflateMesh(const Mesh::IStruct &structInfo, Uint8Array &data);
|
||||
|
||||
static void decodeMesh(Mesh::IStruct &structInfo, Uint8Array &data);
|
||||
|
||||
static void dequantizeMesh(Mesh::IStruct &structInfo, Uint8Array &data);
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
258
cocos/3d/models/BakedSkinningModel.cpp
Normal file
258
cocos/3d/models/BakedSkinningModel.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#include "3d/models/BakedSkinningModel.h"
|
||||
#include "3d/assets/Mesh.h"
|
||||
//#include "3d/skeletal-animation/DataPoolManager.h"
|
||||
#include "core/Root.h"
|
||||
#include "scene/Model.h"
|
||||
#include "scene/SubModel.h"
|
||||
|
||||
namespace {
|
||||
const cc::gfx::SamplerInfo JOINT_TEXTURE_SAMPLER_INFO{
|
||||
cc::gfx::Filter::POINT,
|
||||
cc::gfx::Filter::POINT,
|
||||
cc::gfx::Filter::NONE,
|
||||
cc::gfx::Address::CLAMP,
|
||||
cc::gfx::Address::CLAMP,
|
||||
cc::gfx::Address::CLAMP,
|
||||
};
|
||||
|
||||
ccstd::vector<cc::scene::IMacroPatch> myPatches{
|
||||
{"CC_USE_SKINNING", true},
|
||||
{"CC_USE_BAKED_ANIMATION", true}};
|
||||
|
||||
const ccstd::string INST_JOINT_ANIM_INFO = "a_jointAnimInfo";
|
||||
} // namespace
|
||||
namespace cc {
|
||||
|
||||
BakedSkinningModel::BakedSkinningModel()
|
||||
//, _dataPoolManager(Root::getInstance()->getDataPoolManager())
|
||||
{
|
||||
_type = Model::Type::BAKED_SKINNING;
|
||||
_jointMedium.jointTextureInfo.reset(4);
|
||||
// JSB uses _dataPoolManager in JS and the data is synchronized by syncDataForJS & syncAnimInfoForJS
|
||||
// _jointMedium.animInfo = _dataPoolManager->jointAnimationInfo->getData();
|
||||
}
|
||||
|
||||
void BakedSkinningModel::destroy() {
|
||||
// CC_SAFE_DELETE(uploadedAnim);
|
||||
_jointMedium.boundsInfo.clear();
|
||||
|
||||
if (_jointMedium.buffer != nullptr) {
|
||||
CC_SAFE_DESTROY_NULL(_jointMedium.buffer);
|
||||
}
|
||||
if (_jointMedium.texture.has_value()) {
|
||||
CC_SAFE_DELETE(_jointMedium.texture.value());
|
||||
}
|
||||
applyJointTexture(ccstd::nullopt);
|
||||
Super::destroy();
|
||||
}
|
||||
|
||||
void BakedSkinningModel::bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh) {
|
||||
_skeleton = skeleton;
|
||||
_mesh = mesh;
|
||||
if (skeleton == nullptr || skinningRoot == nullptr || mesh == nullptr) return;
|
||||
setTransform(skinningRoot);
|
||||
|
||||
// JSB uses _dataPoolManager in JS and the data is synchronized by syncDataForJS & syncAnimInfoForJS
|
||||
// _jointMedium.animInfo = _dataPoolManager->jointAnimationInfo->getData(skinningRoot->getUuid());
|
||||
|
||||
if (_jointMedium.buffer == nullptr) {
|
||||
_jointMedium.buffer = _device->createBuffer({
|
||||
gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST,
|
||||
gfx::MemoryUsageBit::DEVICE,
|
||||
pipeline::UBOSkinning::size,
|
||||
pipeline::UBOSkinning::size,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BakedSkinningModel::updateTransform(uint32_t stamp) {
|
||||
Super::updateTransform(stamp);
|
||||
if (!_isUploadedAnim) {
|
||||
return;
|
||||
}
|
||||
IAnimInfo &animInfo = _jointMedium.animInfo;
|
||||
geometry::AABB *skelBound = nullptr;
|
||||
const float *curFrame = animInfo.curFrame;
|
||||
// float curFrame = info.data[0];
|
||||
auto index = static_cast<index_t>(std::roundf(*curFrame));
|
||||
if (!_jointMedium.boundsInfo.empty() && index < _jointMedium.boundsInfo.size()) {
|
||||
skelBound = &_jointMedium.boundsInfo[index].value();
|
||||
}
|
||||
|
||||
if (_worldBounds && skelBound != nullptr) {
|
||||
Node *node = getTransform();
|
||||
skelBound->transform(node->getWorldMatrix(), _worldBounds);
|
||||
_worldBoundsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void BakedSkinningModel::updateUBOs(uint32_t stamp) {
|
||||
Super::updateUBOs(stamp);
|
||||
|
||||
IAnimInfo &info = _jointMedium.animInfo;
|
||||
const int idx = _instAnimInfoIdx;
|
||||
const float *curFrame = info.curFrame;
|
||||
bool hasNonInstancingPass = false;
|
||||
for (const auto &subModel : _subModels) {
|
||||
if (idx >= 0) {
|
||||
auto &views = subModel->getInstancedAttributeBlock().views[idx];
|
||||
setTypedArrayValue(views, 0, *curFrame);
|
||||
} else {
|
||||
hasNonInstancingPass = true;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t frameDataBytes = info.frameDataBytes;
|
||||
if (hasNonInstancingPass && *info.dirtyForJSB != 0) {
|
||||
info.buffer->update(curFrame, frameDataBytes);
|
||||
*info.dirtyForJSB = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BakedSkinningModel::applyJointTexture(const ccstd::optional<IJointTextureHandle *> &texture) {
|
||||
auto oldTex = _jointMedium.texture;
|
||||
if (oldTex.has_value() && texture.has_value() && (&oldTex.value() != &texture.value())) {
|
||||
// _dataPoolManager->jointTexturePool->releaseHandle(oldTex.value());
|
||||
}
|
||||
_jointMedium.texture = texture;
|
||||
if (!texture.has_value()) {
|
||||
return;
|
||||
}
|
||||
auto *textureHandle = texture.value();
|
||||
auto *buffer = _jointMedium.buffer.get();
|
||||
auto &jointTextureInfo = _jointMedium.jointTextureInfo;
|
||||
jointTextureInfo[0] = static_cast<float>(textureHandle->handle.texture->getWidth());
|
||||
jointTextureInfo[1] = static_cast<float>(_skeleton->getJoints().size());
|
||||
jointTextureInfo[2] = static_cast<float>(textureHandle->pixelOffset) + 0.1F; // guard against floor() underflow
|
||||
jointTextureInfo[3] = 1 / jointTextureInfo[0];
|
||||
updateInstancedJointTextureInfo();
|
||||
if (buffer != nullptr) {
|
||||
buffer->update(&jointTextureInfo[0], jointTextureInfo.byteLength());
|
||||
}
|
||||
auto *tex = textureHandle->handle.texture;
|
||||
|
||||
for (const auto &subModel : _subModels) {
|
||||
auto *descriptorSet = subModel->getDescriptorSet();
|
||||
descriptorSet->bindTexture(pipeline::JOINTTEXTURE::BINDING, tex);
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> BakedSkinningModel::getMacroPatches(index_t subModelIndex) {
|
||||
auto patches = Super::getMacroPatches(subModelIndex);
|
||||
patches.reserve(patches.size() + myPatches.size());
|
||||
patches.insert(std::end(patches), std::begin(myPatches), std::end(myPatches));
|
||||
return patches;
|
||||
}
|
||||
|
||||
void BakedSkinningModel::updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) {
|
||||
Super::updateLocalDescriptors(subModelIndex, descriptorSet);
|
||||
gfx::Buffer *buffer = _jointMedium.buffer;
|
||||
auto &texture = _jointMedium.texture;
|
||||
const IAnimInfo &animInfo = _jointMedium.animInfo;
|
||||
descriptorSet->bindBuffer(pipeline::UBOSkinningTexture::BINDING, buffer);
|
||||
descriptorSet->bindBuffer(pipeline::UBOSkinningAnimation::BINDING, animInfo.buffer);
|
||||
if (texture.has_value()) {
|
||||
auto *sampler = _device->getSampler(JOINT_TEXTURE_SAMPLER_INFO);
|
||||
descriptorSet->bindTexture(pipeline::JOINTTEXTURE::BINDING, texture.value()->handle.texture);
|
||||
descriptorSet->bindSampler(pipeline::JOINTTEXTURE::BINDING, sampler);
|
||||
}
|
||||
}
|
||||
|
||||
void BakedSkinningModel::updateInstancedAttributes(const ccstd::vector<gfx::Attribute> &attributes, scene::SubModel *subModel) {
|
||||
Super::updateInstancedAttributes(attributes, subModel);
|
||||
_instAnimInfoIdx = subModel->getInstancedAttributeIndex(INST_JOINT_ANIM_INFO);
|
||||
updateInstancedJointTextureInfo();
|
||||
}
|
||||
|
||||
void BakedSkinningModel::updateInstancedJointTextureInfo() {
|
||||
const auto &jointTextureInfo = _jointMedium.jointTextureInfo;
|
||||
const IAnimInfo &animInfo = _jointMedium.animInfo;
|
||||
const index_t idx = _instAnimInfoIdx;
|
||||
for (const auto &subModel : _subModels) {
|
||||
auto &views = subModel->getInstancedAttributeBlock().views;
|
||||
if (idx >= 0 && !views.empty()) {
|
||||
auto &view = views[idx];
|
||||
setTypedArrayValue(view, 0, *animInfo.curFrame); //NOTE: curFrame is only used in JSB.
|
||||
setTypedArrayValue(view, 1, jointTextureInfo[1]);
|
||||
setTypedArrayValue(view, 2, jointTextureInfo[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BakedSkinningModel::syncAnimInfoForJS(gfx::Buffer *buffer, const Float32Array &data, Uint8Array &dirty) {
|
||||
_jointMedium.animInfo.buffer = buffer;
|
||||
_jointMedium.animInfo.curFrame = &data[0];
|
||||
_jointMedium.animInfo.frameDataBytes = data.byteLength();
|
||||
_jointMedium.animInfo.dirtyForJSB = &dirty[0];
|
||||
}
|
||||
|
||||
void BakedSkinningModel::syncDataForJS(const ccstd::vector<ccstd::optional<geometry::AABB>> &boundsInfo,
|
||||
const ccstd::optional<geometry::AABB> &modelBound,
|
||||
float jointTextureInfo0,
|
||||
float jointTextureInfo1,
|
||||
float jointTextureInfo2,
|
||||
float jointTextureInfo3,
|
||||
gfx::Texture *tex,
|
||||
const Float32Array &animInfoData) {
|
||||
_jointMedium.boundsInfo = boundsInfo;
|
||||
|
||||
if (modelBound.has_value()) {
|
||||
const geometry::AABB &modelBounldValue = modelBound.value();
|
||||
_modelBounds->set(modelBounldValue.center, modelBounldValue.halfExtents);
|
||||
} else {
|
||||
_modelBounds = nullptr;
|
||||
}
|
||||
|
||||
_jointMedium.jointTextureInfo[0] = jointTextureInfo0;
|
||||
_jointMedium.jointTextureInfo[1] = jointTextureInfo1;
|
||||
_jointMedium.jointTextureInfo[2] = jointTextureInfo2;
|
||||
_jointMedium.jointTextureInfo[3] = jointTextureInfo3;
|
||||
|
||||
_jointMedium.animInfo.curFrame = &animInfoData[0];
|
||||
_jointMedium.animInfo.frameDataBytes = animInfoData.byteLength();
|
||||
|
||||
if (_jointMedium.texture.has_value()) {
|
||||
delete _jointMedium.texture.value();
|
||||
_jointMedium.texture = ccstd::nullopt;
|
||||
}
|
||||
IJointTextureHandle *textureInfo = IJointTextureHandle::createJoinTextureHandle();
|
||||
textureInfo->handle.texture = tex;
|
||||
_jointMedium.texture = textureInfo;
|
||||
|
||||
updateInstancedJointTextureInfo();
|
||||
|
||||
auto *buffer = _jointMedium.buffer.get();
|
||||
if (buffer != nullptr) {
|
||||
buffer->update(&_jointMedium.jointTextureInfo[0], _jointMedium.jointTextureInfo.byteLength());
|
||||
}
|
||||
|
||||
for (const auto &subModel : _subModels) {
|
||||
auto *descriptorSet = subModel->getDescriptorSet();
|
||||
descriptorSet->bindTexture(pipeline::JOINTTEXTURE::BINDING, tex);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
101
cocos/3d/models/BakedSkinningModel.h
Normal file
101
cocos/3d/models/BakedSkinningModel.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "3d/assets/Skeleton.h"
|
||||
#include "3d/models/MorphModel.h"
|
||||
#include "3d/skeletal-animation/SkeletalAnimationUtils.h"
|
||||
#include "gfx-base/GFXDef-common.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
namespace gfx {
|
||||
class Texture;
|
||||
}
|
||||
|
||||
class DataPoolManager;
|
||||
|
||||
struct BakedJointInfo {
|
||||
IntrusivePtr<gfx::Buffer> buffer;
|
||||
Float32Array jointTextureInfo;
|
||||
ccstd::optional<IJointTextureHandle *> texture;
|
||||
IAnimInfo animInfo;
|
||||
ccstd::vector<ccstd::optional<geometry::AABB>> boundsInfo;
|
||||
};
|
||||
|
||||
class BakedSkinningModel final : public MorphModel {
|
||||
public:
|
||||
using Super = MorphModel;
|
||||
BakedSkinningModel();
|
||||
~BakedSkinningModel() override = default;
|
||||
void destroy() override;
|
||||
ccstd::vector<scene::IMacroPatch> getMacroPatches(index_t subModelIndex) override;
|
||||
void updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) override;
|
||||
void updateTransform(uint32_t stamp) override;
|
||||
void updateUBOs(uint32_t stamp) override;
|
||||
void updateInstancedAttributes(const ccstd::vector<gfx::Attribute> &attributes, scene::SubModel *subModel) override;
|
||||
void updateInstancedJointTextureInfo();
|
||||
// void uploadAnimation(AnimationClip *anim); // TODO(xwx): AnimationClip not define
|
||||
|
||||
void bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh);
|
||||
|
||||
inline void updateModelBounds(geometry::AABB *modelBounds) {
|
||||
if (modelBounds == nullptr) {
|
||||
return;
|
||||
}
|
||||
_modelBounds->setValid(true);
|
||||
_modelBounds->set(modelBounds->getCenter(), modelBounds->getHalfExtents());
|
||||
}
|
||||
|
||||
void syncAnimInfoForJS(gfx::Buffer *buffer, const Float32Array &data, Uint8Array &dirty);
|
||||
void syncDataForJS(const ccstd::vector<ccstd::optional<geometry::AABB>> &boundsInfo,
|
||||
const ccstd::optional<geometry::AABB> &modelBound,
|
||||
float jointTextureInfo0,
|
||||
float jointTextureInfo1,
|
||||
float jointTextureInfo2,
|
||||
float jointTextureInfo3,
|
||||
gfx::Texture *tex,
|
||||
const Float32Array &animInfoData);
|
||||
|
||||
void setUploadedAnimForJS(bool value) { _isUploadedAnim = value; }
|
||||
|
||||
protected:
|
||||
void applyJointTexture(const ccstd::optional<IJointTextureHandle *> &texture);
|
||||
|
||||
private:
|
||||
BakedJointInfo _jointMedium;
|
||||
index_t _instAnimInfoIdx{CC_INVALID_INDEX};
|
||||
// IntrusivePtr<DataPoolManager> _dataPoolManager;
|
||||
IntrusivePtr<Skeleton> _skeleton;
|
||||
IntrusivePtr<Mesh> _mesh;
|
||||
// AnimationClip* uploadedAnim;
|
||||
bool _isUploadedAnim{false};
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(BakedSkinningModel);
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
64
cocos/3d/models/MorphModel.cpp
Normal file
64
cocos/3d/models/MorphModel.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#include "3d/models/MorphModel.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> MorphModel::getMacroPatches(index_t subModelIndex) {
|
||||
ccstd::vector<scene::IMacroPatch> superMacroPatches = Super::getMacroPatches(subModelIndex);
|
||||
if (_morphRenderingInstance) {
|
||||
ccstd::vector<scene::IMacroPatch> morphInstanceMacroPatches = _morphRenderingInstance->requiredPatches(subModelIndex);
|
||||
if (!morphInstanceMacroPatches.empty()) {
|
||||
if (!superMacroPatches.empty()) {
|
||||
morphInstanceMacroPatches.reserve(morphInstanceMacroPatches.size() + superMacroPatches.size());
|
||||
morphInstanceMacroPatches.insert(morphInstanceMacroPatches.end(), superMacroPatches.begin(), superMacroPatches.end());
|
||||
}
|
||||
return morphInstanceMacroPatches;
|
||||
}
|
||||
}
|
||||
return superMacroPatches;
|
||||
}
|
||||
|
||||
void MorphModel::initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) {
|
||||
Super::initSubModel(idx, subMeshData, launderMaterial(mat));
|
||||
}
|
||||
|
||||
void MorphModel::destroy() {
|
||||
Super::destroy();
|
||||
_morphRenderingInstance = nullptr; //minggo: should delete it?
|
||||
}
|
||||
|
||||
void MorphModel::setSubModelMaterial(index_t idx, Material *mat) {
|
||||
Super::setSubModelMaterial(idx, launderMaterial(mat));
|
||||
}
|
||||
|
||||
void MorphModel::updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) {
|
||||
Super::updateLocalDescriptors(subModelIndex, descriptorSet);
|
||||
|
||||
if (_morphRenderingInstance) {
|
||||
_morphRenderingInstance->adaptPipelineState(subModelIndex, descriptorSet);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
57
cocos/3d/models/MorphModel.h
Normal file
57
cocos/3d/models/MorphModel.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include "3d/assets/MorphRendering.h"
|
||||
#include "scene/Define.h"
|
||||
#include "scene/Model.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class MorphModel : public scene::Model {
|
||||
public:
|
||||
using Super = scene::Model;
|
||||
|
||||
MorphModel() = default;
|
||||
~MorphModel() override = default;
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(MorphModel);
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> getMacroPatches(index_t subModelIndex) override;
|
||||
void initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) override;
|
||||
void destroy() override;
|
||||
void setSubModelMaterial(index_t idx, Material *mat) override;
|
||||
|
||||
inline void setMorphRendering(MorphRenderingInstance *morphRendering) { _morphRenderingInstance = morphRendering; }
|
||||
|
||||
protected:
|
||||
void updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) override;
|
||||
|
||||
private:
|
||||
inline Material *launderMaterial(Material *material) { return material; } //NOLINT(readability-convert-member-functions-to-static)
|
||||
|
||||
IntrusivePtr<MorphRenderingInstance> _morphRenderingInstance;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
359
cocos/3d/models/SkinningModel.cpp
Normal file
359
cocos/3d/models/SkinningModel.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
#include "3d/models/SkinningModel.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "3d/assets/Mesh.h"
|
||||
#include "3d/assets/Skeleton.h"
|
||||
#include "core/platform/Debug.h"
|
||||
#include "core/scene-graph/Node.h"
|
||||
#include "renderer/gfx-base/GFXBuffer.h"
|
||||
#include "scene/Pass.h"
|
||||
#include "scene/RenderScene.h"
|
||||
|
||||
const uint32_t REALTIME_JOINT_TEXTURE_WIDTH = 256;
|
||||
const uint32_t REALTIME_JOINT_TEXTURE_HEIGHT = 3;
|
||||
|
||||
namespace {
|
||||
void getRelevantBuffers(ccstd::vector<index_t> &outIndices, ccstd::vector<int32_t> &outBuffers, const ccstd::vector<ccstd::vector<int32_t>> &jointMaps, int32_t targetJoint) {
|
||||
for (int32_t i = 0; i < jointMaps.size(); i++) {
|
||||
index_t index = CC_INVALID_INDEX;
|
||||
for (int32_t j = 0; j < jointMaps[i].size(); j++) {
|
||||
if (jointMaps[i][j] == targetJoint) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
outBuffers.emplace_back(i);
|
||||
outIndices.emplace_back(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<cc::scene::IMacroPatch> uniformPatches{{"CC_USE_SKINNING", true}, {"CC_USE_REAL_TIME_JOINT_TEXTURE", false}};
|
||||
ccstd::vector<cc::scene::IMacroPatch> texturePatches{{"CC_USE_SKINNING", true}, {"CC_USE_REAL_TIME_JOINT_TEXTURE", true}};
|
||||
|
||||
} // namespace
|
||||
namespace cc {
|
||||
|
||||
SkinningModel::SkinningModel() {
|
||||
_type = Model::Type::SKINNING;
|
||||
}
|
||||
SkinningModel::~SkinningModel() {
|
||||
releaseData();
|
||||
}
|
||||
|
||||
void SkinningModel::destroy() {
|
||||
bindSkeleton(nullptr, nullptr, nullptr);
|
||||
releaseData();
|
||||
Super::destroy();
|
||||
}
|
||||
|
||||
void SkinningModel::bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh) {
|
||||
for (const JointInfo &joint : _joints) {
|
||||
deleteTransform(joint.target);
|
||||
}
|
||||
_bufferIndices.clear();
|
||||
_joints.clear();
|
||||
|
||||
if (!skeleton || !skinningRoot || !mesh) return;
|
||||
auto jointCount = static_cast<uint32_t>(skeleton->getJoints().size());
|
||||
_realTimeTextureMode = pipeline::SkinningJointCapacity::jointUniformCapacity < jointCount;
|
||||
setTransform(skinningRoot);
|
||||
auto boneSpaceBounds = mesh->getBoneSpaceBounds(skeleton);
|
||||
const auto &jointMaps = mesh->getStruct().jointMaps;
|
||||
ensureEnoughBuffers((jointMaps.has_value() && !jointMaps->empty()) ? static_cast<uint32_t>(jointMaps->size()) : 1);
|
||||
_bufferIndices = mesh->getJointBufferIndices();
|
||||
initRealTimeJointTexture();
|
||||
for (index_t index = 0; index < skeleton->getJoints().size(); ++index) {
|
||||
geometry::AABB *bound = boneSpaceBounds[index];
|
||||
auto *target = skinningRoot->getChildByPath(skeleton->getJoints()[index]);
|
||||
if (!bound || !target) continue;
|
||||
|
||||
auto *transform = cc::getTransform(target, skinningRoot);
|
||||
const Mat4 &bindPose = skeleton->getBindposes()[index];
|
||||
ccstd::vector<index_t> indices;
|
||||
ccstd::vector<index_t> buffers;
|
||||
if (!jointMaps.has_value()) {
|
||||
indices.emplace_back(index);
|
||||
buffers.emplace_back(0);
|
||||
} else {
|
||||
getRelevantBuffers(indices, buffers, jointMaps.value(), index);
|
||||
}
|
||||
|
||||
JointInfo jointInfo;
|
||||
jointInfo.bound = bound;
|
||||
jointInfo.target = target;
|
||||
jointInfo.bindpose = bindPose;
|
||||
jointInfo.transform = transform;
|
||||
jointInfo.buffers = std::move(buffers);
|
||||
jointInfo.indices = std::move(indices);
|
||||
_joints.emplace_back(std::move(jointInfo));
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningModel::updateTransform(uint32_t stamp) {
|
||||
auto *root = getTransform();
|
||||
if (root->getChangedFlags() || root->isTransformDirty()) {
|
||||
root->updateWorldTransform();
|
||||
_localDataUpdated = true;
|
||||
}
|
||||
Vec3 v3Min{INFINITY, INFINITY, INFINITY};
|
||||
Vec3 v3Max{-INFINITY, -INFINITY, -INFINITY};
|
||||
geometry::AABB ab1;
|
||||
Vec3 v31;
|
||||
Vec3 v32;
|
||||
for (JointInfo &jointInfo : _joints) {
|
||||
auto &transform = jointInfo.transform;
|
||||
Mat4 worldMatrix = cc::getWorldMatrix(transform, static_cast<int32_t>(stamp));
|
||||
jointInfo.bound->transform(worldMatrix, &ab1);
|
||||
ab1.getBoundary(&v31, &v32);
|
||||
Vec3::min(v3Min, v31, &v3Min);
|
||||
Vec3::max(v3Max, v32, &v3Max);
|
||||
}
|
||||
if (_modelBounds && _modelBounds->isValid() && _worldBounds) {
|
||||
geometry::AABB::fromPoints(v3Min, v3Max, _modelBounds);
|
||||
_modelBounds->transform(root->getWorldMatrix(), _worldBounds);
|
||||
_worldBoundsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningModel::updateUBOs(uint32_t stamp) {
|
||||
Super::updateUBOs(stamp);
|
||||
uint32_t bIdx = 0;
|
||||
Mat4 mat4;
|
||||
for (const JointInfo &jointInfo : _joints) {
|
||||
Mat4::multiply(jointInfo.transform->world, jointInfo.bindpose, &mat4);
|
||||
for (uint32_t buffer : jointInfo.buffers) {
|
||||
uploadJointData(jointInfo.indices[bIdx] * 12, mat4, _dataArray[buffer]);
|
||||
bIdx++;
|
||||
}
|
||||
bIdx = 0;
|
||||
}
|
||||
if (_realTimeTextureMode) {
|
||||
updateRealTimeJointTextureBuffer();
|
||||
} else {
|
||||
bIdx = 0;
|
||||
for (gfx::Buffer *buffer : _buffers) {
|
||||
buffer->update(_dataArray[bIdx], buffer->getSize());
|
||||
bIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningModel::initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) {
|
||||
const auto &original = subMeshData->getVertexBuffers();
|
||||
auto &iaInfo = subMeshData->getIaInfo();
|
||||
iaInfo.vertexBuffers = subMeshData->getJointMappedBuffers();
|
||||
Super::initSubModel(idx, subMeshData, mat);
|
||||
iaInfo.vertexBuffers = original;
|
||||
}
|
||||
|
||||
ccstd::vector<scene::IMacroPatch> SkinningModel::getMacroPatches(index_t subModelIndex) {
|
||||
auto patches = Super::getMacroPatches(subModelIndex);
|
||||
auto myPatches = uniformPatches;
|
||||
if (_realTimeTextureMode) {
|
||||
myPatches = texturePatches;
|
||||
}
|
||||
if (!patches.empty()) {
|
||||
patches.reserve(myPatches.size() + patches.size());
|
||||
patches.insert(std::begin(patches), std::begin(myPatches), std::end(myPatches));
|
||||
return patches;
|
||||
}
|
||||
|
||||
return myPatches;
|
||||
}
|
||||
|
||||
void SkinningModel::uploadJointData(uint32_t base, const Mat4 &mat, float *dst) {
|
||||
memcpy(reinterpret_cast<void *>(dst + base), mat.m, sizeof(float) * 12);
|
||||
dst[base + 3] = mat.m[12];
|
||||
dst[base + 7] = mat.m[13];
|
||||
dst[base + 11] = mat.m[14];
|
||||
}
|
||||
|
||||
void SkinningModel::updateLocalDescriptors(index_t submodelIdx, gfx::DescriptorSet *descriptorset) {
|
||||
Super::updateLocalDescriptors(submodelIdx, descriptorset);
|
||||
uint32_t idx = _bufferIndices[submodelIdx];
|
||||
if (!_realTimeTextureMode) {
|
||||
gfx::Buffer *buffer = _buffers[idx];
|
||||
if (buffer) {
|
||||
descriptorset->bindBuffer(pipeline::UBOSkinning::BINDING, buffer);
|
||||
}
|
||||
} else {
|
||||
bindRealTimeJointTexture(idx, descriptorset);
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningModel::updateInstancedAttributes(const ccstd::vector<gfx::Attribute> &attributes, scene::SubModel *subModel) {
|
||||
auto *pass = subModel->getPass(0);
|
||||
if (pass->getBatchingScheme() != scene::BatchingSchemes::NONE) {
|
||||
// TODO(holycanvas): #9203 better to print the complete path instead of only the current node
|
||||
debug::warnID(3936, getNode()->getName());
|
||||
CC_LOG_WARNING("pass batchingScheme is none, %s", getNode()->getName().c_str());
|
||||
}
|
||||
Super::updateInstancedAttributes(attributes, subModel);
|
||||
}
|
||||
|
||||
void SkinningModel::ensureEnoughBuffers(uint32_t count) {
|
||||
if (!_buffers.empty()) {
|
||||
for (gfx::Buffer *buffer : _buffers) {
|
||||
CC_SAFE_DESTROY(buffer);
|
||||
}
|
||||
_buffers.clear();
|
||||
}
|
||||
if (!_dataArray.empty()) {
|
||||
for (auto *data : _dataArray) {
|
||||
CC_SAFE_DELETE_ARRAY(data);
|
||||
}
|
||||
_dataArray.clear();
|
||||
}
|
||||
_dataArray.resize(count);
|
||||
if (!_realTimeTextureMode) {
|
||||
_buffers.resize(count);
|
||||
uint32_t length = pipeline::UBOSkinning::count;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
_buffers[i] = _device->createBuffer({
|
||||
gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST,
|
||||
gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE,
|
||||
pipeline::UBOSkinning::size,
|
||||
pipeline::UBOSkinning::size,
|
||||
});
|
||||
_dataArray[i] = new float[length];
|
||||
memset(_dataArray[i], 0, sizeof(float) * length);
|
||||
}
|
||||
} else {
|
||||
uint32_t length = 4 * REALTIME_JOINT_TEXTURE_WIDTH * REALTIME_JOINT_TEXTURE_HEIGHT;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (_dataArray[i] == nullptr) {
|
||||
_dataArray[i] = new float[length];
|
||||
memset(_dataArray[i], 0, sizeof(float) * length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningModel::initRealTimeJointTexture() {
|
||||
CC_SAFE_DELETE(_realTimeJointTexture);
|
||||
if (!_realTimeTextureMode) return;
|
||||
_realTimeJointTexture = ccnew RealTimeJointTexture;
|
||||
auto *device = gfx::Device::getInstance();
|
||||
uint32_t texWidth = REALTIME_JOINT_TEXTURE_WIDTH;
|
||||
uint32_t texHeight = REALTIME_JOINT_TEXTURE_HEIGHT;
|
||||
gfx::Format textureFormat = gfx::Format::RGBA32F;
|
||||
|
||||
gfx::FormatFeature formatFeature = device->getFormatFeatures(gfx::Format::RGBA32F);
|
||||
if (!(formatFeature & gfx::FormatFeature::SAMPLED_TEXTURE)) {
|
||||
textureFormat = gfx::Format::RGBA8;
|
||||
texWidth = texWidth * 4;
|
||||
}
|
||||
uint32_t length = 4 * REALTIME_JOINT_TEXTURE_WIDTH * REALTIME_JOINT_TEXTURE_HEIGHT;
|
||||
const size_t count = _dataArray.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
gfx::TextureInfo textureInfo;
|
||||
textureInfo.width = texWidth;
|
||||
textureInfo.height = texHeight;
|
||||
textureInfo.usage = gfx::TextureUsageBit::STORAGE | gfx::TextureUsageBit::SAMPLED | gfx::TextureUsageBit::TRANSFER_SRC | gfx::TextureUsageBit::TRANSFER_DST;
|
||||
textureInfo.format = textureFormat;
|
||||
IntrusivePtr<gfx::Texture> texture = device->createTexture(textureInfo);
|
||||
_realTimeJointTexture->textures.push_back(texture);
|
||||
}
|
||||
_realTimeJointTexture->buffer = new float[length];
|
||||
}
|
||||
|
||||
void SkinningModel::bindRealTimeJointTexture(uint32_t idx, gfx::DescriptorSet *descriptorset) {
|
||||
if (_realTimeJointTexture->textures.size() < idx + 1) return;
|
||||
gfx::Texture *texture = _realTimeJointTexture->textures[idx];
|
||||
if (texture) {
|
||||
gfx::SamplerInfo info{
|
||||
gfx::Filter::POINT,
|
||||
gfx::Filter::POINT,
|
||||
gfx::Filter::NONE,
|
||||
gfx::Address::CLAMP,
|
||||
gfx::Address::CLAMP,
|
||||
gfx::Address::CLAMP,
|
||||
};
|
||||
auto *device = gfx::Device::getInstance();
|
||||
auto *sampler = device->getSampler(info);
|
||||
descriptorset->bindTexture(pipeline::REALTIMEJOINTTEXTURE::BINDING, texture);
|
||||
descriptorset->bindSampler(pipeline::REALTIMEJOINTTEXTURE::BINDING, sampler);
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningModel::updateRealTimeJointTextureBuffer() {
|
||||
uint32_t bIdx = 0;
|
||||
uint32_t width = REALTIME_JOINT_TEXTURE_WIDTH;
|
||||
uint32_t height = REALTIME_JOINT_TEXTURE_HEIGHT;
|
||||
for (const auto &texture : _realTimeJointTexture->textures) {
|
||||
auto *buffer = _realTimeJointTexture->buffer;
|
||||
auto *dst = buffer;
|
||||
auto *src = _dataArray[bIdx];
|
||||
uint32_t count = width;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
dst = buffer + (4 * i);
|
||||
memcpy(dst, src, 16);
|
||||
src = src + 4;
|
||||
dst = buffer + (4 * (i + width));
|
||||
memcpy(dst, src, 16);
|
||||
src = src + 4;
|
||||
dst = buffer + 4 * (i + 2 * width);
|
||||
memcpy(dst, src, 16);
|
||||
src = src + 4;
|
||||
}
|
||||
uint32_t buffOffset = 0;
|
||||
gfx::TextureSubresLayers layer;
|
||||
gfx::Offset texOffset;
|
||||
gfx::Extent extent{width, height, 1};
|
||||
gfx::BufferTextureCopy region{
|
||||
buffOffset,
|
||||
width,
|
||||
height,
|
||||
texOffset,
|
||||
extent,
|
||||
layer};
|
||||
auto *device = gfx::Device::getInstance();
|
||||
|
||||
device->copyBuffersToTexture(reinterpret_cast<const uint8_t *const *>(&buffer), texture, ®ion, 1);
|
||||
bIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningModel::releaseData() {
|
||||
if (!_dataArray.empty()) {
|
||||
for (auto *data : _dataArray) {
|
||||
CC_SAFE_DELETE_ARRAY(data);
|
||||
}
|
||||
_dataArray.clear();
|
||||
}
|
||||
CC_SAFE_DELETE(_realTimeJointTexture);
|
||||
if (!_buffers.empty()) {
|
||||
for (gfx::Buffer *buffer : _buffers) {
|
||||
CC_SAFE_DESTROY(buffer);
|
||||
}
|
||||
_buffers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
85
cocos/3d/models/SkinningModel.h
Normal file
85
cocos/3d/models/SkinningModel.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include "3d/models/MorphModel.h"
|
||||
#include "base/std/container/array.h"
|
||||
#include "core/animation/SkeletalAnimationUtils.h"
|
||||
#include "math/Mat4.h"
|
||||
#include "renderer/gfx-base/GFXDef-common.h"
|
||||
#include "renderer/pipeline/Define.h"
|
||||
|
||||
namespace cc {
|
||||
class Skeleton;
|
||||
namespace geometry {
|
||||
class AABB;
|
||||
}
|
||||
|
||||
struct JointInfo {
|
||||
geometry::AABB *bound{nullptr};
|
||||
Node *target{nullptr};
|
||||
Mat4 bindpose;
|
||||
IntrusivePtr<IJointTransform> transform;
|
||||
ccstd::vector<index_t> buffers;
|
||||
ccstd::vector<index_t> indices;
|
||||
};
|
||||
|
||||
class SkinningModel final : public MorphModel {
|
||||
public:
|
||||
using Super = MorphModel;
|
||||
SkinningModel();
|
||||
~SkinningModel() override;
|
||||
|
||||
void updateLocalDescriptors(index_t submodelIdx, gfx::DescriptorSet *descriptorset) override;
|
||||
void updateTransform(uint32_t stamp) override;
|
||||
void updateUBOs(uint32_t stamp) override;
|
||||
void destroy() override;
|
||||
|
||||
void initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) override;
|
||||
ccstd::vector<scene::IMacroPatch> getMacroPatches(index_t subModelIndex) override;
|
||||
void updateInstancedAttributes(const ccstd::vector<gfx::Attribute> &attributes, scene::SubModel *subModel) override;
|
||||
|
||||
void bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh);
|
||||
|
||||
private:
|
||||
static void uploadJointData(uint32_t base, const Mat4 &mat, float *dst);
|
||||
void ensureEnoughBuffers(uint32_t count);
|
||||
void updateRealTimeJointTextureBuffer();
|
||||
void initRealTimeJointTexture();
|
||||
void bindRealTimeJointTexture(uint32_t idx, gfx::DescriptorSet *descriptorset);
|
||||
void releaseData();
|
||||
|
||||
ccstd::vector<index_t> _bufferIndices;
|
||||
ccstd::vector<IntrusivePtr<gfx::Buffer>> _buffers;
|
||||
ccstd::vector<JointInfo> _joints;
|
||||
ccstd::vector<float *> _dataArray;
|
||||
bool _realTimeTextureMode = false;
|
||||
RealTimeJointTexture *_realTimeJointTexture = nullptr;
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(SkinningModel);
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
513
cocos/3d/skeletal-animation/SkeletalAnimationUtils.cpp
Normal file
513
cocos/3d/skeletal-animation/SkeletalAnimationUtils.cpp
Normal file
@@ -0,0 +1,513 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "3d/skeletal-animation/SkeletalAnimationUtils.h"
|
||||
#include "3d/assets/Mesh.h"
|
||||
#include "core/scene-graph/Node.h"
|
||||
#include "renderer/pipeline/Define.h"
|
||||
|
||||
namespace {
|
||||
const float INF = std::numeric_limits<float>::infinity();
|
||||
|
||||
cc::gfx::Format selectJointsMediumFormat(cc::gfx::Device *device) {
|
||||
if (static_cast<uint32_t>(device->getFormatFeatures(cc::gfx::Format::RGBA32F) & cc::gfx::FormatFeature::SAMPLED_TEXTURE)) {
|
||||
return cc::gfx::Format::RGBA32F;
|
||||
}
|
||||
return cc::gfx::Format::RGBA8;
|
||||
}
|
||||
|
||||
// Linear Blending Skinning
|
||||
void uploadJointDataLBS(cc::Float32Array out, uint32_t base, const cc::Mat4 &mat, bool /*firstBone*/) {
|
||||
out[base + 0] = mat.m[0];
|
||||
out[base + 1] = mat.m[1];
|
||||
out[base + 2] = mat.m[2];
|
||||
out[base + 3] = mat.m[12];
|
||||
out[base + 4] = mat.m[4];
|
||||
out[base + 5] = mat.m[5];
|
||||
out[base + 6] = mat.m[6];
|
||||
out[base + 7] = mat.m[13];
|
||||
out[base + 8] = mat.m[8];
|
||||
out[base + 9] = mat.m[9];
|
||||
out[base + 10] = mat.m[10];
|
||||
out[base + 11] = mat.m[14];
|
||||
}
|
||||
|
||||
cc::Quaternion dq0;
|
||||
cc::Quaternion dq1;
|
||||
cc::Vec3 v31;
|
||||
cc::Quaternion qt1;
|
||||
cc::Vec3 v32;
|
||||
|
||||
float dot(const cc::Quaternion &a, const cc::Quaternion &b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||
}
|
||||
|
||||
void multiplyScalar(const cc::Quaternion &a, float b, cc::Quaternion *out) {
|
||||
out->x = a.x * b;
|
||||
out->y = a.y * b;
|
||||
out->z = a.z * b;
|
||||
out->w = a.w * b;
|
||||
}
|
||||
|
||||
// Dual Quaternion Skinning
|
||||
void uploadJointDataDQS(cc::Float32Array out, uint32_t base, cc::Mat4 &mat, bool firstBone) {
|
||||
cc::Mat4::toRTS(mat, &qt1, &v31, &v32);
|
||||
// // sign consistency
|
||||
if (firstBone) {
|
||||
dq0 = qt1;
|
||||
} else if (dot(dq0, qt1) < 0) {
|
||||
multiplyScalar(qt1, -1, &qt1);
|
||||
}
|
||||
// conversion
|
||||
dq1.x = v31.x;
|
||||
dq1.y = v31.y;
|
||||
dq1.z = v31.z;
|
||||
dq1.w = 0;
|
||||
multiplyScalar(dq1 * qt1, 0.5, &dq1);
|
||||
// upload
|
||||
out[base + 0] = qt1.x;
|
||||
out[base + 1] = qt1.y;
|
||||
out[base + 2] = qt1.z;
|
||||
out[base + 3] = qt1.w;
|
||||
out[base + 4] = dq1.x;
|
||||
out[base + 5] = dq1.y;
|
||||
out[base + 6] = dq1.z;
|
||||
out[base + 7] = dq1.w;
|
||||
out[base + 8] = v32.x;
|
||||
out[base + 9] = v32.y;
|
||||
out[base + 10] = v32.z;
|
||||
}
|
||||
|
||||
// change here and cc-skinning.chunk to use other skinning algorithms
|
||||
constexpr auto UPLOAD_JOINT_DATA = uploadJointDataLBS;
|
||||
#if CC_EDITOR
|
||||
const uint32_t MINIMUM_JOINT_TEXTURE_SIZE = 2040;
|
||||
#else
|
||||
const uint32_t MINIMUM_JOINT_TEXTURE_SIZE = 480; // have to be multiples of 12
|
||||
#endif
|
||||
|
||||
uint32_t roundUpTextureSize(uint32_t targetLength, uint32_t formatSize) {
|
||||
double formatScale = 4 / std::sqrt(formatSize);
|
||||
return static_cast<uint32_t>(std::ceil(std::max(MINIMUM_JOINT_TEXTURE_SIZE * formatScale, static_cast<double>(targetLength)) / 12) * 12);
|
||||
}
|
||||
|
||||
const cc::gfx::SamplerInfo JOINT_TEXTURE_SAMPLER_INFO{
|
||||
cc::gfx::Filter::POINT,
|
||||
cc::gfx::Filter::POINT,
|
||||
cc::gfx::Filter::NONE,
|
||||
cc::gfx::Address::CLAMP,
|
||||
cc::gfx::Address::CLAMP,
|
||||
cc::gfx::Address::CLAMP,
|
||||
};
|
||||
|
||||
cc::Mat4 *getWorldTransformUntilRoot(cc::Node *target, cc::Node *root, cc::Mat4 *outMatrix) {
|
||||
outMatrix->setIdentity();
|
||||
cc::Mat4 mat4;
|
||||
while (target != root) {
|
||||
cc::Mat4::fromRTS(target->getRotation(), target->getPosition(), target->getScale(), &mat4);
|
||||
cc::Mat4::multiply(*outMatrix, mat4, outMatrix);
|
||||
target = target->getParent();
|
||||
}
|
||||
return outMatrix;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
namespace cc {
|
||||
JointTexturePool::JointTexturePool(gfx::Device *device) {
|
||||
_device = device;
|
||||
const auto &format = selectJointsMediumFormat(_device);
|
||||
_formatSize = gfx::GFX_FORMAT_INFOS[static_cast<uint32_t>(format)].size;
|
||||
_pixelsPerJoint = 48 / _formatSize;
|
||||
_pool = ccnew TextureBufferPool(device);
|
||||
ITextureBufferPoolInfo poolInfo;
|
||||
poolInfo.format = format;
|
||||
poolInfo.roundUpFn = roundUpType{roundUpTextureSize};
|
||||
_pool->initialize(poolInfo);
|
||||
_customPool = ccnew TextureBufferPool(device);
|
||||
ITextureBufferPoolInfo customPoolInfo;
|
||||
customPoolInfo.format = format;
|
||||
customPoolInfo.roundUpFn = roundUpType{roundUpTextureSize};
|
||||
_customPool->initialize(customPoolInfo);
|
||||
}
|
||||
|
||||
void JointTexturePool::clear() {
|
||||
CC_SAFE_DESTROY(_pool);
|
||||
_textureBuffers.clear();
|
||||
}
|
||||
|
||||
void JointTexturePool::registerCustomTextureLayouts(const ccstd::vector<ICustomJointTextureLayout> &layouts) {
|
||||
for (const auto &layout : layouts) {
|
||||
auto textureLength = layout.textureLength;
|
||||
if (!(static_cast<uint32_t>(_device->getFormatFeatures(cc::gfx::Format::RGBA32F) & cc::gfx::FormatFeature::SAMPLED_TEXTURE))) {
|
||||
textureLength *= 2;
|
||||
}
|
||||
uint32_t chunkIdx = _customPool->createChunk(textureLength);
|
||||
for (const auto &content : layout.contents) {
|
||||
auto skeleton = content.skeleton;
|
||||
_chunkIdxMap[skeleton] = chunkIdx; // include default pose too
|
||||
for (const auto &clip : content.clips) {
|
||||
_chunkIdxMap[skeleton ^ clip] = chunkIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::optional<IJointTextureHandle *> JointTexturePool::getDefaultPoseTexture(Skeleton *skeleton, Mesh *mesh, Node *skinningRoot) {
|
||||
ccstd::hash_t hash = skeleton->getHash() ^ 0; // may not equal to skeleton.hash
|
||||
ccstd::optional<IJointTextureHandle *> texture;
|
||||
if (_textureBuffers.find(hash) != _textureBuffers.end()) {
|
||||
texture = _textureBuffers[hash];
|
||||
}
|
||||
|
||||
const ccstd::vector<ccstd::string> &joints = skeleton->getJoints();
|
||||
const ccstd::vector<Mat4> &bindPoses = skeleton->getBindposes();
|
||||
Float32Array textureBuffer;
|
||||
bool buildTexture = false;
|
||||
auto jointCount = static_cast<uint32_t>(joints.size());
|
||||
if (!texture.has_value()) {
|
||||
uint32_t bufSize = jointCount * 12;
|
||||
ITextureBufferHandle handle;
|
||||
if (_chunkIdxMap.find(hash) != _chunkIdxMap.end()) {
|
||||
handle = _customPool->alloc(bufSize * Float32Array::BYTES_PER_ELEMENT, _chunkIdxMap[hash]);
|
||||
} else {
|
||||
handle = _pool->alloc(bufSize * Float32Array::BYTES_PER_ELEMENT);
|
||||
return texture;
|
||||
}
|
||||
IJointTextureHandle *textureHandle = IJointTextureHandle::createJoinTextureHandle();
|
||||
textureHandle->pixelOffset = handle.start / _formatSize;
|
||||
textureHandle->refCount = 1;
|
||||
textureHandle->clipHash = 0;
|
||||
textureHandle->skeletonHash = skeleton->getHash();
|
||||
textureHandle->readyToBeDeleted = false;
|
||||
textureHandle->handle = handle;
|
||||
texture = textureHandle;
|
||||
textureBuffer = Float32Array(bufSize);
|
||||
buildTexture = true;
|
||||
} else {
|
||||
texture.value()->refCount++;
|
||||
}
|
||||
|
||||
geometry::AABB ab1;
|
||||
Mat4 mat4;
|
||||
Vec3 v34;
|
||||
Vec3 v33;
|
||||
Vec3 v3Min(-INF, -INF, -INF);
|
||||
Vec3 v3Max(-INF, -INF, -INF);
|
||||
auto boneSpaceBounds = mesh->getBoneSpaceBounds(skeleton);
|
||||
for (uint32_t j = 0, offset = 0; j < jointCount; ++j, offset += 12) {
|
||||
auto *node = skinningRoot->getChildByPath(joints[j]);
|
||||
Mat4 mat = node ? *getWorldTransformUntilRoot(node, skinningRoot, &mat4) : skeleton->getInverseBindposes()[j];
|
||||
if (j < boneSpaceBounds.size()) {
|
||||
auto *bound = boneSpaceBounds[j].get();
|
||||
bound->transform(mat, &ab1);
|
||||
ab1.getBoundary(&v33, &v34);
|
||||
Vec3::min(v3Min, v33, &v3Min);
|
||||
Vec3::max(v3Max, v34, &v3Max);
|
||||
}
|
||||
|
||||
if (buildTexture) {
|
||||
if (node != nullptr) {
|
||||
Mat4::multiply(mat, bindPoses[j], &mat);
|
||||
}
|
||||
uploadJointDataLBS(textureBuffer, offset, node ? mat : Mat4::IDENTITY, j == 0);
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<geometry::AABB> bounds;
|
||||
texture.value()->bounds[static_cast<uint32_t>(mesh->getHash())] = bounds;
|
||||
geometry::AABB::fromPoints(v3Min, v3Max, &bounds[0]);
|
||||
if (buildTexture) {
|
||||
_pool->update(texture.value()->handle, textureBuffer.buffer());
|
||||
_textureBuffers[hash] = texture.value();
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
// TODO(xwx): need to implement this function after define AnimationClip
|
||||
// ccstd::optional<IJointTextureHandle> JointTexturePool::getSequencePoseTexture(Skeleton *skeleton,AnimationClip *clip, Mesh *mesh, Node *skinningRoot) {
|
||||
// uint64_t hash = skeleton->getHash() ^ clip->getHash();
|
||||
// ccstd::optional<IJointTextureHandle> texture;
|
||||
// if (_textureBuffers.find(hash) != _textureBuffers.end()) {
|
||||
// texture = _textureBuffers[hash];
|
||||
// if (texture->bounds.find(mesh->getHash()) != texture->bounds.end()) {
|
||||
// texture->refCount++;
|
||||
// return texture;
|
||||
// }
|
||||
// }
|
||||
// const ccstd::vector<ccstd::string> &joints = skeleton->getJoints();
|
||||
// const ccstd::vector<Mat4> & bindPoses = skeleton->getBindposes();
|
||||
// // const clipData = SkelAnimDataHub.getOrExtract(clip);
|
||||
// // const { frames } = clipData;
|
||||
// Float32Array textureBuffer;
|
||||
// bool buildTexture = false;
|
||||
// uint32_t jointCount = joints.size();
|
||||
// if (!texture.has_value()) {
|
||||
// uint32_t bufSize = jointCount * 12;
|
||||
// ITextureBufferHandle handle;
|
||||
// if (_chunkIdxMap.find(hash) != _chunkIdxMap.end()) {
|
||||
// handle = _customPool->alloc(bufSize * sizeof(Float32Array), _chunkIdxMap[hash]); // TODO(xwx): Float32Array.BYTES_PER_ELEMENT == sizeof(Float32Array) ?
|
||||
// } else {
|
||||
// handle = _pool->alloc(bufSize * sizeof(Float32Array));
|
||||
// return texture;
|
||||
// }
|
||||
// // auto animInfos = createAnimInfos(skeleton, clip, skinningRoot); // TODO(xwx): createAnimInfos not implement
|
||||
|
||||
// texture = IJointTextureHandle{
|
||||
// .pixelOffset = handle.start / _formatSize,
|
||||
// .refCount = 1,
|
||||
// .clipHash = 0,
|
||||
// .skeletonHash = skeleton->getHash(),
|
||||
// .readyToBeDeleted = false,
|
||||
// .handle = handle,
|
||||
// // .animInfos = animInfos // TODO(xwx)
|
||||
// };
|
||||
// textureBuffer.resize(bufSize);
|
||||
// buildTexture = true;
|
||||
// } else {
|
||||
// texture->refCount++;
|
||||
// }
|
||||
// auto boneSpaceBounds = mesh->getBoneSpaceBounds(skeleton);
|
||||
// ccstd::vector<geometry::AABB> bounds;
|
||||
// texture->bounds[mesh->getHash()] = bounds;
|
||||
|
||||
// // for (uint32_t f = 0; f < frames; ++f) { // TODO(xwx): frames not define
|
||||
// // bounds.emplace_back(geometry::AABB(INF, INF, INF, -INF, -INF, -INF));
|
||||
// // }
|
||||
|
||||
// // TODO(xwx) : need to implement when define animInfos
|
||||
// // for (uint32_t f = 0, offset = 0; f < frames; ++f) {
|
||||
// // auto bound = bounds[f];
|
||||
// // for (uint32_t j = 0; j < jointCount; ++j, offset += 12) {
|
||||
// // const {
|
||||
// // curveData,
|
||||
// // downstream,
|
||||
// // bindposeIdx,
|
||||
// // bindposeCorrection,
|
||||
// // } = texture.animInfos ![j];
|
||||
// // let mat : Mat4;
|
||||
// // let transformValid = true;
|
||||
// // if (curveData && downstream) { // curve & static two-way combination
|
||||
// // mat = Mat4.multiply(m4_1, curveData[f], downstream);
|
||||
// // } else if (curveData) { // there is a curve directly controlling the joint
|
||||
// // mat = curveData[f];
|
||||
// // } else if (downstream) { // fallback to default pose if no animation curve can be found upstream
|
||||
// // mat = downstream;
|
||||
// // } else { // bottom line: render the original mesh as-is
|
||||
// // mat = skeleton.inverseBindposes[bindposeIdx];
|
||||
// // transformValid = false;
|
||||
// // }
|
||||
// // if (j < boneSpaceBounds.size()) {
|
||||
// // auto bound = boneSpaceBounds[j];
|
||||
// // auto tarnsform = bindposeCorrection ? Mat4::multiply(mat, bindposeCorrection, &m42) : mat; // TODO(xwx): mat not define
|
||||
// // ab1.getBoundary(&v33, &v34);
|
||||
// // Vec3::min(bound.center, v33, &bound.center);
|
||||
// // Vec3::max(bound.halfExtents, v34, &bound.halfExtents);
|
||||
// // }
|
||||
|
||||
// // if (buildTexture) {
|
||||
// // if (transformValid) {
|
||||
// // Mat4::multiply(mat, bindPoses[bindposIdx], &m41);
|
||||
// // UPLOAD_JOINT_DATA(textureBuffer, offset, transformValid ? m41 : Mat4::IDENTITY, j == 0);
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
// // AABB::fromPoints(bound.center, bound.halfExtents, &bound);
|
||||
// // }
|
||||
// if (buildTexture) {
|
||||
// // _pool->update(texture->handle, textureBuffer.buffer); // TODO(xwx): ArrayBuffer not implemented
|
||||
// _textureBuffers[hash] = texture.value();
|
||||
// }
|
||||
// return texture;
|
||||
// }
|
||||
// }
|
||||
|
||||
void JointTexturePool::releaseHandle(IJointTextureHandle *handle) {
|
||||
if (handle->refCount > 0) {
|
||||
handle->refCount--;
|
||||
}
|
||||
if (!handle->refCount && handle->readyToBeDeleted) {
|
||||
ccstd::hash_t hash = handle->skeletonHash ^ handle->clipHash;
|
||||
if (_chunkIdxMap.find(hash) != _chunkIdxMap.end()) {
|
||||
_customPool->free(handle->handle);
|
||||
} else {
|
||||
_pool->free(handle->handle);
|
||||
}
|
||||
if (_textureBuffers[hash] == handle) {
|
||||
_textureBuffers.erase(hash);
|
||||
CC_SAFE_DELETE(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JointTexturePool::releaseSkeleton(Skeleton *skeleton) {
|
||||
for (const auto &texture : _textureBuffers) {
|
||||
auto *handle = texture.second;
|
||||
if (handle->skeletonHash == skeleton->getHash()) {
|
||||
handle->readyToBeDeleted = true;
|
||||
if (handle->refCount > 0) {
|
||||
// delete handle record immediately so new allocations with the same asset could work
|
||||
_textureBuffers.erase(handle->skeletonHash ^ handle->clipHash);
|
||||
} else {
|
||||
releaseHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(xwx): AnimationClip not define
|
||||
// public releaseAnimationClip(clip: AnimationClip) {
|
||||
// const it = this._textureBuffers.values();
|
||||
// let res = it.next();
|
||||
// while (!res.done) {
|
||||
// const handle = res.value;
|
||||
// if (handle.clipHash == = clip.hash) {
|
||||
// handle.readyToBeDeleted = true;
|
||||
// if (handle.refCount) {
|
||||
// // delete handle record immediately so new allocations with the same asset could work
|
||||
// this._textureBuffers.delete(handle.skeletonHash ^ handle.clipHash);
|
||||
// } else {
|
||||
// this.releaseHandle(handle);
|
||||
// }
|
||||
// }
|
||||
// res = it.next();
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO(xwx): AnimationClip not define
|
||||
// private _createAnimInfos (skeleton: Skeleton, clip: AnimationClip, skinningRoot: Node) {
|
||||
// const animInfos: IInternalJointAnimInfo[] = [];
|
||||
// const { joints, bindposes } = skeleton;
|
||||
// const jointCount = joints.length;
|
||||
// const clipData = SkelAnimDataHub.getOrExtract(clip);
|
||||
// for (let j = 0; j < jointCount; j++) {
|
||||
// let animPath = joints[j];
|
||||
// let source = clipData.joints[animPath];
|
||||
// let animNode = skinningRoot.getChildByPath(animPath);
|
||||
// let downstream: Mat4 | undefined;
|
||||
// let correctionPath: string | undefined;
|
||||
// while (!source) {
|
||||
// const idx = animPath.lastIndexOf('/');
|
||||
// animPath = animPath.substring(0, idx);
|
||||
// source = clipData.joints[animPath];
|
||||
// if (animNode) {
|
||||
// if (!downstream) { downstream = ccnew Mat4(); }
|
||||
// Mat4.fromRTS(m4_1, animNode.rotation, animNode.position, animNode.scale);
|
||||
// Mat4.multiply(downstream, m4_1, downstream);
|
||||
// animNode = animNode.parent;
|
||||
// } else { // record the nearest curve path if no downstream pose is present
|
||||
// correctionPath = animPath;
|
||||
// }
|
||||
// if (idx < 0) { break; }
|
||||
// }
|
||||
// // the default behavior, just use the bindpose for current joint directly
|
||||
// let bindposeIdx = j;
|
||||
// let bindposeCorrection: Mat4 | undefined;
|
||||
// /**
|
||||
// * It is regularly observed that developers may choose to delete the whole
|
||||
// * skeleton node tree for skinning models that only use baked animations
|
||||
// * as an effective optimization strategy (substantial improvements on both
|
||||
// * package size and runtime efficiency).
|
||||
// *
|
||||
// * This becomes troublesome in some cases during baking though, e.g. when a
|
||||
// * skeleton joint node is not directly controlled by any animation curve,
|
||||
// * but its parent nodes are. Due to lack of proper downstream default pose,
|
||||
// * the joint transform can not be calculated accurately.
|
||||
// *
|
||||
// * We address this issue by employing some pragmatic approximation.
|
||||
// * Specifically, by multiplying the bindpose of the joint corresponding to
|
||||
// * the nearest curve, instead of the actual target joint. This effectively
|
||||
// * merges the skinning influence of the 'incomplete' joint into its nearest
|
||||
// * parent with accurate transform data.
|
||||
// * It gives more visually-plausible results compared to the naive approach
|
||||
// * for most cases we've covered.
|
||||
// */
|
||||
// if (correctionPath !== undefined && source) {
|
||||
// // just use the previous joint if the exact path is not found
|
||||
// bindposeIdx = j - 1;
|
||||
// for (let t = 0; t < jointCount; t++) {
|
||||
// if (joints[t] === correctionPath) {
|
||||
// bindposeIdx = t;
|
||||
// bindposeCorrection = ccnew Mat4();
|
||||
// Mat4.multiply(bindposeCorrection, bindposes[t], skeleton.inverseBindposes[j]);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// animInfos.push({
|
||||
// curveData: source && source.transforms, downstream, bindposeIdx, bindposeCorrection,
|
||||
// });
|
||||
// }
|
||||
// return animInfos;
|
||||
// }
|
||||
|
||||
JointAnimationInfo::JointAnimationInfo(gfx::Device *device)
|
||||
: _device(device) {
|
||||
}
|
||||
|
||||
IAnimInfo JointAnimationInfo::getData(const ccstd::string &nodeID) {
|
||||
if (_pool.find(nodeID) != _pool.end()) {
|
||||
return _pool[nodeID];
|
||||
}
|
||||
|
||||
auto *buffer = _device->createBuffer(gfx::BufferInfo{
|
||||
gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST,
|
||||
gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE,
|
||||
pipeline::UBOSkinningAnimation::SIZE,
|
||||
pipeline::UBOSkinningAnimation::SIZE,
|
||||
});
|
||||
|
||||
Float32Array data;
|
||||
buffer->update(data.buffer()->getData());
|
||||
IAnimInfo info;
|
||||
info.buffer = buffer;
|
||||
info.data = data;
|
||||
info.dirty = false;
|
||||
_pool[nodeID] = info;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void JointAnimationInfo::destroy(const ccstd::string &nodeID) {
|
||||
if (_pool.find(nodeID) != _pool.end()) {
|
||||
CC_SAFE_DESTROY_AND_DELETE(_pool[nodeID].buffer);
|
||||
_pool.erase(nodeID);
|
||||
}
|
||||
}
|
||||
|
||||
const IAnimInfo &JointAnimationInfo::switchClip(IAnimInfo &info /*, AnimationClip *clip */) {
|
||||
// info.currentClip = clip;
|
||||
info.data[0] = -1;
|
||||
info.buffer->update(info.data.buffer()->getData());
|
||||
info.dirty = false;
|
||||
return info;
|
||||
}
|
||||
|
||||
void JointAnimationInfo::clear() {
|
||||
for (auto pool : _pool) {
|
||||
CC_SAFE_DESTROY_AND_DELETE(pool.second.buffer);
|
||||
}
|
||||
_pool.clear();
|
||||
}
|
||||
} // namespace cc
|
||||
154
cocos/3d/skeletal-animation/SkeletalAnimationUtils.h
Normal file
154
cocos/3d/skeletal-animation/SkeletalAnimationUtils.h
Normal file
@@ -0,0 +1,154 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "base/std/container/unordered_map.h"
|
||||
#include "base/std/optional.h"
|
||||
|
||||
#include "3d/assets/Skeleton.h"
|
||||
#include "core/TypedArray.h"
|
||||
#include "core/geometry/AABB.h"
|
||||
#include "gfx-base/GFXDef-common.h"
|
||||
#include "renderer/core/TextureBufferPool.h"
|
||||
#include "renderer/gfx-base/GFXBuffer.h"
|
||||
#include "renderer/gfx-base/GFXDevice.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class Node;
|
||||
class Mesh;
|
||||
|
||||
// _chunkIdxMap[key] = skeleton ^ clips[i]
|
||||
struct IChunkContent {
|
||||
uint32_t skeleton{0U};
|
||||
ccstd::vector<uint32_t> clips;
|
||||
};
|
||||
|
||||
struct ICustomJointTextureLayout {
|
||||
uint32_t textureLength{0};
|
||||
ccstd::vector<IChunkContent> contents;
|
||||
};
|
||||
|
||||
struct IInternalJointAnimInfo {
|
||||
ccstd::optional<Mat4> downstream; // downstream default pose, if present
|
||||
ccstd::optional<ccstd::vector<Mat4>> curveData; // the nearest animation curve, if present
|
||||
index_t bindposeIdx{0}; // index of the actual bindpose to use
|
||||
ccstd::optional<Mat4> bindposeCorrection; // correction factor from the original bindpose
|
||||
};
|
||||
|
||||
class IJointTextureHandle {
|
||||
public:
|
||||
uint32_t pixelOffset{0};
|
||||
uint32_t refCount{0};
|
||||
ccstd::hash_t clipHash{0U};
|
||||
ccstd::hash_t skeletonHash{0U};
|
||||
bool readyToBeDeleted{false};
|
||||
ITextureBufferHandle handle;
|
||||
ccstd::unordered_map<uint32_t, ccstd::vector<geometry::AABB>> bounds;
|
||||
ccstd::optional<ccstd::vector<IInternalJointAnimInfo>> animInfos;
|
||||
|
||||
static IJointTextureHandle *createJoinTextureHandle() {
|
||||
return ccnew IJointTextureHandle();
|
||||
}
|
||||
|
||||
private:
|
||||
IJointTextureHandle() = default;
|
||||
};
|
||||
|
||||
class JointTexturePool : public RefCounted {
|
||||
public:
|
||||
JointTexturePool() = default;
|
||||
explicit JointTexturePool(gfx::Device *device);
|
||||
~JointTexturePool() override = default;
|
||||
|
||||
inline uint32_t getPixelsPerJoint() const { return _pixelsPerJoint; }
|
||||
|
||||
void clear();
|
||||
|
||||
void registerCustomTextureLayouts(const ccstd::vector<ICustomJointTextureLayout> &layouts);
|
||||
|
||||
/**
|
||||
* @en
|
||||
* Get joint texture for the default pose.
|
||||
* @zh
|
||||
* 获取默认姿势的骨骼贴图。
|
||||
*/
|
||||
ccstd::optional<IJointTextureHandle *> getDefaultPoseTexture(Skeleton *skeleton, Mesh *mesh, Node *skinningRoot);
|
||||
|
||||
/**
|
||||
* @en
|
||||
* Get joint texture for the specified animation clip.
|
||||
* @zh
|
||||
* 获取指定动画片段的骨骼贴图。
|
||||
*/
|
||||
// ccstd::optional<IJointTextureHandle> getSequencePoseTexture(Skeleton *skeleton, AnimationClip *clip, Mesh *mesh, Node *skinningRoot);
|
||||
|
||||
void releaseHandle(IJointTextureHandle *handle);
|
||||
|
||||
void releaseSkeleton(Skeleton *skeleton);
|
||||
|
||||
// void releaseAnimationClip (AnimationClip* clip); // TODO(xwx): AnimationClip not define
|
||||
|
||||
private:
|
||||
// const IInternalJointAnimInfo &createAnimInfos(Skeleton *skeleton, AnimationClip *clip, Node *skinningRoot); // TODO(xwx): AnimationClip not define
|
||||
|
||||
gfx::Device *_device{nullptr};
|
||||
IntrusivePtr<TextureBufferPool> _pool;
|
||||
ccstd::unordered_map<ccstd::hash_t, IJointTextureHandle *> _textureBuffers;
|
||||
uint32_t _formatSize{0};
|
||||
uint32_t _pixelsPerJoint{0};
|
||||
IntrusivePtr<TextureBufferPool> _customPool;
|
||||
ccstd::unordered_map<ccstd::hash_t, index_t> _chunkIdxMap; // hash -> chunkIdx
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(JointTexturePool);
|
||||
};
|
||||
|
||||
struct IAnimInfo {
|
||||
gfx::Buffer *buffer{nullptr};
|
||||
Float32Array data;
|
||||
const float *curFrame{nullptr}; // Only used in JSB
|
||||
uint32_t frameDataBytes{0}; // Only used in JSB
|
||||
uint8_t *dirtyForJSB{nullptr}; // Only used in JSB
|
||||
bool dirty{false};
|
||||
};
|
||||
|
||||
class JointAnimationInfo : public RefCounted {
|
||||
public:
|
||||
JointAnimationInfo() = default;
|
||||
explicit JointAnimationInfo(gfx::Device *device);
|
||||
~JointAnimationInfo() override = default;
|
||||
|
||||
IAnimInfo getData(const ccstd::string &nodeID = "-1");
|
||||
void destroy(const ccstd::string &nodeID);
|
||||
static const IAnimInfo &switchClip(IAnimInfo &info /*, AnimationClip *clip */);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
ccstd::unordered_map<ccstd::string, IAnimInfo> _pool; // pre node
|
||||
gfx::Device *_device{nullptr};
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(JointAnimationInfo);
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
Reference in New Issue
Block a user