Web3D 在线3D模型骨骼动画编辑器(开源 Reze Studio)

Web3D 在线3D模型骨骼动画编辑器 Reze Studio

「32 Web3D 在线3D模型骨骼动画编辑器THreeJS」

/~f4543YKLSZ~:/

链接:https://pan.quark.cn/s/c83b484c466b
预览: https://reze-studio.vercel.app/

一个现代化的、原生Web的MMD动画编辑器------专为手动关键帧编辑.vmd片段而设计的独立时间轴和曲线编辑器,摆脱了仅限Windows的桌面安装限制。它并非一个完全的MMD替代品(目前不支持MME风格的着色器或视频导出),也无意成为Maya或Blender;它是一个专注的、跨平台工具,旨在出色地完成动画编辑工作。通过WebGPU和reze引擎(集成Ammo.js物理引擎、IK解算器)直接在GPU上运行渲染,可在从iPad到游戏笔记本的任何设备上提供高帧率播放和流畅交互。

功能特性

  • PMX模型和VMD动画加载与渲染,支持IK和物理效果

  • 时间轴,包含摄影表和逐通道曲线编辑器

  • 贝塞尔插值曲线编辑

  • 在播放头位置插入/删除关键帧

  • VMD导入/导出

  • 从本地文件夹加载用户的PMX模型

  • 骨骼列表,支持分组层级结构

  • 表情(Morph)列表

  • 旋转/平移滑块,支持直接数字输入

  • 表情权重关键帧设置

  • 针对片段编辑的撤销/重做

  • 轨道操作:简化(关键帧精简)、清空

  • 键盘快捷键

  • 标签页关闭/刷新时的未保存更改警告

  • 视口骨骼拾取(双击)+ 3D变换控件(Gizmo)拖动

  • 在材质面板中拾取材质并显示高亮轮廓

  • 支持混合权重和骨骼蒙版的动画层

  • 支持静音/独奏切换的自定义骨骼组

  • 片段操作:剪切、复制、粘贴、镜像粘贴(左↔右)、导入、时间拉伸

  • 动作捕捉导入(视频 → VMD)

  • Overleaf风格的实时协作

  • AI辅助动画(生成式补间、动作重定向)

快速开始

  1. 打开 reze.studio------ 会加载一个默认的Reze模型和示例片段,因此您可以立即开始编辑。

  2. (可选)加载您自己的模型:文件加载PMX文件夹...,选择包含.pmx文件的文件夹(纹理文件必须与.pmx文件位于同一目录)。

  3. (可选)加载现有片段,或从头开始:文件加载VMD...导入现有的 .vmd 文件,或选择 文件新建以清空时间轴,在已加载的模型上自行设置关键帧动画。

  4. 播放动画:按 空格键或点击播放按钮。

  5. 保存您的编辑:文件导出VMD...。没有服务器参与------所有操作都在您的浏览器中完成,因此请在关闭标签页前导出。

动画编辑流程简介

如果您从未手动设置过关键帧动画,可以这样理解:一个片段是每个骨骼(和每个表情)的关键帧 (keyframe) 列表------即"在第N帧,骨骼处于此姿态"的快照。引擎会在关键帧之间进行插值,使角色动作平滑流畅。编辑片段意味着移动、添加或调整这些关键帧。

