首页
/ OBS Studio 图形数学库详解:vec4 四维向量 API 全解析(libobs graphics)

OBS Studio 图形数学库详解:vec4 四维向量 API 全解析(libobs graphics)

2026-09-05 21:09:55作者:卓炯娓

libobs 的图形子系统为 OBS Studio 的插件、效果(effect)与渲染管线提供一套跨平台的 C 语言数学库。本篇以 docs/sphinx/reference-libobs-graphics-vec4.rst 这份 4 分量向量(vec4)API 参考文档为主体,逐节覆盖 struct vec4 的每个成员与全部 25 个 API 函数的语义,并结合 libobs/graphics/vec4.hlibobs/graphics/vec4.c 的源码实现,讲清其基于 SSE 的 SIMD 优化方式、色彩转换辅助函数,以及 vec4 在 libobs 图形渲染管线中的位置。读完本文,你将能够直接使用 graphics/vec4.h 编写插件或 effect 参数处理逻辑,并理解每个函数在源码层面的实际行为。

一、头文件与 struct vec4 结构

按参考文档的约定,API 通过如下方式引入:

#include <graphics/vec4.h>

struct vec4 是一个四分量浮点向量结构。原文档给出的成员有 xyzw 四个分量以及一个联合数组 ptr[4]。实际源码(vec4.h)中该结构比文档描述多一个成员——__m128 m

struct vec4 {
	union {
		struct {
			float x, y, z, w;
		};
		float ptr[4];
		__m128 m;      /* 文档未列出的 SIMD 视图 */
	};
};

三个视图通过 C 的 union 重叠在同一块 16 字节内存上:

  • x/y/z/w:文档列出的按名访问方式;
  • ptr[4]:文档列出的“所有分量的联合数组”,支持按下标批量访问(例如 memcpy、传给按 float* 接收的 API);
  • m:把整个向量当作一个 __m128(128 位 SSE 寄存器类型),使得任何算术运算都可以一条指令完成 4 个分量的并行计算。

这正是 vec4 实现高性能的根本原因:下文几乎所有函数都直接对 v->m 执行一条 SIMD 指令。

关于 m 的可移植性值得注意:sse-intrin.h 中,Windows 上 x64/x86(MSVC 与 MinGW)直接包含原生 <emmintrin.h>;其他平台(Linux、macOS、ARM 等)则通过 SIMDe 库的 simde/x86/sse2.h 提供 SSE 内建函数的软件模拟或平台等价映射。因此在任何受支持的平台上 vec4_* 函数语义一致,只是性能特征不同。此外 vec4.hextern "C" 包裹声明,保证 C/C++ 混合编译时符号一致;它同时依赖 math-defs.h 提供的 M_PIRAD/DEG 与各级 EPSILON 常量。

二、构造与基础操作

零化与赋值

函数 签名 语义 源码实现要点
vec4_zero void vec4_zero(struct vec4 *dst) 将向量置零 v->m = _mm_setzero_ps(),单条指令清零四个分量
vec4_set void vec4_set(struct vec4 *dst, float x, float y, float z, float w) 按分量设置 dst->m = _mm_set_ps(w, z, y, x)

两个细节:

  • vec4_zero 的参数文档写作 dst: Destination,源码中形参名是 v,语义相同——原地清零,无返回值;
  • vec4_set 在 SSE 层调用的 _mm_set_ps 参数顺序是从高位到低位w, z, y, x),这是 SSE 指令的固有约定,API 使用者只需按数学习惯传 x, y, z, w 即可,封装已经处理了顺序问题。

复制与从 vec3 构造

函数 签名 语义
vec4_copy void vec4_copy(struct vec4 *dst, const struct vec4 *v) 复制向量
vec4_from_vec3 void vec4_from_vec3(struct vec4 *dst, const struct vec3 *v) 由 3 分量向量构造 4 分量向量

vec4_copydst->m = v->m 的一次 128 位拷贝。vec4_from_vec3 是唯一在 vec4.c 中实现的非内联函数之一:

