首页
/ OBS Studio libobs 图形数学扩展:角度换算、平滑扭矩与随机浮点的 math-extra 模块实战解析

OBS Studio libobs 图形数学扩展:角度换算、平滑扭矩与随机浮点的 math-extra 模块实战解析

2026-09-05 18:16:47作者:戚魁泉Nursing

本篇围绕 OBS Studio 仓库中的图形数学扩展接口 math-extra.h 展开,对应 Sphinx 参考文档 reference-libobs-graphics-math.rst。你将学到 RAD/DEG 角度换算宏与各级 epsilon 常量的精确定义、rand_float 随机浮点生成函数,以及源码中比文档更完整的极坐标/笛卡尔换算、扭矩平滑(torque)函数族——并了解它们在场景切换过渡动画(transition)等真实场景中的调用方式。

模块定位:为什么需要 math-extra

OBS 的渲染核心 libobs 内置了一套自研的图形数学库,向量、矩阵、四元数等分别位于 vec2.cvec3.cmatrix4.cquat.c 等文件中。而一些“无法归入某一具体类型”的通用数学函数被集中放在了 math-extra 模块中。math-extra.h 头部注释明确说明了这一设计意图:

/*
 * A few general math functions that I couldn't really decide where to put.
 *
 *   Polar/Cart conversion, torque functions (for smooth movement), percentage,
 * random floats.
 */

即:极坐标/笛卡尔换算、用于平滑运动的扭矩函数、百分比计算、随机浮点。使用方式按 Sphinx 文档 reference-libobs-graphics-math.rst 所述,直接包含头文件即可:

#include <graphics/math-extra.h>

需要区分的是两个相关文件:

  • math-defs.h:定义角度换算宏、epsilon 常量族与浮点比较辅助函数,是纯头文件;
  • math-extra.h / math-extra.c:声明并实现可执行函数,math-extra.c 内部 #include "math-defs.h" 依赖前者。

角度换算宏 RAD 与 DEG

参考文档列出的两个核心宏定义在 math-defs.h

定义 语义
RAD(val) (val) * 0.0174532925199432957692369076848f 将浮点数角度值转换为弧度
DEG(val) (val) * 57.295779513082320876798154814105f 将浮点数弧度值转换为角度

两个换算系数分别是 π/180180/π 的高精度十进制展开。注意宏参数带括号包裹,可以安全地传入复杂表达式,例如 RAD(-90.0f + angle_offset)。此外,math-defs.h 还为缺少 M_PI 定义的 C 标准库环境补充了 M_PI 常量:

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795f
#endif

配合 RAD/DEG,编写 OBS 图形代码(例如设置旋转矩阵、极坐标转换)时可以直接以角度为直观单位参与计算,再统一换算为弧度喂给 sinf/cosf 等三角函数。

epsilon 常量族与 M_INFINITE

文档列出的四个常量同样来自 math-defs.h,完整取值如下:

常量 取值 含义
LARGE_EPSILON 1e-2f 较大的误差容限,用于距离接近性判断
EPSILON 1e-4f 常规浮点相等比较容限
TINY_EPSILON 1e-5f 更严格的微小容限
M_INFINITE 3.4e38f 近似 float 最大值的“无穷大”哨兵值

这些容限并不是摆设——同一头文件里还定义了浮点近似比较的内联函数,它依赖上述精度参数工作:

static inline bool close_float(float f1, float f2, float precision)
{
	return fabsf(f1 - f2) <= precision;
}

后续 calc_torquefcart_to_polar 等函数内部都通过 close_float + EPSILON 判断两个值是否“足够接近”,避免直接对 float 做 == 比较带来的经典浮点陷阱。

极坐标与笛卡尔坐标换算

math-extra.h 声明了四组坐标/法线转换函数,实现在 math-extra.c

struct vec2;
struct vec3;

