首页
/ OBS Studio libobs 图形 API 详解:axisang(轴角表示)结构与四元数互转

OBS Studio libobs 图形 API 详解:axisang(轴角表示)结构与四元数互转

2026-09-06 17:19:52作者:侯霆垣

本文基于 OBS Studio 官方 Sphinx 参考文档 docs/sphinx/reference-libobs-graphics-axisang.rst,完整讲解 libobs 图形模块中 axisang(轴角表示)辅助结构体的成员布局与全部公开 API,并结合 libobs/graphics/axisang.clibobs/graphics/quat.c 等源码剖析轴角与四元数互转的数学实现、数值稳定性处理,以及它在渲染矩阵变换管线中的真实调用链,帮助读者在插件与滤镜开发中正确使用这一旋转表示。

1. axisang 的定位:libobs 旋转数据家族的一环

在 OBS Studio 的底层图形库 libobs 中,旋转数据主要通过三套类型表示:四元数 struct quatlibobs/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] 同一块内存的数组视图

几个值得注意的源码细节:

  1. x, y, z 是轴,w 才是角度。这一点与四元数 struct quatlibobs/graphics/quat.h)的布局刻意保持一致——quatw 是标量分量,而 axisangw 被复用为角度,便于两套表示在渲染代码中混用时心智模型统一。
  2. union + ptr[4] 的惯用手法ptr 提供把四个分量当作连续数组访问的能力,例如与 SIMD 或按内存块拷贝的场景对接。这与 struct quat 中额外叠加 __m128 m 成员的做法一脉相承(quat 直接用 SSE 指令做四分量运算,见 quat_addquat_mulf 等内联函数),而 axisang 没有引入 __m128 视图,从源码结构看它是纯辅助结构、不参与高频运算。
  3. 角度单位是弧度。从源码可以确认: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 的风格一致,调用方需注意 dstaa 不可重叠(源码未做别名保护,属于 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;
	}
}

这段实现完整体现了"轴角 = 四元数向量部分的方向 + 两倍的标量反余弦"这一数学关系,且有工程上值得学习的细节:

  1. 轴提取与归一化。四元数的向量部分 (x, y, z) 本身就平行于旋转轴,其长度等于 sin(θ/2)。代码先求平方和 len,再乘 1/sqrt(len) 完成归一化,得到单位轴。

  2. 角度恢复dst->w = acosf(q->w) * 2.0f 利用了 q.w = cos(θ/2),即 θ = 2·acos(q.w)

  3. 近零旋转的退化分支。当 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_quatacosf(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_axisangmatrix3_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_axisangmatrix4_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 判空,保证在无渲染上下文的线程中调用不会崩溃。

真实调用点(可作为使用范例直接参考):

  1. 异步视频源的旋转渲染libobs/obs-source.c 在处理带旋转的异步源时,先平移再绕 Z 轴旋转,角度由度数经 RAD 宏转为弧度:

    gs_matrix_translate3f(x, y, 0);
    gs_matrix_rotaa4f(0.0f, 0.0f, -1.0f, RAD((float)rotation));
    
  2. 预览窗口选择手柄的绘制frontend/widgets/OBSBasicPreview.cpp 中围绕手柄中心旋转绘制,同样走 gs_matrix_rotaa4f(0, 0, 1, RAD(rot)) 的路径,配合 gs_matrix_push/pop 保存恢复矩阵栈。

  3. 注视方向四元数构造libobs/graphics/quat.cquat_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_axisangmatrix*_from_axisang 进入 libobs 的标准旋转管线;而高频插值与组合运算则直接使用 struct quat(其头部注释明确说明四元数用于避免万向锁的旋转插值)。

7. 延伸阅读路径

登录后查看全文
热门项目推荐
相关项目推荐