void vec4_from_vec3(struct vec4 *dst, const struct vec3 *v)
{
	dst->m = v->m;
	dst->w = 1.0f;
}

它把 vec3 的三个分量直接搬入,并将 w 强制置为 1.0f——即生成齐次坐标(homogeneous coordinate)中的普通空间点,而非方向向量。这一默认行为在变换管线(配合 w 做透视除法)中很常见,使用时若需要表示“方向”,应显式调用 vec4_setw 设为 0。

逐分量四则运算

文档中的四个二元运算函数:

  • vec4_add(dst, v1, v2)dst = v1 + v2
  • vec4_sub(dst, v1, v2)dst = v1 - v2
  • vec4_mul(dst, v1, v2)逐分量相乘(Hadamard 积),不是点积
  • vec4_div(dst, v1, v2)v1 为被除数,v2 为除数,逐分量相除

对应源码分别是一条 _mm_add_ps / _mm_sub_ps / _mm_mul_ps / _mm_div_ps(见 vec4.h)。需要警惕的两个坑:

  1. 全部是非 in-place 语义(三参数:目标 + 两个源),若写 vec4_add(&a, &a, &b) 这种 dst 与源重合的调用,语义上等价于 a = a + b,SSE 指令对同一寄存器读写是安全的;
  2. vec4_div 不处理除零——某分量除数为 0 时得到 Inf/NaN,是调用者的责任。

标量广播运算(*f 系列)

文档中 vec4_addf / vec4_subf / vec4_mulf / vec4_divf 把一个 float 与向量所有分量运算:

static inline void vec4_addf(struct vec4 *dst, const struct vec4 *v, float f)
{
	dst->m = _mm_add_ps(v->m, _mm_set1_ps(f));
}

实现上使用 _mm_set1_ps(f) 把标量广播到四个通道,再做向量运算(vec4.h)。典型用途:给颜色 vec4 整体提亮(vec4_addf)、按系数缩放 RGB 并保留 alpha(缩放时注意 w 也会一起缩放,需要单独处理)。原文档中 vec4_addfvec4_mulf 的参数说明笔误地把向量参数标成了 dst,实际第二个参数是源向量 v,以源码为准。

三、点积、长度与距离

这三个函数把 vec4 当作 4 维欧氏空间中的向量处理:

函数 签名 说明
vec4_dot float vec4_dot(const struct vec4 *v1, const struct vec4 *v2) 点积,返回标量
vec4_len float vec4_len(const struct vec4 *v) 向量长度(模)
vec4_dist float vec4_dist(const struct vec4 *v1, const struct vec4 *v2) 两向量 4 维空间距离

vec4_dot 的实现(vec4.h)展示了经典 SSE 归约技巧:

static inline float vec4_dot(const struct vec4 *v1, const struct vec4 *v2)
{
	struct vec4 add;
	__m128 mul = _mm_mul_ps(v1->m, v2->m);
	add.m = _mm_add_ps(_mm_movehl_ps(mul, mul), mul);
	add.m = _mm_add_ps(_mm_shuffle_ps(add.m, add.m, 0x55), add.m);
	return add.x;
}

先逐分量相乘,再经 movehl + shuffle(0x55) 两次横向加,最终标量结果落在 x 分量读出。由此:

  • vec4_len(v) 计算 vec4_dot(v, v) 后开方,且做了保护:dot_val > 0.0f ? sqrtf(dot_val) : 0.0f,避免对(理论上不该出现的)负值开方;
  • vec4_dist(v1, v2)vec4_sub 再对差向量自点积开方,逻辑与 vec4_len(vec4_sub(v1, v2)) 等价。

实际使用建议:几何上绝大多数“向量”场景用 vec3 即可;vec4_len/dist/dot 主要用于把颜色、齐次坐标等 4 分量数据当作普通数值向量做距离或内积比较时。

四、归一化与取反

  • vec4_neg(dst, v):逐分量取负。文档归在基础运算区,实现为逐分量 -v->x 等四条标量赋值(vec4.h);
  • vec4_norm(dst, v):归一化,使结果长度(若 w 参与)为 1。实现上直接 v / |v| 的一次 SIMD 乘法:
