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