diff --git a/cocos/3d/assets/mesh.ts b/cocos/3d/assets/mesh.ts
index d064d93ddb0..5011a6e09d8 100644
--- a/cocos/3d/assets/mesh.ts
+++ b/cocos/3d/assets/mesh.ts
@@ -28,11 +28,11 @@ import { Asset } from '../../asset/assets/asset';
import { IDynamicGeometry } from '../../primitive/define';
import { BufferBlob } from '../misc/buffer-blob';
import { Skeleton } from './skeleton';
-import { geometry, cclegacy, sys, warnID, Mat4, Quat, Vec3, assertIsTrue, murmurhash2_32_gc, errorID } from '../../core';
+import { geometry, cclegacy, sys, warnID, Mat4, Quat, Vec3, assertIsTrue, murmurhash2_32_gc, errorID, halfToFloat } from '../../core';
import { RenderingSubMesh } from '../../asset/assets';
import {
Attribute, Device, Buffer, BufferInfo, AttributeName, BufferUsageBit, Feature, Format,
- FormatInfos, FormatType, MemoryUsageBit, PrimitiveMode, getTypedArrayConstructor, DrawInfo, FormatInfo, deviceManager,
+ FormatInfos, FormatType, MemoryUsageBit, PrimitiveMode, getTypedArrayConstructor, DrawInfo, FormatInfo, deviceManager, FormatFeatureBit,
} from '../../gfx';
import { Morph } from './morph';
import { MorphRendering, createMorphRendering } from './morph-rendering';
@@ -409,6 +409,11 @@ export class Mesh extends Asset {
if (this.struct.encoded) { // decode mesh data
info = decodeMesh(info);
}
+ if (this.struct.quantized
+ && !(deviceManager.gfxDevice.getFormatFeatures(Format.RGB16F) & FormatFeatureBit.VERTEX_ATTRIBUTE)) {
+ // dequantize mesh data
+ info = dequantizeMesh(info);
+ }
this._struct = info.struct;
this._data = info.data;
@@ -1507,11 +1512,6 @@ export function decodeMesh (mesh: Mesh.ICreateInfo): Mesh.ICreateInfo {
return mesh;
}
- // decode the mesh
- if (!MeshoptDecoder.supported) {
- return mesh;
- }
-
const res_checker = (res: number): void => {
if (res < 0) {
errorID(14204, res);
@@ -1581,4 +1581,150 @@ export function inflateMesh (mesh: Mesh.ICreateInfo): Mesh.ICreateInfo {
return mesh;
}
+export function dequantizeMesh (mesh: Mesh.ICreateInfo): Mesh.ICreateInfo {
+ const struct = JSON.parse(JSON.stringify(mesh.struct)) as Mesh.IStruct;
+
+ const bufferBlob = new BufferBlob();
+ bufferBlob.setNextAlignment(0);
+
+ function transformVertex (
+ reader: ((offset: number) => number),
+ writer: ((offset: number, value: number) => void),
+ count: number,
+ components: number,
+ componentSize: number,
+ readerStride: number,
+ writerStride: number,
+ ): void {
+ for (let i = 0; i < count; i++) {
+ for (let j = 0; j < components; j++) {
+ const inputOffset = readerStride * i + componentSize * j;
+ const outputOffset = writerStride * i + componentSize * j;
+ writer(outputOffset, reader(inputOffset));
+ }
+ }
+ }
+
+ function dequantizeHalf (
+ reader: ((offset: number) => number),
+ writer: ((offset: number, value: number) => void),
+ count: number,
+ components: number,
+ readerStride: number,
+ writerStride: number,
+ ): void {
+ for (let i = 0; i < count; i++) {
+ for (let j = 0; j < components; j++) {
+ const inputOffset = readerStride * i + 2 * j;
+ const outputOffset = writerStride * i + 4 * j;
+ const value = halfToFloat(reader(inputOffset));
+ writer(outputOffset, value);
+ }
+ }
+ }
+
+ for (let i = 0; i < struct.vertexBundles.length; ++i) {
+ const bundle = struct.vertexBundles[i];
+ const view = bundle.view;
+ const attributes = bundle.attributes;
+ const oldAttributes = mesh.struct.vertexBundles[i].attributes;
+ const strides: number[] = [];
+ const dequantizes: boolean[] = [];
+ const readers: ((offset: number) => number)[] = [];
+ for (let j = 0; j < attributes.length; ++j) {
+ const attr = attributes[j];
+ const inputView = new DataView(mesh.data.buffer, view.offset + getOffset(oldAttributes, j));
+ const reader = getReader(inputView, attr.format);
+ let dequantize = true;
+ switch (attr.format) {
+ case Format.R16F:
+ attr.format = Format.R32F;
+ break;
+ case Format.RG16F:
+ attr.format = Format.RG32F;
+ break;
+ case Format.RGB16F:
+ attr.format = Format.RGB32F;
+ break;
+ case Format.RGBA16F:
+ attr.format = Format.RGBA32F;
+ break;
+ default:
+ dequantize = false;
+ break;
+ }
+ strides.push(FormatInfos[attr.format].size);
+ dequantizes.push(dequantize);
+ readers.push(reader!);
+ }
+ const netStride = strides.reduce((acc, cur) => acc + cur, 0);
+ const newBuffer = new Uint8Array(netStride * view.count);
+ for (let j = 0; j < attributes.length; ++j) {
+ const attribute = attributes[j];
+ const reader = readers[j];
+ const outputView = new DataView(newBuffer.buffer, getOffset(attributes, j));
+ const writer = getWriter(outputView, attribute.format)!;
+ const dequantize = dequantizes[j];
+ const formatInfo = FormatInfos[attribute.format];
+ if (dequantize) {
+ dequantizeHalf(
+ reader,
+ writer,
+ view.count,
+ formatInfo.count,
+ view.stride,
+ netStride,
+ );
+ } else {
+ transformVertex(
+ reader,
+ writer,
+ view.count,
+ formatInfo.count,
+ formatInfo.size / formatInfo.count,
+ view.stride,
+ netStride,
+ );
+ }
+ }
+
+ bufferBlob.setNextAlignment(netStride);
+ const newView: Mesh.IBufferView = {
+ offset: bufferBlob.getLength(),
+ length: newBuffer.byteLength,
+ count: view.count,
+ stride: netStride,
+ };
+ bundle.view = newView;
+ bufferBlob.addBuffer(newBuffer);
+ }
+
+ // dump index buffer
+ for (const primitive of struct.primitives) {
+ if (primitive.indexView === undefined) {
+ continue;
+ }
+ const view = primitive.indexView;
+ const buffer = new Uint8Array(mesh.data.buffer, view.offset, view.length);
+ bufferBlob.setNextAlignment(view.stride);
+ const newView: Mesh.IBufferView = {
+ offset: bufferBlob.getLength(),
+ length: buffer.byteLength,
+ count: view.count,
+ stride: view.stride,
+ };
+ primitive.indexView = newView;
+ bufferBlob.addBuffer(buffer);
+ }
+
+ const data = new Uint8Array(bufferBlob.getCombined());
+
+ struct.quantized = false;
+
+ return {
+ struct,
+ data,
+ };
+}
+
// function get
diff --git a/editor/i18n/zh/assets.js b/editor/i18n/zh/assets.js
index 6396bdfe316..e02c185354e 100644
--- a/editor/i18n/zh/assets.js
+++ b/editor/i18n/zh/assets.js
@@ -325,84 +325,65 @@ module.exports = {
name: '填充顶点色',
title: '如果模型没有顶点颜色属性,添加颜色属性,填充为白色。',
},
+ meshOptimize: {
+ name: '网格优化',
+ title: '是否优化网格数据。',
+ vertexCache: {
+ name: '顶点缓存',
+ title: '优化顶点缓冲区以提高顶点缓存命中率。
建议对顶点数较高的模型启用此选项。',
+ },
+ vertexFetch: {
+ name: '顶点提取',
+ title: '优化顶点缓冲区以提高顶点提取效率。
建议对顶点数较高的模型启用此选项。',
+ },
+ overdraw: {
+ name: '过度绘制',
+ title: '优化顶点缓冲区以减少过度绘制。
建议对顶点数较高的模型启用此选项。',
+ },
+ },
meshSimplify: {
- name: 'Mesh 简化',
- title: 'Mesh 简化可以被用来简化导入的模型,可以在需要模型减面时使用。
在一些少数情况下减面后的模型可能会出现显示异常,如发生这种情况请尝试调整参数并重试。',
- simplification: {
- name: 'Simplification',
- title: 'Simplification',
- si: {
- name: 'Achieve The Ratio R',
- title: 'Achieve The Ratio R',
- },
- sa: {
- name: 'Aggressively Simplify',
- title: 'Aggressively Simplify',
- },
+ name: '网格简化',
+ title: '是否简化网格数据。',
+ targetRatio: {
+ name: '目标比率',
+ title: '简化网格数据的目标顶点数的比例。
建议将此值设置为 0.5。',
},
- scene: {
- name: 'Scene',
- title: 'Scene',
- kn: {
- name: 'Keep Nodes Transform',
- title: 'Keep Nodes Transform',
- },
- ke: {
- name: 'Keep Extras Data',
- title: 'Keep Extras Data',
- },
+ autoErrorRate: {
+ name: '自动误差率',
+ title: '是否自动计算简化网格数据的误差率。',
},
- miscellaneous: {
- name: 'Miscellaneous',
- title: 'Miscellaneous',
- noq: {
- name: 'Disable Quantization',
- title: 'Disable Quantization',
- },
- v: {
- name: 'Verbose Output',
- title: 'Verbose Output',
- },
+ errorRate: {
+ name: '误差率',
+ title: '简化网格数据的最大误差率。
此值还会影响结果大小。
建议调整直到获得良好的结果。',
},
- algorithm: {
- name: '减面算法',
- simplify: 'simplify',
- gltfpack: 'gltfpack (已废弃)',
+ lockBoundary: {
+ name: '锁定边界',
+ title: '是否锁定简化网格数据的边界。',
},
- simplify: {
- targetRatio: {
- name: 'LOD 压缩比例',
- title: '减面之后的目标面数比例,0 代表减面至最少,1 代表没有减面的原模型。',
- },
- preserveSurfaceCurvature: {
- name: '保留表面曲率',
- title: 'Preserve Surface Curvature',
- },
- preserveBorderEdges: {
- name: '保留边界边',
- title: 'Preserve Border Edges',
- },
- preserveUVSeamEdges: {
- name: '保留 UV 缝合边',
- title: 'Preserve UV Seam Edges',
- },
- preserveUVFoldoverEdges: {
- name: '保留 UV 折叠边',
- title: 'Preserve UV Foldover Edges',
- },
- agressiveness: {
- name: '误差距离',
- title: '模型减面算法的激进程度。
当设置数值越高时,算法的减面策略会越激进,但是过于激进的策略更有可能导致结果错误。',
- },
- maxIterationCount: {
- name: '计算迭代次数',
- title: '最大重复计数代表减面算法运行的重复次数。
高数值可以使算法运行结果更接近目标,但也会增加运行时间和结果错误的概率。',
- },
+ },
+ meshCluster: {
+ name: '网格切块',
+ title: '是否分割网格数据。',
+ generateBounding: {
+ name: '生成包围体',
+ title: '是否为聚类的网格数据生成包围球和法线锥。',
+ },
+ },
+ meshCompress:{
+ name: '网格压缩',
+ title: '是否压缩网格数据。',
+ encode: {
+ name: '编码',
+ title: '对网格数据进行编码以减少数据大小。',
+ },
+ compress: {
+ name: '压缩',
+ title: '对网格数据进行压缩以减少数据大小。',
},
- gltfpack: {
- warn: '当前资源使用的减面算法 gltfpack 已被废弃,请选用新的 simplify 减面算法。',
+ quantize: {
+ name: '量化',
+ title: '对网格数据进行量化以减少数据大小。',
},
- warn: '警告:优化后,网格资源的数量和名称会发生改变,这将会造成组件引用的资源丢失,请及时手动更新;(另外,对于模型资源中预生成的预制体,资源同步机制会自动更新)',
},
animationBakeRate: {
name: '动画烘焙速率',
diff --git a/native/cocos/3d/assets/Mesh.cpp b/native/cocos/3d/assets/Mesh.cpp
index 615a06b3aa9..8446ec0f51e 100644
--- a/native/cocos/3d/assets/Mesh.cpp
+++ b/native/cocos/3d/assets/Mesh.cpp
@@ -32,8 +32,12 @@
#include "core/assets/RenderingSubMesh.h"
#include "core/platform/Debug.h"
#include "math/Quaternion.h"
+#include "math/Utils.h"
#include "renderer/gfx-base/GFXDevice.h"
+#include
+#include
+
#define CC_OPTIMIZE_MESH_DATA 0
namespace cc {
@@ -247,6 +251,143 @@ void convertRG32FToRG16F(const float *src, uint16_t *dst) {
} // namespace
+void MeshUtils::dequantizeMesh(Mesh::IStruct &structInfo, Uint8Array &data) {
+ BufferBlob bufferBlob;
+ bufferBlob.setNextAlignment(0);
+
+ using DataReaderCallback = std::function;
+ using DataWritterCallback = std::function;
+
+ const auto transformVertex =
+ [](const DataReaderCallback &reader,
+ const DataWritterCallback &writer,
+ uint32_t count,
+ uint32_t components,
+ uint32_t componentSize,
+ uint32_t readerStride,
+ uint32_t writerStride) -> void {
+ for (uint32_t i = 0; i < count; ++i) {
+ for (uint32_t j = 0; j < components; ++j) {
+ const auto inputOffset = readerStride * i + componentSize * j;
+ const auto outputOffset = writerStride * i + componentSize * j;
+ writer(outputOffset, reader(inputOffset));
+ }
+ }
+ };
+
+ const auto dequantizeHalf =
+ [](const DataReaderCallback &reader,
+ const DataWritterCallback &writer,
+ uint32_t count,
+ uint32_t components,
+ uint32_t readerStride,
+ uint32_t writerStride) -> void {
+ for (uint32_t i = 0; i < count; ++i) {
+ for (uint32_t j = 0; j < components; ++j) {
+ const auto inputOffset = readerStride * i + 2 * j;
+ const auto outputOffset = writerStride * i + 4 * j;
+ const auto val = mathutils::halfToFloat(ccstd::get(reader(inputOffset)));
+ writer(outputOffset, val);
+ }
+ }
+ };
+
+ for (auto &bundle : structInfo.vertexBundles) {
+ auto &view = bundle.view;
+ auto &attrs = bundle.attributes;
+ auto oldAttrs = attrs;
+ std::vector strides;
+ std::vector dequantizes;
+ std::vector readers;
+ strides.reserve(attrs.size());
+ dequantizes.reserve(attrs.size());
+ readers.reserve(attrs.size());
+ for (uint32_t i = 0; i < attrs.size(); ++i) {
+ auto &attr = attrs[i];
+ auto inputView = DataView(data.buffer(), view.offset + getOffset(oldAttrs, i));
+ auto reader = getReader(inputView, attr.format);
+ auto dequantize = true;
+ switch (attr.format) {
+ case gfx::Format::R16F:
+ attr.format = gfx::Format::R32F;
+ break;
+ case gfx::Format::RG16F:
+ attr.format = gfx::Format::RG32F;
+ break;
+ case gfx::Format::RGB16F:
+ attr.format = gfx::Format::RGB32F;
+ break;
+ case gfx::Format::RGBA16F:
+ attr.format = gfx::Format::RGBA32F;
+ break;
+ default:
+ dequantize = false;
+ break;
+ }
+ strides.push_back(gfx::GFX_FORMAT_INFOS[static_cast(attr.format)].size);
+ dequantizes.push_back(dequantize);
+ readers.push_back(reader);
+ }
+ auto netStride = std::accumulate(strides.begin(), strides.end(), 0U);
+ auto vertData = Uint8Array(view.count * netStride);
+ for (uint32_t i = 0; i < attrs.size(); i++) {
+ const auto &attr = attrs[i];
+ const auto &reader = readers[i];
+ auto outputView = DataView(vertData.buffer(), getOffset(attrs, i));
+ auto writer = getWriter(outputView, attr.format);
+ const auto &dequantize = dequantizes[i];
+ const auto &formatInfo = gfx::GFX_FORMAT_INFOS[static_cast(attr.format)];
+ if (dequantize) {
+ dequantizeHalf(
+ reader,
+ writer,
+ view.count,
+ formatInfo.count,
+ view.stride,
+ netStride);
+ } else {
+ transformVertex(
+ reader,
+ writer,
+ view.count,
+ formatInfo.count,
+ formatInfo.size / formatInfo.count,
+ view.stride,
+ netStride);
+ }
+ }
+
+ bufferBlob.setNextAlignment(netStride);
+ Mesh::IBufferView vertexView;
+ vertexView.offset = bufferBlob.getLength();
+ vertexView.length = view.count * netStride;
+ vertexView.count = view.count;
+ vertexView.stride = netStride;
+ bundle.view = vertexView;
+ bufferBlob.addBuffer(vertData.buffer());
+ }
+
+ for (auto &primitive : structInfo.primitives) {
+ if (!primitive.indexView.has_value()) {
+ continue;
+ }
+ auto &view = *primitive.indexView;
+ auto *buffer = ccnew ArrayBuffer(data.buffer()->getData() + view.offset, view.length);
+ bufferBlob.setNextAlignment(view.stride);
+ Mesh::IBufferView indexView;
+ indexView.offset = bufferBlob.getLength();
+ indexView.length = view.length;
+ indexView.count = view.count;
+ indexView.stride = view.stride;
+ primitive.indexView = indexView;
+ bufferBlob.addBuffer(buffer);
+ }
+
+ structInfo.quantized = false;
+
+ data = Uint8Array(bufferBlob.getCombined());
+}
+
Mesh::~Mesh() = default;
ccstd::any Mesh::getNativeAsset() const {
@@ -314,6 +455,9 @@ void Mesh::initialize() {
// decode
MeshUtils::decodeMesh(_struct, _data);
}
+ if (_struct.quantized && !hasFlag(gfx::Device::getInstance()->getFormatFeatures(gfx::Format::RG16F), gfx::FormatFeature::VERTEX_ATTRIBUTE)) {
+ MeshUtils::dequantizeMesh(_struct, _data);
+ }
if (_struct.dynamic.has_value()) {
auto *device = gfx::Device::getInstance();
diff --git a/native/cocos/3d/assets/Mesh.h b/native/cocos/3d/assets/Mesh.h
index 0eac30df259..8895059823c 100644
--- a/native/cocos/3d/assets/Mesh.h
+++ b/native/cocos/3d/assets/Mesh.h
@@ -72,6 +72,13 @@ class Mesh : public Asset {
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 子网格。子网格由一系列相同类型的图元组成(例如点、线、面等)。
@@ -101,6 +108,8 @@ class Mesh : public Asset {
* 如未定义或指向的映射表不存在,则默认 VB 内所有关节索引数据直接对应骨骼资源数据。
*/
ccstd::optional jointMapIndex;
+
+ ccstd::optional cluster;
};
/**
diff --git a/native/cocos/3d/misc/CreateMesh.cpp b/native/cocos/3d/misc/CreateMesh.cpp
index e479a23d33d..ffed86a849d 100644
--- a/native/cocos/3d/misc/CreateMesh.cpp
+++ b/native/cocos/3d/misc/CreateMesh.cpp
@@ -409,14 +409,22 @@ Mesh::ICreateInfo MeshUtils::createDynamicMeshInfo(const IDynamicGeometry &geome
void MeshUtils::inflateMesh(const Mesh::IStruct &structInfo, Uint8Array &data) {
uLongf uncompressedSize = 0U;
for (const auto &prim : structInfo.primitives) {
- uncompressedSize += prim.indexView->length;
+ 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;
+ uncompressedSize += vb.view.length + vb.view.stride;
}
auto uncompressedData = Uint8Array(static_cast(uncompressedSize));
auto res = uncompress(uncompressedData.buffer()->getData(), &uncompressedSize, data.buffer()->getData(), data.byteLength());
- data = uncompressedData;
+ data = Uint8Array(uncompressedData.buffer(), 0, static_cast(uncompressedSize));
}
void MeshUtils::decodeMesh(Mesh::IStruct &structInfo, Uint8Array &data) {
diff --git a/native/cocos/3d/misc/CreateMesh.h b/native/cocos/3d/misc/CreateMesh.h
index 84f1272168e..4aba2eeb144 100644
--- a/native/cocos/3d/misc/CreateMesh.h
+++ b/native/cocos/3d/misc/CreateMesh.h
@@ -94,6 +94,8 @@ class MeshUtils {
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