EXPORT void polar_to_cart(struct vec3 *dst, const struct vec3 *v);
EXPORT void cart_to_polar(struct vec3 *dst, const struct vec3 *v);
EXPORT void norm_to_polar(struct vec2 *dst, const struct vec3 *norm);
EXPORT void polar_to_norm(struct vec3 *dst, const struct vec2 *polar);

polar_to_cart / cart_to_polar

struct vec3(见 vec3.h)承载:极坐标输入约定 x 为俯仰角、y 为方位角、z 为长度;输出为标准笛卡尔坐标。核心实现:

void polar_to_cart(struct vec3 *dst, const struct vec3 *v)
{
	struct vec3 cart;
	float sinx = cosf(v->x);
	float sinx_z = v->z * sinx;

	cart.x = sinx_z * sinf(v->y);
	cart.z = sinx_z * cosf(v->y);
	cart.y = v->z * sinf(v->x);

	vec3_copy(dst, &cart);
}

逆过程 cart_to_polar 先求长度 vec3_len 作为 z,再用 asinfatan2f 反解两个角度;当长度接近 0 时通过 close_float(polar.z, 0.0f, EPSILON) 判定并直接归零,避免 asin(y/r) 在 r 趋于 0 时产生数值噪声:

void cart_to_polar(struct vec3 *dst, const struct vec3 *v)
{
	struct vec3 polar;
	polar.z = vec3_len(v);

	if (close_float(polar.z, 0.0f, EPSILON)) {
		vec3_zero(&polar);
	} else {
		polar.x = asinf(v->y / polar.z);
		polar.y = atan2f(v->x, v->z);
	}

	vec3_copy(dst, &polar);
}

norm_to_polar / polar_to_norm

这一组处理单位法线向量与二维极坐标(方位角 + 俯仰角)之间的转换,实现更简洁,因为法线长度已被假设为 1:

void norm_to_polar(struct vec2 *dst, const struct vec3 *norm)
{
	dst->x = atan2f(norm->x, norm->z);
	dst->y = asinf(norm->y);
}

void polar_to_norm(struct vec3 *dst, const struct vec2 *polar)
{
	float sinx = sinf(polar->x);

	dst->x = sinx * cosf(polar->y);
	dst->y = sinx * sinf(polar->y);
	dst->z = cosf(polar->x);
}

从源码结构看,这类接口服务于光源方向、观察方向等需要以角度参数驱动的场景,是 OBS 效果着色器(effect)背后 C 层参数准备的常用工具。

扭矩函数族:实现平滑运动的 calc_torque

“torque functions (for smooth movement)” 是该模块的特色内容。它实现的是基于目标距离的缓动逼近:当前值每帧朝目标值移动一段“扭矩距离”,且速度随剩余距离缩放,同时用最小调整量防止末尾过慢、用钳制防止过冲。

标量版 calc_torquef

EXPORT float calc_torquef(float val1, float val2, float torque, float min_adjust, float t);

参数含义:val1 当前值、val2 目标值、torque 扭矩系数、min_adjust 最小调整量(防止过慢)、t 本帧时间步长。math-extra.c 中的实现逻辑清晰:

float calc_torquef(float val1, float val2, float torque, float min_adjust, float t)
{
	float out = val1;
	float dist;
	bool over;

	if (close_float(val1, val2, EPSILON))
		return val2;

	dist = (val2 - val1) * torque;
	over = dist > 0.0f;

	if (over) {
		if (dist < min_adjust) /* prevents from going too slow */
			dist = min_adjust;
		out += dist * t; /* add torque */
		if (out > val2)  /* clamp if overshoot */
			out = val2;
	} else {
		if (dist > -min_adjust)
			dist = -min_adjust;
		out += dist * t;
		if (out < val2)
			out = val2;
	}

	return out;
}

可以看到它正是文档中 EPSILON 容限的典型应用:起点即终点时直接返回目标值,避免无意义的逐帧抖动。

向量版 calc_torque

EXPORT void calc_torque(struct vec3 *dst, const struct vec3 *v1, const struct vec3 *v2,
                         float torque, float min_adjust, float t);