在 Reze Studio 中的典型工作流程:

  1. 选择一个骨骼。在左侧面板、摄影表(dope sheet)中点击它,或在视口中双击模型。右侧的"属性检查器"(Properties Inspector)会显示其旋转/平移值以及该骨骼上的所有关键帧,并且在3D视图中该骨骼位置会出现一个环形/轴控制器(Gizmo)。

  2. 跳转到某一帧 。在时间轴中拖动播放头,或使用 / 键逐帧步进。视口会实时更新。

  3. 调整骨骼姿态。在检查器中拖动旋转/平移滑块、直接输入数字,或在视口中拖动Gizmo(环形控制旋转,轴控制平移)。这两种方式都会写入当前帧的关键帧------如果该帧不存在关键帧,则会自动插入一个。每次拖动操作将被记录为一个独立的可撤销编辑单元。

  4. 调整关键帧间的运动曲线。在摄影表中选择一个关键帧,然后打开曲线编辑器选项卡。每个通道(rotX, rotY, rotZ, tX, tY, tZ)都有其独立的贝塞尔曲线------拖动手柄可以改变缓动效果。这是将"僵硬"动画变得"生动"的关键。

  5. 删除/微移/拖拽关键帧。在摄影表中,您可以左右拖动菱形关键帧以调整时间,或选择并删除。方向键可逐帧微调。

  6. 清理轨道 。在属性检查器中,简化功能可移除选定骨骼上的冗余关键帧(即被相邻关键帧间的贝塞尔曲线在指定的旋转/平移容差内精确复现的关键帧)。清空则会完全清除该轨道。两者都可撤销。

  7. 撤销错误操作Ctrl/+Z撤销上一次片段编辑;Ctrl/+Shift+Z(或 +Y)重做。历史记录保留最近的100次编辑。加载新的VMD或PMX文件不会进入历史堆栈------否则会导致加载的模型与之不同步。

  8. 检查材质。打开"材质"选项卡(右侧面板),点击材质名称可在视口中高亮显示------便于确认网格对应关系。点击同一名称或列表中的任意空白区域可清除高亮。材质选择与骨骼/表情选择互斥。

  9. 对所有骨骼重复上述步骤,直到姿态流畅。最后导出为VMD文件。

键盘快捷键

按键 功能
空格键 播放 / 暂停
/ 后退 / 前进一帧
Home 跳转到第一帧
End 跳转到最后一帧
Ctrl/ + Z 撤销上一次片段编辑
Ctrl/ + Shift+ Z, +Y 重做
在帧数输入框内使用 / 递减 / 递增播放头所在帧
Shift+ 鼠标滚轮 缩放数值 / Y轴
Ctrl/ + 鼠标滚轮 缩放时间 / X轴

技术栈

  • 引擎: reze-engine ------ WebGPU渲染器,Ammo.js物理引擎,IK解算器

  • 编辑器: Next.js 16, React 19, TypeScript, shadcn/ui, Tailwind

架构

除了作为MMD编辑器,此仓库也是对"如何在React中实现响应迅速的时间轴编辑器"的一项研究。时间轴编辑器是对框架的极限压力测试:它需要高帧率的播放头、多轴拖拽、数千个关键帧,以及绝不能卡顿的WebGPU画布------所有这些都与常规的React UI共享同一棵树。本节记录了Reze Studio如何实现这一目标。

简而言之 ------ React工程要点

  • 分离外部状态存储 。文档/选择状态存在于 <Studio>中;播放控制(播放头、播放状态)存在于 <Playback>中。播放控制以rAF(requestAnimationFrame)频率更新,不会触发撤销/重做目标的无效更新。

  • useSyncExternalStore+ 选择器模式 。组件订阅单一数据片段(useStudioSelector(s => s.field)),仅在数据片段变化时重新渲染。操作包(useStudioActions())保持稳定,不会导致重新渲染。

  • 热路径完全绕过React。播放、关键帧拖拽和姿态滑块拖拽都通过命令式操作直接修改引用/对象,通过命令式句柄重绘画布,并且仅在释放时与React交互一次。

  • currentFrameRef逃生舱。播放控制存储持有一个引用,EngineBridge的rAF循环直接向其写入。非订阅的消费者(检查器采样器、PMX交换快照)无需触发重新渲染即可读取实时播放头。

  • 具备快照桥接撤销功能的类Reducer核心结构 。由于预览期间的编辑会直接修改当前片段,存储还会维护一个不可变的clipSnapshot(在最后一次提交/撤销/重做时进行的深拷贝)。commit()将该快照推入历史记录(past)------而不是被修改的片段------因此历史记录永远不会捕获到拖拽中的中间状态。

提供者树结构

复制代码
<Studio>                          // 外部存储 ------ 片段 + 选择状态(撤销/重做目标)
  └─ <Playback>                   // 外部存储 ------ 当前帧、播放状态(rAF循环更新不触发重渲染)
       └─ <StudioStatusProvider>  // 外部存储 ------ PMX名称、FPS、消息(与页面重渲染隔离)
            └─ <StudioPage>       // 布局外壳 + 文件处理器
                 ├─ <EngineBridge>          // 无头组件 ------ 所有与引擎相关的副作用,返回 null
                 ├─ <StudioLeftPanel>       // 被记忆化 ------ 骨骼列表、表情列表、文件菜单
                 ├─ <StudioViewport>        // 被记忆化 ------ WebGPU <canvas>
                 ├─ <Timeline>              // 切片订阅 ------ 摄影表 + 曲线编辑器
                 │    └─ <TimelineCanvas>   // 命令式播放头 + 拖拽重绘句柄
                 ├─ <PropertiesInspector>   // 切片订阅 ------ 姿态滑块、表情权重(播放期间通过rAF自行采样)
                 └─ <StudioStatusFooter>    // 切片订阅 ------ PMX名称、FPS、片段名称

