no message

This commit is contained in:
gem
2025-02-18 15:21:31 +08:00
commit 2d133e56d7
1980 changed files with 465595 additions and 0 deletions

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
View 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
View 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

View 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

View 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

View 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

View 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
View 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