OBS Studio libobs 图形 API 详解:axisang(轴角表示)结构与四元数互转
本文基于 OBS Studio 官方 Sphinx 参考文档 docs/sphinx/reference-libobs-graphics-axisang.rst,完整讲解 libobs 图形模块中 axisang(轴角表示)辅助结构体的成员布局与全部公开 API,并结合 libobs/graphics/axisang.c、libobs/graphics/quat.c 等源码剖析轴角与四元数互转的数学实现、数值稳定性处理,以及它在渲染矩阵变换管线中的真实调用链,帮助读者在插件与滤镜开发中正确使用这一旋转表示。
1. axisang 的定位:libobs 旋转数据家族的一环
在 OBS Studio 的底层图形库 libobs 中,旋转数据主要通过三套类型表示:四元数 struct quat(libobs/graphics/quat.h)、轴角 struct axisang,以及由二者生成的旋转矩阵(struct matrix3 / struct matrix4)。
axisang 的定位非常明确——官方参考文档将其概括为 "Provides a helper structure for conversion to quaternions",即它是轴角表示(axis-angle)的辅助结构,核心价值在于与四元数之间的相互转换:
- 轴角表示直观易懂:一个旋转 = 一条单位轴 + 一个绕该轴转过的角度(弧度);
- 四元数在插值和组合上更稳健(
quat.h头注释说明四元数用于表示旋转数据,且插值时不受万向锁影响); axisang因此承担"人话接口"的角色:开发者按(x, y, z, angle)的直觉写入旋转,再通过quat_from_axisang进入 libobs 的核心旋转运算体系。
使用方式与文档一致,直接包含头文件即可:
#include <graphics/axisang.h>
注意 libobs 是 C 接口库,头文件内含 extern "C" 保护(见 libobs/graphics/axisang.h),C++ 插件可直接调用。
2. 结构体定义:union 布局与成员语义
文档描述的 axisang 结构在源码中的完整定义见 libobs/graphics/axisang.h:
struct quat;
struct axisang {
union {
struct {
float x, y, z, w;
};
float ptr[4];
};
};
对照文档中的成员说明:
| 成员 | 类型 | 语义 |
|---|---|---|
axisang.x |
float |
旋转轴的 X 分量 |
axisang.y |
float |
旋转轴的 Y 分量 |
axisang.z |
float |
旋转轴的 Z 分量 |
axisang.w |
float |
旋转角度(Angle) |
axisang.ptr[4] |
float[4] |
同一块内存的数组视图 |
几个值得注意的源码细节:
x, y, z是轴,w才是角度。这一点与四元数struct quat(libobs/graphics/quat.h)的布局刻意保持一致——quat中w是标量分量,而axisang中w被复用为角度,便于两套表示在渲染代码中混用时心智模型统一。- union +
ptr[4]的惯用手法。ptr提供把四个分量当作连续数组访问的能力,例如与 SIMD 或按内存块拷贝的场景对接。这与struct quat中额外叠加__m128 m成员的做法一脉相承(quat直接用 SSE 指令做四分量运算,见quat_add、quat_mulf等内联函数),而axisang没有引入__m128视图,从源码结构看它是纯辅助结构、不参与高频运算。 - 角度单位是弧度。从源码可以确认:
quat_from_axisang直接对w调用sinf(halfa)/cosf(halfa)(见第 4 节),调用方在需要角度制时显式使用 libobs/graphics/math-defs.h 中定义的RAD(val)/DEG(val)宏做转换,例如RAD(90.0f)得到π/2。
3. 四个 API 逐个讲解
3.1 axisang_zero:清零
文档签名:void axisang_zero(struct axisang *dst),功能 "Zeroes the axis angle"。
实现是头文件中的内联函数(libobs/graphics/axisang.h):
static inline void axisang_zero(struct axisang *dst)
{
dst->x = 0.0f;
dst->y = 0.0f;
dst->z = 0.0f;
dst->w = 0.0f;
}
清零后的轴角是"零旋转"的退化形态:轴全零、角度为零。
3.2 axisang_copy:拷贝
文档签名:void axisang_copy(struct axisang *dst, struct axisang *aa),功能 "Copies an axis angle",参数分别为拷贝目标 dst 与来源 aa。
实现(libobs/graphics/axisang.h):
static inline void axisang_copy(struct axisang *dst, struct axisang *aa)
{
dst->x = aa->x;
dst->y = aa->y;
dst->z = aa->z;
dst->w = aa->w;
}
逐分量赋值而非 memcpy,与 quat_copy 的风格一致,调用方需注意 dst 与 aa 不可重叠(源码未做别名保护,属于 C 库的常见约定)。
3.3 axisang_set:一次性赋值
文档签名:void axisang_set(struct axisang *dst, float x, float y, float z, float w),功能 "Sets an axis angle",参数依次是目标、X 轴、Y 轴、Z 轴、角度。
实现(libobs/graphics/axisang.h):
static inline void axisang_set(struct axisang *dst, float x, float y, float z, float w)
{
dst->x = x;
dst->y = y;
dst->z = z;
dst->w = w;
}
这是四个函数中最常用的一个:libobs 内部大量调用点都是"栈上临时构造一个轴角再立即使用"的模式,第 5 节会给出实例。
3.4 axisang_from_quat:从四元数创建轴角
文档签名:void axisang_from_quat(struct axisang *dst, const struct quat *q),功能 "Creates an axis angle from a quaternion",参数为轴角目标 dst 与待转换四元数 q。
这是四个 API 中唯一编译进 libobs 动态库导出(EXPORT)而非头文件内联的函数,实现位于 libobs/graphics/axisang.c:
void axisang_from_quat(struct axisang *dst, const struct quat *q)
{
float len, leni;
len = q->x * q->x + q->y * q->y + q->z * q->z;
if (!close_float(len, 0.0f, EPSILON)) {
leni = 1.0f / sqrtf(len);
dst->x = q->x * leni;
dst->y = q->y * leni;
dst->z = q->z * leni;
dst->w = acosf(q->w) * 2.0f;
} else {
dst->x = 0.0f;
dst->y = 0.0f;
dst->z = 0.0f;
dst->w = 0.0f;
}
}
这段实现完整体现了"轴角 = 四元数向量部分的方向 + 两倍的标量反余弦"这一数学关系,且有工程上值得学习的细节:
-
轴提取与归一化。四元数的向量部分
(x, y, z)本身就平行于旋转轴,其长度等于sin(θ/2)。代码先求平方和len,再乘1/sqrt(len)完成归一化,得到单位轴。 -
角度恢复。
dst->w = acosf(q->w) * 2.0f利用了q.w = cos(θ/2),即θ = 2·acos(q.w)。 -
近零旋转的退化分支。当
len与 0 的差值小于EPSILON时(单位四元数的恒等旋转w=1, x=y=z=0恰好落在此分支),输出全零轴角,避免对零向量做无意义的归一化。这里用到的工具定义在 libobs/graphics/math-defs.h:#define EPSILON 1e-4f static inline bool close_float(float f1, float f2, float precision) { return fabsf(f1 - f2) <= precision; }即判据是
|len - 0| <= 1e-4,对应|sin(θ/2)| ≤ ~0.01,也就是小于约 1.14° 的微小旋转统一视为零旋转。这是浮点安全设计:若不做此判断,恒等四元数会被转成"轴不定"的噪声值。
4. 正向链路:轴角如何变成四元数
虽然 axisang 文档只描述了 axisang_from_quat 这一个转换方向,但完整的往返链路里另一半 quat_from_axisang 是理解本结构体的关键,它声明在 libobs/graphics/quat.h,实现在 libobs/graphics/quat.c:
void quat_from_axisang(struct quat *dst, const struct axisang *aa)
{
float halfa = aa->w * 0.5f;
float sine = sinf(halfa);
dst->x = aa->x * sine;
dst->y = aa->y * sine;
dst->z = aa->z * sine;
dst->w = cosf(halfa);
}
实现即教科书公式 q = (sin(θ/2)·axis, cos(θ/2))。两点实用结论:
- 轴需要调用方保证为单位向量。从源码看,
quat_from_axisang直接以aa->x/y/z乘以sine,未做归一化;这与axisang_from_quat会归一化形成不对称——可以推断:由四元数转出的轴角总是单位轴,而手工axisang_set写入的轴若不为单位向量,生成的四元数长度将偏离 1,需要自行注意。 - 往返一致性依赖四元数为单位四元数这一前提,
axisang_from_quat的acosf(q->w)对非单位四元数会给出无意义结果。
5. 调用链实证:axisang 在 OBS 渲染管线中的位置
从源码结构看,axisang 并非孤立结构,它是渲染矩阵"从轴角到旋转矩阵"链路的入口。典型调用链为:
axisang_set()
→ quat_from_axisang() // libobs/graphics/quat.c
→ matrix3/4_from_axisang() // 经由 quat 生成旋转矩阵
→ matrix*_rotate_aa() / gs_matrix_rotaa4f() // 乘入当前矩阵
各环节的源码位置:
-
3x4 矩阵:libobs/graphics/matrix3.h 导出
matrix3_from_axisang、matrix3_rotate_aa,并内联了四参数便捷函数(matrix3.h):static inline void matrix3_rotate_aa4f(struct matrix3 *dst, const struct matrix3 *m, float x, float y, float z, float rot) { struct axisang aa; axisang_set(&aa, x, y, z, rot); matrix3_rotate_aa(dst, m, &aa); } -
4x4 矩阵:libobs/graphics/matrix4.h 提供
matrix4_from_axisang、matrix4_rotate_aa(后乘)与matrix4_rotate_aa_i(前乘)及matrix4_rotate_aa4f。前/后乘之分对应不同的变换组合次序,见 libobs/graphics/matrix4.c:void matrix4_from_axisang(struct matrix4 *dst, const struct axisang *aa) { struct quat q; quat_from_axisang(&q, aa); matrix4_from_quat(dst, &q); } -
渲染主循环接口:libobs/graphics/graphics.c 中的
gs_matrix_rotaa把轴角右乘到当前顶点的 top matrix;其四参数版本gs_matrix_rotaa4f则是axisang_set最典型的使用范式:void gs_matrix_rotaa4f(float x, float y, float z, float angle) { struct matrix4 *top_mat; struct axisang aa; if (!gs_valid("gs_matrix_rotaa4f")) return; top_mat = top_matrix(thread_graphics); if (top_mat) { axisang_set(&aa, x, y, z, angle); matrix4_rotate_aa_i(top_mat, &aa, top_mat); } }注意两处防御:
gs_valid检查图形子系统是否就绪,top_matrix判空,保证在无渲染上下文的线程中调用不会崩溃。
真实调用点(可作为使用范例直接参考):
-
异步视频源的旋转渲染:libobs/obs-source.c 在处理带旋转的异步源时,先平移再绕 Z 轴旋转,角度由度数经
RAD宏转为弧度:gs_matrix_translate3f(x, y, 0); gs_matrix_rotaa4f(0.0f, 0.0f, -1.0f, RAD((float)rotation)); -
预览窗口选择手柄的绘制:frontend/widgets/OBSBasicPreview.cpp 中围绕手柄中心旋转绘制,同样走
gs_matrix_rotaa4f(0, 0, 1, RAD(rot))的路径,配合gs_matrix_push/pop保存恢复矩阵栈。 -
注视方向四元数构造:libobs/graphics/quat.c 的
quat_set_look_dir内部用axisang_set分别构造绕 Y 轴的偏航与绕 X 轴的俯仰两个轴角,再各自quat_from_axisang合成最终朝向,展示了"轴角作为中间量构造四元数"的复合用法。
6. 工程要点小结
结合文档与源码,使用 axisang 时可归纳为:
- 四个 API 的分工:
axisang_zero/axisang_set负责初始化,axisang_copy负责传递,axisang_from_quat负责从四元数逆向提取轴角;前三个为头文件内联函数(libobs/graphics/axisang.h),后者为库导出符号(libobs/graphics/axisang.c),在 libobs/CMakeLists.txt 的构建目标中,graphics/axisang.c计入私有源文件而graphics/axisang.h计入public_headers对外安装。 - 单位约定:
w分量是弧度角度;x, y, z建议为单位轴向量,quat_from_axisang不做归一化。 - 数值边界:小于
EPSILON(1e-4,见 libobs/graphics/math-defs.h)的旋转分量在axisang_from_quat中会被归零输出,这是刻意的稳定化设计,测试或比较轴角结果时应考虑这一阈值。 - 适用场景:需要"轴 + 角"这种人类可读的旋转描述时(如 UI 角度滑块、场景变换的旋转角),用
axisang建模,随后交给quat_from_axisang或matrix*_from_axisang进入 libobs 的标准旋转管线;而高频插值与组合运算则直接使用struct quat(其头部注释明确说明四元数用于避免万向锁的旋转插值)。
7. 延伸阅读路径
- 四元数完整 API:docs/sphinx/reference-libobs-graphics-quat.rst、libobs/graphics/quat.h
- 矩阵接口(含
from_axisang/rotate_aa系列):docs/sphinx/reference-libobs-graphics-matrix4.rst、libobs/graphics/matrix4.h、libobs/graphics/matrix3.h - 图形子系统总览与
gs_matrix_*矩阵接口:docs/sphinx/reference-libobs-graphics-graphics.rst、libobs/graphics/graphics.h - 图形模块文档入口:docs/sphinx/graphics.rst
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00