状态分层

层级 所在位置 说明
文档 context/studio-context.ts 外部存储,切片订阅,撤销/重做目标
选择状态 context/studio-context.ts 骨骼、表情、关键帧
播放控制 context/playback-context.ts 外部存储;currentFrame, playing;存储自身拥有的currentFrameRef供rAF消费者使用(见下文注释)
状态栏 components/studio-status.tsx 外部存储;PMX文件名、FPS、临时消息
引擎引用 StudioPage engineRef, modelRef, canvasRef
视图 Timeline中的本地 useState 缩放、滚动、选项卡
界面 StudioPage中的本地 useState 菜单栏、文件选择对话框

播放控制注释currentFrameRef通过 usePlaybackFrameRef()共享。EngineBridge 的 rAF 循环直接将实时播放头写入 .current而不经过 set(),因此非订阅的消费者无需任何React工作即可读取实时帧。

订阅模型

Studio(文档/选择状态)、Playback(播放控制)和 StudioStatus(状态栏)都是基于 useSyncExternalStore的外部存储。组件通过 useStudioSelector(s => s.field)/ usePlaybackSelector(...)/ useStudioStatusSelector(...)读取,因此每个组件仅在其依赖的数据片段变化时重新渲染,并通过 use*Actions()写入,这些操作返回稳定的操作包,不会导致重新渲染。包装存储内部的 set()也是撤销/重做的挂载点------commit()将快照推入历史记录,replaceClip()(用于VMD/PMX加载和"新建")清除历史记录,选择状态的变化从不触及撤销堆栈。

热路径 ------ 交互时零React更新

三种高频交互(播放、关键帧拖拽、姿态滑块拖拽)都遵循相同的模式:命令式地修改引用/对象,通过命令式句柄重绘画布,并在释放时仅与React交互一次。

  • 播放 ​ ------ <EngineBridge>的 rAF 循环读取引擎时钟,将实时帧写入播放控制存储的 currentFrameRef(通过 usePlaybackFrameRef()共享的唯一引用),并调用 playheadDrawRef.current(frame)------ 这是 <TimelineCanvas>暴露的一个句柄,用于直接重绘播放头叠加层。没有每次tick的 setCurrentFrame,因此不会重新渲染,但任何非订阅的消费者(检查器姿态采样、PMX交换快照)仍然可以通过该引用看到实时帧。自动滚动(当播放头移出视口时翻页)位于相同的命令式路径中,仅在罕见的翻页边界处触及React。暂停时,最终帧会通过 setCurrentFrame刷新,以便暂停视图与最后绘制的内容一致。

  • 实时姿态/表情读数 ​ ------ <PropertiesInspector>在隔离的叶子子组件中采样所选骨骼的姿态和表情权重。暂停时,它订阅 currentFrame并在变化时重新采样;播放时,它运行自己的小型 rAF 循环,直接读取 modelRef.currentruntimeSkeleton/ getMorphWeights(),并通过相等性检查进行门控,以防止未变化的帧触发协调。这将逐帧的工作保持在父检查器和 <StudioPage>之外。

  • 关键帧拖拽 ​ ------ <Timeline>的移动回调命令式地修改关键帧的 frame/ 通道值 / 轨道顺序,并触发 dragRedrawRef.current(),这会增加一个用于静态层缓存失效检查的内部拖拽版本号,并重绘画布。selectedKeyframes条目被命令式地修改,使高亮跟随拖拽。在鼠标弹起时,一个单独的 commit()克隆轨道 Maps 并创建撤销/重做快照,同时通过 <EngineBridge>执行一次 model.loadClip

  • 姿态滑块拖拽 ​ ------ <PropertiesInspector>apply*Axis/ applyMorphWeight函数在每次拖拽tick中以"预览"模式运行:原地修改匹配的关键帧(或插入一个),然后执行 model.loadClip+ 跳转以更新3D视口。没有 commit(),因此时间轴保持静态,检查器也不会协调。<AxisSliderRow>在拖拽期间保持本地的滑块值,以防止Radix控件回弹到过期的受控属性值。在 onValueCommit时,触发一个单独的克隆 + commit()操作 ------ 只有这个 commit()会进入撤销历史,因此一次拖拽是一个可撤销的单元,而不是数百个预览帧。

