Hotdry.

Article

Ratty 终端内联 3D 图形渲染:RGP 协议与单元格空间锚定

解析 Ratty 终端模拟器如何通过 Ratty Graphics Protocol 在终端缓冲区中嵌入 GPU 加速的 3D 场景,涵盖 APC 转义序列协议设计与单元格空间映射工程。

2026-05-11systems

Ratty 的核心创新在于将 3D 渲染能力嵌入终端缓冲区。传统的终端模拟器将光标视为一个字符单元,而 Ratty 将光标重塑为一个可动画的 3D 模型,并将整个终端窗口转变为可透视的 3D 画布。这种设计灵感源自 TempleOS 的 DolDoc 文档格式 —— 在该系统中,命令行动态内嵌精灵图与 3D 网格作为一等公民。相比之下,Ratty 在保持 POSIX 兼容性的前提下,通过自定义图形协议实现了类似能力。

渲染管线架构

Ratty 的渲染管线分为三个解耦阶段。首阶段处理 PTY I/O 与转义序列解析:终端应用运行在由 portable-pty 管理的伪终端中,vt100 crate 负责解析 VT100 控制序列并维护终端屏幕状态。次阶段由 Ratatui 将终端状态重建为缓冲区,再通过 parley_ratatui 将其渲染为 GPU 纹理 —— 底层由 parley 处理字形整形与字体管理,Vello 承担 GPU 渲染任务。末阶段由 Bevy 接管纹理并将其置于 3D 场景中,支持相机运动、光照与模型动画。

这种分离设计的工程价值在于:终端仿真逻辑与视觉呈现完全解耦。PTY 一侧无需感知 3D 渲染的存在,而 Bevy 侧也无需理解终端协议细节。两者的唯一交集是 Ratatui 缓冲区 —— 它既是终端状态的重建结果,也是 3D 场景的纹理输入。

Ratty Graphics Protocol 设计

Ratty Graphics Protocol(RGP)采用 APC(Application Program Command)控制序列作为载体,格式为:

ESC _ ratty ; g ; <verb> [ ; <key=value> ... ] ESC \

其中 ratty 为协议命名空间,g 为图形子命名空间,<verb> 指定操作类型。当前协议定义四个核心操作:s 查询终端能力、r 注册 3D 资产、p 将对象锚定至终端单元格空间、d 删除已注册对象。

能力查询响应的格式如下:

ESC _ ratty;g;s;v=1;fmt=obj|glb;path=1;anim=1;depth=1;color=1;brightness=1 ESC \

各参数表明终端支持的格式(obj 或 glb)、路径引用、动画、深度偏移、颜色覆盖与亮度调节能力。资产注册的示例:

ESC _ ratty;g;r;id=7;fmt=obj;path=CairoSpinyMouse.obj ESC \

注册后通过 placement 操作将其嵌入单元格空间:

ESC _ ratty;g;p;id=7;row=5;col=10;w=3;h=2;animate=1;scale=1.0;depth=1.5;color=7fd0ff;brightness=1.0 ESC \

单元格空间锚定原理

RGP 的核心抽象是将 3D 对象锚定到终端单元格坐标。当锚点单元格因终端输出而移动时,3D 对象随之位移,保持与文本内容的空间关联。这一机制的实现依赖于从终端状态到场景空间的坐标映射。

Ratatui 侧将光标视为普通字符单元,通过空白字符占位;Bevy 侧独立维护光标实体(3D 模型句柄),每次帧更新时从终端状态查询光标位置。坐标转换逻辑如下:

let cell_width = viewport_size.x / cols as f32;
let cell_height = viewport_size.y / rows as f32;

let x = -viewport_size.x * 0.5 + (cursor_col as f32 + 0.5) * cell_width;
let y = viewport_size.y * 0.5 - (cursor_row as f32 + 0.5) * cell_height;

其中 viewport_size 是像素级视口尺寸,colsrows 是字符级终端尺寸。转换后的 (x, y) 作为 Bevy Transform 的平移分量,实现光标位置与终端内容的精确对齐。动画层面可在此基础上添加旋转与上下浮动效果:

let spin = elapsed_secs * 3.0;
let bob = (elapsed_secs * 5.0).sin() * cell_height * 0.15;

transform.translation = Vec3::new(x, y + bob, 10.0);
transform.rotation = Quat::from_rotation_y(spin) * Quat::from_rotation_x(-0.25);
transform.scale = Vec3::splat(cell_width.min(cell_height));

depth 参数控制对象在 Z 轴上的偏移量,正值使对象浮于终端文本之上,负值则嵌入文本之下,实现多层叠放效果。

Ratatui Widget 集成

对于基于 Ratatui 构建的 TUI 应用,ratatui-ratty crate 提供了零侵入式的集成方案。应用无需直接构造 APC 转义序列,仅需声明图形元数据并调用 Widget trait:

use ratatui_ratty::{RattyGraphic, RattyGraphicSettings};

let graphic = RatatuiGraphic::new(
    RattyGraphicSettings::new("CairoSpinyMouse.obj")
        .id(7)
        .animate(true)
        .scale(1.0)
        .depth(1.5)
        .color([0x7f, 0xd0, 0xff])
        .brightness(1.0),
);

graphic.register()?;

let mut buf = Buffer::empty(Rect::new(0, 0, 80, 24));
(&graphic).render(Rect::new(10, 5, 24, 10), &mut buf);

该 Widget 的内部实现并非直接将图形绘制到缓冲区,而是向 stdout 写入对应的 RGP 消息,由 Ratty 终端负责渲染。这种设计保持了 Ratatui 的缓冲区模型完整性,同时扩展了其渲染能力边界。

配置参数与性能考量

Ratty 的光标模型可通过配置文件进行调优,关键参数包括:spin_speed 控制旋转角速度、bob_speedbob_amplitude 控制垂直浮动频率与振幅、scale_factor 控制模型相对单元格尺寸的缩放比例、brightness 控制光照强度。资源配置层面,Ratty 因底层运行 Bevy 游戏引擎而具有较高的内存占用 —— 官方文档坦承约 300MB 初始内存消耗,这在传统终端模拟器中是异常值,但在支持实时 3D 渲染的上下文中属于合理范畴。

RGP 的协议扩展性通过 key=value 键值对实现,新增渲染特性仅需协商能力参数而不必修改协议主版本。格式层面同时支持 OBJ 与 glTF(glb)两种 3D 资产格式,前者便于原型验证,后者支持骨骼动画与材质贴图等高级特性。

工程边界与生态定位

RGP 的设计明确承认其架构依赖性 —— 协议与 Ratty 的渲染管线深度耦合,传统终端模拟器难以直接兼容。一种可行但不完美的兼容路径是将 3D 对象回退为 ASCII 艺术占位符,保持协议层面的功能可用性。另一种路径是激发更广泛的终端图形协议创新,催生跨终端的标准化方案。

作为 Rust 生态系统在终端图形领域的实验性尝试,Ratty 填补了 GPU 加速终端与 3D 渲染能力之间的空白。其技术路径表明:通过解耦终端仿真与视觉呈现,并借助成熟的开源组件(Ratatui + Bevy),可以在不重写整个终端生态的前提下实现渐进式能力扩展。

资料来源:Ratty 官方博客(blog.orhun.dev)与项目 GitHub 仓库。

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com