static inline void vec4_norm(struct vec4 *dst, const struct vec4 *v)
{
	float dot_val = vec4_dot(v, v);
	dst->m = (dot_val > 0.0f) ? _mm_mul_ps(v->m, _mm_set1_ps(1.0f / sqrtf(dot_val))) : _mm_setzero_ps();
}

零向量保护:当 |v|² <= 0(即零向量)时返回全零而不是 NaN,这是与“裸写 v/len”的重要区别——归一化零向量安全地得到零向量。

五、分量级极值、绝对值与取整

文档中 6 个“逐分量”函数,全部对应一条 SSE 指令或标量逐分量循环:

函数 语义 实现
vec4_minf(dst, v, val) 每分量取 min(v[i], val) _mm_min_ps(v->m, _mm_set1_ps(val))
vec4_min(dst, v, min_v) 逐分量取两向量较小值 _mm_min_ps(v1->m, v2->m)
vec4_maxf(dst, v, val) 每分量取 max(v[i], val) _mm_max_ps(v->m, _mm_set1_ps(val))
vec4_max(dst, v, max_v) 逐分量取两向量较大值 _mm_max_ps(v1->m, v2->m)
vec4_abs(dst, v) 逐分量绝对值 标量 fabsf 四次
vec4_floor(dst, v) 逐分量向下取整 标量 floorf 四次
vec4_ceil(dst, v) 逐分量向上取整 标量 ceilf 四次

(见 vec4.h。)

典型组合:vec4_maxf(&c, &c, 0.0f) 可把负颜色分量钳到 0,vec4_minf(&c, &c, 1.0f) 把分量钳到 1,两者连用即是对 RGB 分量做 [0,1] clamp(若需保留 alpha 不钳制,需先备份 w 或用 vec4_set 恢复)。

六、向量比较:vec4_close

int vec4_close(const struct vec4 *v1, const struct vec4 *v2, float epsilon)

文档语义:以 epsilon 作为“最大比较精度”比较两个向量。源码实现(vec4.h):

static inline int vec4_close(const struct vec4 *v1, const struct vec4 *v2, float epsilon)
{
	struct vec4 test;
	vec4_sub(&test, v1, v2);
	return test.x < epsilon && test.y < epsilon && test.z < epsilon && test.w < epsilon;
}

两个必须注意的实现细节:

  1. 返回 int(非 bool),非零即“相等”,符合文档中 int 返回类型;
  2. 比较的是 差分量 < epsilon 而非绝对值差 |v1-v2| < epsilon——当某个分量 v1[i] > v2[i] + epsilon 且差值为负时仍会判为“close”。实践中应传入非负 epsilon 且对顺序敏感场景自行取绝对差(例如先 vec4_subvec4_abs 后逐分量判断)。epsilon 的选取可参考 math-defs.h 中定义的 LARGE_EPSILON 1e-2fEPSILON 1e-4fTINY_EPSILON 1e-5f

七、矩阵变换:vec4_transform

void vec4_transform(struct vec4 *dst, const struct vec4 *v, const struct matrix4 *m)

文档一句话概括“Transfoms a vector”,源码(vec4.c)给出了完整实现:

void vec4_transform(struct vec4 *dst, const struct vec4 *v, const struct matrix4 *m)
{
	struct vec4 temp;
	struct matrix4 transpose;

	matrix4_transpose(&transpose, m);

	temp.x = vec4_dot(&transpose.x, v);
	temp.y = vec4_dot(&transpose.y, v);
	temp.z = vec4_dot(&transpose.z, v);
	temp.w = vec4_dot(&transpose.t, v);

	vec4_copy(dst, &temp);
}

实现要点:

  • 先对 struct matrix4(其行 x/y/z/t 本身就是 vec4)做转置,再用 4 次 vec4_dot 分别算出结果四分量,等价于 dst = M * v 的向量-矩阵乘法;
  • 这里没有w == 0(方向)或 w == 1(点)做透视除法——齐次除法(除以 w)由渲染管线下游负责,本函数只做线性变换部分;
  • dst 允许与 v 重叠(先算临时向量再复制)。