Reze Studio

一个现代化的、原生Web的MMD动画编辑器------专为手动关键帧编辑.vmd片段而设计的独立时间轴和曲线编辑器,摆脱了仅限Windows的桌面安装限制。它并非一个完全的MMD替代品(目前不支持MME风格的着色器或视频导出),也无意成为Maya或Blender;它是一个专注的、跨平台工具,旨在出色地完成动画编辑工作。通过WebGPU和reze引擎(集成Ammo.js物理引擎、IK解算器)直接在GPU上运行渲染,可在从iPad到游戏笔记本的任何设备上提供高帧率播放和流畅交互。

功能特性

  • PMX模型和VMD动画加载与渲染,支持IK和物理效果

  • 时间轴,包含摄影表和逐通道曲线编辑器

  • 贝塞尔插值曲线编辑

  • 在播放头位置插入/删除关键帧

  • VMD导入/导出

  • 从本地文件夹加载用户的PMX模型

  • 骨骼列表,支持分组层级结构

  • 表情(Morph)列表

  • 旋转/平移滑块,支持直接数字输入

  • 表情权重关键帧设置

  • 针对片段编辑的撤销/重做

  • 轨道操作:简化(关键帧精简)、清空

  • 键盘快捷键

  • 标签页关闭/刷新时的未保存更改警告

  • 视口骨骼拾取(双击)+ 3D变换控件(Gizmo)拖动

  • 在材质面板中拾取材质并显示高亮轮廓

  • 支持混合权重和骨骼蒙版的动画层

  • 支持静音/独奏切换的自定义骨骼组

  • 片段操作:剪切、复制、粘贴、镜像粘贴(左↔右)、导入、时间拉伸

  • 动作捕捉导入(视频 → VMD)

  • Overleaf风格的实时协作

  • AI辅助动画(生成式补间、动作重定向)

快速开始

  1. 打开 reze.studio------ 会加载一个默认的Reze模型和示例片段,因此您可以立即开始编辑。

  2. (可选)加载您自己的模型:文件加载PMX文件夹...,选择包含.pmx文件的文件夹(纹理文件必须与.pmx文件位于同一目录)。

  3. (可选)加载现有片段,或从头开始:文件加载VMD...导入现有的 .vmd 文件,或选择 文件新建以清空时间轴,在已加载的模型上自行设置关键帧动画。

  4. 播放动画:按 空格键或点击播放按钮。

  5. 保存您的编辑:文件导出VMD...。没有服务器参与------所有操作都在您的浏览器中完成,因此请在关闭标签页前导出。

动画编辑流程简介

如果您从未手动设置过关键帧动画,可以这样理解:一个片段是每个骨骼(和每个表情)的关键帧 (keyframe) 列表------即"在第N帧,骨骼处于此姿态"的快照。引擎会在关键帧之间进行插值,使角色动作平滑流畅。编辑片段意味着移动、添加或调整这些关键帧。