math-extra.c 中先计算两点的方向向量 dir 与原始距离 orig_dist,把 orig_dist * torque 作为本帧应移动的距离(距离越远移动越快),再用 LARGE_EPSILON 判断本帧位移是否已覆盖全部剩余距离,若是则直接钳制到目标点 v2。这就是文档所列 LARGE_EPSILON 的典型用途场景。

真实调用点:场景切换的 MANUAL 过渡

calc_torquef 在 OBS 中有一个直接的生产级用例——场景切换(transition)的手动控制模式。obs-source-transition.c 中每帧 tick 的逻辑为:

if (transition->transition_mode == OBS_TRANSITION_MODE_MANUAL) {
	if (transition->transition_manual_torque == 0.0f) {
		transition->transition_manual_val = transition->transition_manual_target;
	} else {
		transition->transition_manual_val = calc_torquef(transition->transition_manual_val,
							 transition->transition_manual_target,
							 transition->transition_manual_torque,
							 transition->transition_manual_clamp, t);
	}
}

即当切换源处于手动模式(OBS_TRANSITION_MODE_MANUAL)时,切换进度值 transition_manual_val 不会瞬间跳变到目标值,而是按 torque(扭矩系数)与 clamp(最小调整量)平滑逼近——这正是“torque functions (for smooth movement)” 注释所指的功能。

百分比计算:get_percentage 内联函数

头文件内还内联提供了两个百分比计算函数,用于求 mid 在区间 [start, end] 中的相对位置(归一化到 0..1):

static inline float get_percentage(float start, float end, float mid)
{
	return (mid - start) / (end - start);
}

static inline float get_percentagei(int start, int end, int mid)
{
	return (float)(mid - start) / (float)(end - start);
}

整数版先做整数减法再统一转 float,保证区间计算精度。这两个函数被 vec2.cvec3.c 等向量实现内部使用,属于向量/边界插值计算的基础工具。

随机浮点:rand_float

参考文档明确收录的函数是 rand_float

.. function:: float rand_float(int positive_only)

   Generates a random floating point value (from -1.0f..1.0f, or
   0.0f..1.0f if *positive_only* is set).

即生成随机浮点数:positive_only 为真时返回 0.0f..1.0f,否则返回 -1.0f..1.0fmath-extra.c 的实现基于 rand()/RAND_MAX 并做了 double 中间换算,以避免整数除法截断:

float rand_float(int positive_only)
{
	if (positive_only)
		return (float)((double)rand() / (double)RAND_MAX);
	else
		return (float)(((double)rand() / (double)RAND_MAX * 2.0) - 1.0);
}

仓库中可查到的一个使用示例是自动配置向导的测试场景 TestMode.hpp

vec4_set(&r, rand_float(true) * 100.0f, rand_float(true) * 100.0f,
	     rand_float(true) * 50000.0f + 10000.0f, 0.0f);

它利用 rand_float(true) 的 0..1 输出构造测试用随机向量。

构建与引用路径

libobs/CMakeLists.txt 可以看到 graphics/math-extra.h 被同时列入 libobs 的源文件与公共头文件安装列表,因此下游模块(包括 OBS 前端 Qt 工程)可以按 #include <graphics/math-extra.h> 的方式引用——这也是参考文档中示例代码的写法。仓库内实际引用方包括:

小结

math-extra 模块虽小,却是 libobs 图形数学体系中“胶水层”的角色:

  1. math-defs.h 提供 RAD/DEG 角度换算宏、LARGE_EPSILON/EPSILON/TINY_EPSILON/M_INFINITE 常量族及 close_float 比较工具;
  2. math-extra.h 提供极坐标/笛卡尔/法线三组坐标转换、标量与向量两级 calc_torque 平滑函数、百分比内联函数与 rand_float 随机浮点生成;
  3. 这些工具在场景切换过渡(obs_transition_tick)、自动配置测试等真实代码路径中被直接调用,是理解 OBS 底层动画与图形参数处理的入手点之一。
登录后查看全文
热门项目推荐
相关项目推荐