配合 graphics.rst 中描述的 effect/着色器体系,vec4_transform 常用于 CPU 侧预计算世界/视口坐标,再把结果作为 float4 uniform 传给 effect。

八、源码中的扩展:颜色转换辅助函数

参考文档未收录、但同为 vec4.h 公共 API 的一组颜色转换内联函数,是把 vec4 当 RGBA 颜色容器使用时的高频工具(vec4.h):

函数 作用
vec4_to_rgba(const struct vec4 *src) 向量 → uint32_t RGBA,经 gs_float4_to_u8x4 做浮点→8 位量化
vec4_to_bgra(const struct vec4 *src) 同上但交换 R/B 通道,输出 BGRA 布局
vec4_from_rgba(struct vec4 *dst, uint32_t rgba) uint32_t RGBA → 向量(0..255 量化值线性映射到 0..1 浮点)
vec4_from_bgra(struct vec4 *dst, uint32_t bgra) BGRA → 向量,内部完成通道交换
vec4_from_rgba_srgb(struct vec4 *dst, uint32_t rgba) 转换后额外调用 gs_float3_srgb_nonlinear_to_linear,把 sRGB 非线性值转为线性值供渲染管线使用

实现细节:量化通过 memcpy + srgb.h 中定义的 gs_float4_to_u8x4 / gs_u8x4_to_float4 完成,不依赖平台字节序的假设写法是“先 memcpy 成 uint8_t[4],必要时交换 u[0]u[2] 再 memcpy 回 uint32_t”。写 effect 或插件时,若设置/读取的是 32 位颜色值,优先使用这组函数而不是手写位移,可保证与 libobs 内部量化行为一致。

九、API 速查表

以下为参考文档收录的全部 25 个函数(结构成员 5 个已见第一节),均位于 graphics/vec4.h

分类 函数 返回值
构造 vec4_zerovec4_setvec4_copyvec4_from_vec3 void
向量算术 vec4_addvec4_subvec4_mulvec4_divvec4_neg void
标量算术 vec4_addfvec4_subfvec4_mulfvec4_divf void
度量 vec4_dotvec4_lenvec4_distvec4_norm float / void
分量级 vec4_minfvec4_minvec4_maxfvec4_maxvec4_absvec4_floorvec4_ceil void
比较 vec4_close int
变换 vec4_transform void

十、实践要点小结

  1. 性能模型vec4 的所有算术函数都是“一次函数调用 = 一条 128 位 SIMD 指令”,在热路径中批量处理大量 vec4(如颜色 LUT、逐像素参数表)时收益明显;SIMD 支持由 sse-intrin.h 统一抽象,Windows 走原生 SSE2,其余平台经 SIMDe 等价实现,API 层无需分支;
  2. 安全边界vec4_normvec4_lenvec4_dist 对零向量做了保护(返回零而非 NaN),而 vec4_div/vec4_divf 不做除零检查,使用前需自行保证除数非零;
  3. 与 vec3 的衔接vec4_from_vec3 固定 w = 1;需要方向向量(w = 0)时请显式 vec4_set
  4. 文档与源码的差异点:参考文档中 vec4_addf/vec4_mulf 的参数标签笔误、struct vec4 描述成 “Two component vector structure”(应为 Four component)均为上游文档笔误,实现与本文给出的签名一致;文档亦未覆盖 __m128 m 联合成员与颜色转换函数族,使用时以 vec4.h 为准;
  5. 适用场景:vec4 是 OBS Studio 插件(plugins/ 下各 effect/C 插件)、libobs-data 颜色参数处理以及 CPU 侧几何预计算中的标准 4 分量载体,与 vec3matrix4(同目录 vec3.hmatrix4.h)共同构成 libobs 图形数学基础。
登录后查看全文
热门项目推荐
相关项目推荐