在 Reze Studio 中的典型工作流程:

  1. 选择一个骨骼。在左侧面板、摄影表(dope sheet)中点击它,或在视口中双击模型。右侧的"属性检查器"(Properties Inspector)会显示其旋转/平移值以及该骨骼上的所有关键帧,并且在3D视图中该骨骼位置会出现一个环形/轴控制器(Gizmo)。

  2. 跳转到某一帧 。在时间轴中拖动播放头,或使用 / 键逐帧步进。视口会实时更新。

  3. 调整骨骼姿态。在检查器中拖动旋转/平移滑块、直接输入数字,或在视口中拖动Gizmo(环形控制旋转,轴控制平移)。这两种方式都会写入当前帧的关键帧------如果该帧不存在关键帧,则会自动插入一个。每次拖动操作将被记录为一个独立的可撤销编辑单元。

  4. 调整关键帧间的运动曲线。在摄影表中选择一个关键帧,然后打开曲线编辑器选项卡。每个通道(rotX, rotY, rotZ, tX, tY, tZ)都有其独立的贝塞尔曲线------拖动手柄可以改变缓动效果。这是将"僵硬"动画变得"生动"的关键。

  5. 删除/微移/拖拽关键帧。在摄影表中,您可以左右拖动菱形关键帧以调整时间,或选择并删除。方向键可逐帧微调。

  6. 清理轨道 。在属性检查器中,简化功能可移除选定骨骼上的冗余关键帧(即被相邻关键帧间的贝塞尔曲线在指定的旋转/平移容差内精确复现的关键帧)。清空则会完全清除该轨道。两者都可撤销。

  7. 撤销错误操作Ctrl/+Z撤销上一次片段编辑;Ctrl/+Shift+Z(或 +Y)重做。历史记录保留最近的100次编辑。加载新的VMD或PMX文件不会进入历史堆栈------否则会导致加载的模型与之不同步。

  8. 检查材质。打开"材质"选项卡(右侧面板),点击材质名称可在视口中高亮显示------便于确认网格对应关系。点击同一名称或列表中的任意空白区域可清除高亮。材质选择与骨骼/表情选择互斥。

  9. 对所有骨骼重复上述步骤,直到姿态流畅。最后导出为VMD文件。

键盘快捷键

按键 功能
空格键 播放 / 暂停
/ 后退 / 前进一帧
Home 跳转到第一帧
End 跳转到最后一帧
Ctrl/ + Z 撤销上一次片段编辑
Ctrl/ + Shift+ Z, +Y 重做
在帧数输入框内使用 / 递减 / 递增播放头所在帧
Shift+ 鼠标滚轮 缩放数值 / Y轴
Ctrl/ + 鼠标滚轮 缩放时间 / X轴

技术栈

  • 引擎: reze-engine ------ WebGPU渲染器,Ammo.js物理引擎,IK解算器

  • 编辑器: Next.js 16, React 19, TypeScript, shadcn/ui, Tailwind

架构

除了作为MMD编辑器,此仓库也是对"如何在React中实现响应迅速的时间轴编辑器"的一项研究。时间轴编辑器是对框架的极限压力测试:它需要高帧率的播放头、多轴拖拽、数千个关键帧,以及绝不能卡顿的WebGPU画布------所有这些都与常规的React UI共享同一棵树。本节记录了Reze Studio如何实现这一目标。

简而言之 ------ React工程要点

  • 分离外部状态存储 。文档/选择状态存在于 <Studio>中;播放控制(播放头、播放状态)存在于 <Playback>中。播放控制以rAF(requestAnimationFrame)频率更新,不会触发撤销/重做目标的无效更新。

  • useSyncExternalStore+ 选择器模式 。组件订阅单一数据片段(useStudioSelector(s => s.field)),仅在数据片段变化时重新渲染。操作包(useStudioActions())保持稳定,不会导致重新渲染。

  • 热路径完全绕过React。播放、关键帧拖拽和姿态滑块拖拽都通过命令式操作直接修改引用/对象,通过命令式句柄重绘画布,并且仅在释放时与React交互一次。

  • currentFrameRef逃生舱。播放控制存储持有一个引用,EngineBridge的rAF循环直接向其写入。非订阅的消费者(检查器采样器、PMX交换快照)无需触发重新渲染即可读取实时播放头。

  • 具备快照桥接撤销功能的类Reducer核心结构 。由于预览期间的编辑会直接修改当前片段,存储还会维护一个不可变的clipSnapshot(在最后一次提交/撤销/重做时进行的深拷贝)。commit()将该快照推入历史记录(past)------而不是被修改的片段------因此历史记录永远不会捕获到拖拽中的中间状态。

提供者树结构

复制代码
复制代码
复制代码
<Studio>                          // 外部存储 ------ 片段 + 选择状态(撤销/重做目标)
  └─ <Playback>                   // 外部存储 ------ 当前帧、播放状态(rAF循环更新不触发重渲染)
       └─ <StudioStatusProvider>  // 外部存储 ------ PMX名称、FPS、消息(与页面重渲染隔离)
            └─ <StudioPage>       // 布局外壳 + 文件处理器
                 ├─ <EngineBridge>          // 无头组件 ------ 所有与引擎相关的副作用,返回 null
                 ├─ <StudioLeftPanel>       // 被记忆化 ------ 骨骼列表、表情列表、文件菜单
                 ├─ <StudioViewport>        // 被记忆化 ------ WebGPU <canvas>
                 ├─ <Timeline>              // 切片订阅 ------ 摄影表 + 曲线编辑器
                 │    └─ <TimelineCanvas>   // 命令式播放头 + 拖拽重绘句柄
                 ├─ <PropertiesInspector>   // 切片订阅 ------ 姿态滑块、表情权重(播放期间通过rAF自行采样)
                 └─ <StudioStatusFooter>    // 切片订阅 ------ PMX名称、FPS、片段名称

