VoxelTurntableExporter
VoxelTurntableExporter 是 VoxelKit 的独立动图导出能力。它不截取屏幕,也不要求页面已经创建 VoxelViewer:给它一份体素 JSON,它会在异步 Native 任务中把体素表面按旋转角度绘制为 GIF 帧并返回 GIF 字节。若当前 ABI 无法加载 Native 方法或 Native 执行失败,组件会自动切换到 ArkTS TaskPool 兼容路径,对调用方透明。
导出器会扫描模型的实际表面 bounds,并在整个旋转周期中选取同一个安全取景比例:主体不会因为逐帧自动缩放而忽大忽小,同时会尽可能填充画布。为了在透明背景上仍能看清深色体素,阴面会保留较高的源颜色亮度;这是一套面向清晰预览的轻量离屏光照,并不等同于 VoxelViewer 的实时 PBR 灯光和后期效果。通过 VoxelEditorController 导出时,未显式填写的俯仰角、曝光、环境光、主光方向与强度会继承当前 Viewer;直接调用独立导出器时使用稳定的等距机位和内置光照。GIF 色表也会从当前模型实际调色板生成,优先保存最多 85 个主色及亮、中、暗三档,避免固定 RGB 色盘把青色或蓝色误映射为紫色。
它适合“作品分享封面”“聊天发送动图”“生成作品库动效预览”等场景。因为不录屏,导出文件不会包含页面按钮、状态栏、背景图片或加载遮罩。通过编辑器便捷入口导出时会继承 Controller 最近管理的俯仰和当前灯光,但不会继承平移、缩放或方位角;GIF 始终输出完整一圈并自动取景。
new VoxelTurntableExporter().exportJsonText(
jsonText: string,
options?: VoxelTurntableGifOptions
): Promise<VoxelTurntableGifResult>
最小示例
import {
VoxelAxis,
VoxelTurntableExporter,
VoxelTurntableGifOptions
} from 'voxel-kit';
const options = new VoxelTurntableGifOptions();
options.width = 768;
options.height = 768;
options.frameCount = 180;
options.antiAliasScale = 2;
options.rotationAxis = VoxelAxis.Z;
options.transparent = true;
options.onProgress = (progress) => {
console.info(progress.stage + ' · ' + progress.percent + '%');
};
const gif = await new VoxelTurntableExporter().exportJsonText(jsonText, options);
// gif.bytes 的 MIME 类型固定为 image/gif。
// 由宿主使用已授权的文件 URI、分享或网络上传流程处理这些字节。
VoxelTurntableGifOptions
| 字段 | 类型 | 默认值 | 范围与含义 |
|---|---|---|---|
width | number | 768 | 输出宽度,限制为 64..1024。 |
height | number | 768 | 输出高度,限制为 64..1024。 |
frameCount | number | 180 | 一圈帧数,限制为 12..240。默认每圈 5 秒、平均约 36fps。更多帧更平滑,也更慢、更大。 |
durationMs | number | undefined | undefined | 完整一圈时长,限制为 300..60000 毫秒。经编辑器导出时不填则继承 Viewer 的 autoRotateDurationMs;直接导出时为 5000 ms。 |
antiAliasScale | number | 2 | 内部超采样倍数,限制为 1..2。2 表示每个输出像素使用 2×2 深度测试子像素后再解析。 |
pitchRadians | number | undefined | undefined | 导出俯仰角。编辑器路径不填则继承当前 Viewer;独立路径使用标准等距俯仰。 |
lightIntensity | number | undefined | undefined | 离屏光照总倍率。编辑器路径不填则继承当前场景,独立路径默认为 1。 |
exposure | number | undefined | undefined | 曝光;不填时按上面的继承规则处理。 |
ambientDiffuse | number | undefined | undefined | 环境漫反射强度。 |
keyIntensity | number | undefined | undefined | 主光强度。 |
keyAzimuth | number | undefined | undefined | 主光水平角,单位为度。 |
keyElevation | number | undefined | undefined | 主光仰角,单位为度。 |
rotationAxis | VoxelAxis | VoxelAxis.Z | 旋转轴。体素数据是 Z-up,因此常规展示使用 Z。 |
transparent | boolean | true | 是否使用透明背景。 |
backgroundColor | string | '#FFFFFF' | 仅在 transparent=false 时使用;接受 #RGB 或 #RRGGBB。 |
loopCount | number | 0 | GIF 循环次数,0 表示无限循环;限制为 0..65535。 |
onProgress | (progress: VoxelTurntableGifProgress) => void | undefined | 接收导出阶段与估算进度;不传入 TaskPool。完成时回调 100。 |
所有越界的数值都会在实际导出时限制到上表范围。建议对 128³ 密集模型先使用默认 768 × 768 / 180 帧 / 2×2 SSAA,确认分享体积和耗时后再提高或降低参数。编码器会按厘秒精度在帧间分配延迟,使所有帧合计严格匹配最终时长。GIF 透明度是二值的,因此超采样改善轮廓采样位置,但不能像 PNG 那样保存半透明边缘像素。
默认路径在一个 NAPI 异步任务中完成 JSON 解析、贪心面生成和 GIF 输出。4 个 C++ 线程按连续帧区间执行投影、深度测试、凸四边形光栅、2×2 超采样解析与固定 9-bit LZW;帧缓冲、深度缓冲和字典按线程复用,并采用“渲染一帧、立即编码一帧”的流式路径。完成后按原帧序拼接,每个分片使用同一确定性色表、帧延迟和 LZW 规则。ArkTS 回退路径使用相同公式、超采样和码流规则,由 4 个 TaskPool Worker 完成。LZW 清表发生在字典即将触发 9-bit 到 10-bit 位宽切换之前,避免部分真机解码器把后续帧误读为白屏。
在 Mate 80 Pro Max 上以内置 128³ / 116638 体素模型、上一版 640 × 640 / 180 帧 / 5000 ms / 无超采样 规格实测,旧单 Worker 为 249.94 s,4 Worker ArkTS 基线为 54.024 s,Native C++ 为 1.104 s。Native 相对当前 ArkTS 基线约提升 48.9×;两次最终输出均为 8,374,808 bytes,SHA-256 完全相同,180 帧逐像素对比为零差异。该数字是同设备同样本的相对记录,不是所有模型的固定耗时。
当前默认 768 × 768 / 180 帧 / 5000 ms / 2×2 SSAA 在同设备同模型实测为 3.175 s,输出 11,209,238 bytes。回读确认 180 帧、无限循环、透明索引 0 和全帧 disposal method 2;六方向接触表未见残影、异色或取景回退。
VoxelTurntableGifProgress
onProgress 收到的进度对象包含:
| 字段 | 类型 | 说明 |
|---|---|---|
percent | number | 0..100。Worker 不逐帧跨线程回传,因此中段为根据画布、帧数、JSON 大小和并发数计算的时间估算;它不会固定停在 94%,而是在后处理阶段逐步趋近 99%,成功完成时必为 100。 |
stage | string | 正在准备体素面、正在离屏渲染 GIF 帧、正在整理 GIF 帧数据、正在压缩 GIF 数据 或 GIF 导出完成。 |
在 VoxelEditorController 的导出便捷入口中,同样的状态会写到 VoxelEditorState.exportProgress 与 VoxelEditorState.exportStage,方便直接绑定进度条。
VoxelTurntableGifResult
| 字段 | 类型 | 说明 |
|---|---|---|
bytes | ArrayBuffer | 完整 GIF89a 文件字节。 |
mimeType | 'image/gif' | 固定值。 |
width / height | number | 实际生效的输出尺寸。 |
frameCount / durationMs | number | 实际生效的帧数和总时长。 |
transparent | boolean | 实际的透明背景开关。 |
uri | string | 只有编辑器的 exportTurntableGifToUri() / exportTurntableGifAs() 成功写入后才有值;直接导出时为空。 |
和编辑器一起使用
VoxelEditorController 的便捷方法既可导出只读预览,也可导出用户刚刚修改、但尚未保存到 JSON/VFP 的模型:
const result = await editor.exportTurntableGifAs(
this.getUIContext().getHostContext(),
options
);
如果 options.durationMs、pitchRadians 或光照字段保持 undefined,该入口会分别读取 Viewer 当前自动旋转速度、Controller 最近管理的俯仰和 VoxelRenderTuning。2.14.3 不在手动相机手势结束时发布实时快照,因此要求导出角度与用户刚刚拖动后的画面严格一致时,应显式设置 pitchRadians。例如只提高导出亮度可设置 lightIntensity,其余仍继承现场。
它不要求先调用 enterEdit()。只读预览会临时恢复权威 JSON/VFP 体素源用于离屏绘制,但不会创建编辑 Chunk、选区、撤销栈或放置动画资源;进入编辑后则使用当前内存文档。调用 viewer.canExportTurntableGif() 可判断当前加载源是否足以导出。
exportTurntableGifAs() 会拉起系统文件保存器。用户取消时返回 undefined,不是错误。也可调用 exportTurntableGifToUri(uri, options),把已获得授权的 URI 交给组件写入。导出期间 VoxelEditorState.exporting 为 true,并且 editor.isUpdating() 也为 true;业务 UI 应禁用重复导出、保存和编辑命令。
外观与边界
- 导出器基于体素的贪心表面计划做软件离屏绘制,有统一的方向明暗,但不是 ArkGraphics 的实时 PBR 截图。
- 不包含
VoxelViewer的背景图片、地面网格、实时阴影、Bloom、单位描边和业务 UI;当前光照继承是对主光与环境亮度的软件近似,不是 Surface 像素复制。 - GIF 只有“完全透明 / 完全不透明”两种 alpha,不能表达半透明阴影或柔和背景。若需要连续透明度,应使用 PNG 序列或未来的视频导出能力。
- 默认光栅内核由 HAR 内置的
libvoxelkit_compiler.so承载,但不调用 VFP 编译 API;GIF 光栅职责与闭源 VFP 编译器彼此独立。HAR 仍保留可审计的 ArkTS 回退实现。导出器不能把 VFP 容器缓存或任意 GLB 当作导出源。
下一步可阅读 编辑器保存与性能,了解 JSON/VFP 保存与 GIF 导出的职责差异。