状态分层

层级 所在位置 说明
文档 context/studio-context.ts 外部存储,切片订阅,撤销/重做目标
选择状态 context/studio-context.ts 骨骼、表情、关键帧
播放控制 context/playback-context.ts 外部存储;currentFrame, playing;存储自身拥有的currentFrameRef供rAF消费者使用(见下文注释)
状态栏 components/studio-status.tsx 外部存储;PMX文件名、FPS、临时消息
引擎引用 StudioPage engineRef, modelRef, canvasRef
视图 Timeline中的本地 useState 缩放、滚动、选项卡
界面 StudioPage中的本地 useState 菜单栏、文件选择对话框

播放控制注释currentFrameRef通过 usePlaybackFrameRef()共享。EngineBridge 的 rAF 循环直接将实时播放头写入 .current而不经过 set(),因此非订阅的消费者无需任何React工作即可读取实时帧。

订阅模型

Studio(文档/选择状态)、Playback(播放控制)和 StudioStatus(状态栏)都是基于 useSyncExternalStore的外部存储。组件通过 useStudioSelector(s => s.field)/ usePlaybackSelector(...)/ useStudioStatusSelector(...)读取,因此每个组件仅在其依赖的数据片段变化时重新渲染,并通过 use*Actions()写入,这些操作返回稳定的操作包,不会导致重新渲染。包装存储内部的 set()也是撤销/重做的挂载点------commit()将快照推入历史记录,replaceClip()(用于VMD/PMX加载和"新建")清除历史记录,选择状态的变化从不触及撤销堆栈。

热路径 ------ 交互时零React更新

三种高频交互(播放、关键帧拖拽、姿态滑块拖拽)都遵循相同的模式:命令式地修改引用/对象,通过命令式句柄重绘画布,并在释放时仅与React交互一次。

  • 播放 ​ ------ <EngineBridge>的 rAF 循环读取引擎时钟,将实时帧写入播放控制存储的 currentFrameRef(通过 usePlaybackFrameRef()共享的唯一引用),并调用 playheadDrawRef.current(frame)------ 这是 <TimelineCanvas>暴露的一个句柄,用于直接重绘播放头叠加层。没有每次tick的 setCurrentFrame,因此不会重新渲染,但任何非订阅的消费者(检查器姿态采样、PMX交换快照)仍然可以通过该引用看到实时帧。自动滚动(当播放头移出视口时翻页)位于相同的命令式路径中,仅在罕见的翻页边界处触及React。暂停时,最终帧会通过 setCurrentFrame刷新,以便暂停视图与最后绘制的内容一致。

  • 实时姿态/表情读数 ​ ------ <PropertiesInspector>在隔离的叶子子组件中采样所选骨骼的姿态和表情权重。暂停时,它订阅 currentFrame并在变化时重新采样;播放时,它运行自己的小型 rAF 循环,直接读取 modelRef.currentruntimeSkeleton/ getMorphWeights(),并通过相等性检查进行门控,以防止未变化的帧触发协调。这将逐帧的工作保持在父检查器和 <StudioPage>之外。

  • 关键帧拖拽 ​ ------ <Timeline>的移动回调命令式地修改关键帧的 frame/ 通道值 / 轨道顺序,并触发 dragRedrawRef.current(),这会增加一个用于静态层缓存失效检查的内部拖拽版本号,并重绘画布。selectedKeyframes条目被命令式地修改,使高亮跟随拖拽。在鼠标弹起时,一个单独的 commit()克隆轨道 Maps 并创建撤销/重做快照,同时通过 <EngineBridge>执行一次 model.loadClip

  • 姿态滑块拖拽 ​ ------ <PropertiesInspector>apply*Axis/ applyMorphWeight函数在每次拖拽tick中以"预览"模式运行:原地修改匹配的关键帧(或插入一个),然后执行 model.loadClip+ 跳转以更新3D视口。没有 commit(),因此时间轴保持静态,检查器也不会协调。<AxisSliderRow>在拖拽期间保持本地的滑块值,以防止Radix控件回弹到过期的受控属性值。在 onValueCommit时,触发一个单独的克隆 + commit()操作 ------ 只有这个 commit()会进入撤销历史,因此一次拖拽是一个可撤销的单元,而不是数百个预览帧。

简化轨道(关键帧精简)

MMD的插值模型使得经典的Ramer--Douglas--Peucker算法不太适用:每一帧存储一条完整的记录,旋转是一个由单个贝塞尔曲线控制的slerp-t插值(因此rotX/rotY/rotZ共享一个线段,而非独立的曲线),而平移具有三个独立的每轴贝塞尔曲线。Reze Studio 使用了一种专为此模型设计的、类似Schneider的自顶向下拟合方法,而不是逐个丢弃关键帧:

  1. [第一帧, 最后一帧]范围内,对原始轨道在每一整数帧进行密集采样。

  2. 尝试用一条VMD段覆盖整个区间------包含四个独立的贝塞尔曲线(旋转slerp-t + tX/tY/tZ)。对于每条曲线,根据密集采样点匹配端点速度来初始化手柄,然后通过在127参数空间的粗糙5^4网格搜索以及围绕最佳结果的局部5^4搜索进行优化。

  3. 如果最大逐点误差 ≤ ε(旋转为测地角,平移为每轴距离),则输出一个关键帧,并折叠其间的所有中间关键帧。

  4. 否则,在最接近最大偏差帧的原始关键帧处分割,并对两部分递归执行上述操作。

  5. 相邻的原始关键帧会被原样保留,包括其手动设置的插值。

较早的贪心"如果可容忍则丢弃"算法存在一个细微缺陷:丢弃一个关键帧会继承保留关键帧的贝塞尔手柄,而这些手柄是为较短的线段设计的------将它们拉伸到更长的区间会扭曲速度曲线,即使点对点误差 ε 很严格,也会产生可见的抖动。为每个输出线段进行自定义拟合避免了这个问题。固定的容差为0.5° / 0.01单位,不提供用户调节旋钮。整个操作作为一次 commit()提交,因此一次简化是一个撤销步骤。

各部分职责所在文件

文件 职责
app/page.tsx Next.js入口 ------ 挂载所有提供者 + <StudioPage />
context/studio-context.ts 文档 + 选择状态存储,useStudioSelector, 操作
context/playback-context.ts 播放控制存储,选择器,操作,usePlaybackFrameRef
components/studio.tsx StudioPage------ 布局、文件处理、菜单栏、导出
components/studio-status.tsx 状态栏存储 + <StudioStatusFooter>
components/engine-bridge.tsx 与引擎相关的副作用(初始化、跳转、播放、rAF播放循环)
components/timeline.tsx 摄影表 + 曲线编辑器,命令式播放头 / 拖拽重绘
components/properties-inspector.tsx 姿态滑块、表情权重、插值编辑器
components/axis-slider-row.tsx 带有预览/提交分离 + 本地拖拽值的滑块行
相关推荐
柳杉2 小时前
有了大屏设计稿还不够,我又用 gpt-image-2把里面的素材扒了出来
前端·three.js·数据可视化
朝阳392 小时前
react 实战【svg 图片】插件 vite-plugin-svgr 的使用
前端·javascript·react.js
heiqizero2 小时前
spark01-创建RDD
linux·前端·python
水木流年追梦2 小时前
CodeTop Top 300 热门题目8-字符串解码
linux·运维·服务器·前端·算法·leetcode
csdn_aspnet2 小时前
HTML头部元信息避坑指南,深度解析charset、lang、meta标签常见误用与SEO/可访问性影响
javascript·meta·html·seo·title
Coffeeee2 小时前
用了一段时间的AI了,忍不住想吐点槽,你的AI帮你提效了吗?
前端·人工智能·程序员
XW-ABAP2 小时前
ABAP Dynamic Report Extraction and ADBC CRUD Practical Solution (V2)
开源
0pen12 小时前
ZygiskNext 源码解析(一):总体架构与启动链路
android·开源·zygote
大囚长2 小时前
deepseek v4象棋编写测试
css·html·css3