
OBS Studio libobs Effects 特效 API 详解着色器、Technique 与 Pass 的加载、执行与参数设置机制【免费下载链接】obs-studioOBS Studio - Free and open source software for live streaming and screen recording项目地址: https://gitcode.com/GitHub_Trending/ob/obs-studiolibobs 的 Effects特效子系统是 OBS Studio 图形层将 HLSL 着色器文本与 C 运行时参数管理绑定在一起的核心机制它让开发者可以把顶点着色器、像素着色器、共享函数与 uniform 参数写在同一个.effect文件中再经由gs_effect_*系列 API 在运行时加载 Technique技术、驱动 Pass通道执行并注入参数。本文基于仓库中的 Sphinx API 参考文档 Effects (Shaders) 及其对应的源码实现 effect.c、effect-parser.c完整讲解 Effect/Technique/Pass/Param 四类对象的生命周期、内置.effect文件的真实结构以及参数上传与缓存的内部原理帮助你在编写 OBS 插件或滤镜时正确调用这套 API。一、Effect 是什么一个文件内的着色器集合API 文档给出的定义是Effects are a single collection of related shaders. Theyre used for easily writing vertex and pixel shaders together all in the same file in HLSL format.即一个 Effect 是一组相关着色器的集合目的是让你可以在同一个文件里用 HLSL 格式一起编写顶点着色器和像素着色器。源码 effect.h 中的注释进一步解释了它的动机/* * Effects introduce a means of bundling together shader text into one * file with shared functions and parameters. This is done because often * shaders must be duplicated when you need to alter minor aspects of the code * that cannot be done via constants. Effects allow developers to easily * switch shaders and set constants that can be used between shaders. * * Effects are built via the effect parser, and shaders are automatically * generated for each techniques pass. */翻译过来就是很多场景下只是要改变着色器代码的一小部分就必须整段复制着色器Effect 机制允许开发者在同一个文件里声明共享的函数、结构体、uniform 参数和采样器状态然后定义多个 Technique每个 Technique 的一个或多个 Pass 各自引用不同的顶点/像素着色函数。解析器effect parser在加载时会自动为每个 Pass 生成独立的着色器文本并自动把该 Pass 依赖的结构体、函数、参数、采样器一并写入。这套机制对外暴露的核心类型均需#include graphics/graphics.h类型C 类型含义Effect 对象gs_effect_tstruct gs_effect一个 effect 文件对应的整体对象包含全部参数与 TechniqueTechnique 对象gs_technique_tstruct gs_effect_technique一组 Pass 的集合对应一次完整的绘制策略Effect 参数对象gs_eparam_tstruct gs_effect_param一个 uniform 参数含 annotation 注解参数从源码结构看三者的层级关系是gs_effect持有params与techniques两个动态数组gs_effect_technique持有passes数组每个gs_effect_pass持有编译好的vertshader/pixelshader以及两个参数映射表vertshader_params/pixelshader_params见 effect.hstruct gs_effect_param { char *name; enum effect_section section; enum gs_shader_param_type type; bool changed; DARRAY(uint8_t) cur_val; // 当前值字节缓冲 DARRAY(uint8_t) default_val; // 默认值字节缓冲 gs_effect_t *effect; gs_samplerstate_t *next_sampler; gs_effect_param_array_t annotations; }; struct gs_effect { bool processing; bool cached; char *effect_path, *effect_dir; gs_effect_param_array_t params; DARRAY(struct gs_effect_technique) techniques; struct gs_effect_technique *cur_technique; struct gs_effect_pass *cur_pass; gs_eparam_t *view_proj, *world, *scale; graphics_t *graphics; struct gs_effect *next; // 线程级缓存链表 size_t loop_pass; bool looping; };可以看到参数值是以原始字节DARRAY(uint8_t)形式缓存的——这解释了后文为什么gs_effect_get_val返回的是“当前值的字节拷贝”以及为什么gs_effect_set_*系列函数内部统一走“memcpy 到 cur_val 标记 changed”的路径。二、真实.effect文件结构以 libobs 内置文件为例理解 API 之前先看仓库中实际使用的 effect 文件。libobs 内置的 21 个.effect文件位于 libobs/data/包括default.effect、opaque.effect、solid.effect、bicubic_scale.effect、lanczos_scale.effect、format_conversion.effect、deinterlace_*.effect等分别服务于场景渲染、不透明源绘制、纹理缩放、像素格式转换与隔行扫描消隐。以 default.effect 为例它展示了一个典型 effect 文件的完整语法#include color.effect // 共享函数库sRGB/HDR 转换 uniform float4x4 ViewProj; // 参数视图投影矩阵 uniform texture2d image; // 参数输入纹理 uniform float multiplier; // 参数音量/不透明度乘子 sampler_state def_sampler { // 采样器状态声明 Filter Linear; AddressU Clamp; AddressV Clamp; }; struct VertInOut { // 共享的顶点输入输出结构体 float4 pos : POSITION; float2 uv : TEXCOORD0; }; VertInOut VSDefault(VertInOut vert_in) // 顶点着色函数 { VertInOut vert_out; vert_out.pos mul(float4(vert_in.pos.xyz, 1.0), ViewProj); vert_out.uv vert_in.uv; return vert_out; } float4 PSDrawBare(VertInOut vert_in) : TARGET // 像素着色函数 { return image.Sample(def_sampler, vert_in.uv); } technique Draw // Technique单 Pass { pass { vertex_shader VSDefault(vert_in); pixel_shader PSDrawBare(vert_in); } } technique DrawMultiply // 另一组像素逻辑复用同一个 VS { pass { vertex_shader VSDefault(vert_in); pixel_shader PSDrawMultiply(vert_in); } }其中值得注意的要点uniform声明即参数入口ViewProj、image、multiplier会成为该 effect 的gs_eparam_t参数运行时通过gs_effect_get_param_by_name拿到后设置。Technique 与 Pass 的对应文件里定义了Draw、DrawAlphaDivide、DrawTonemap、DrawPQ、DrawD65P3等十余个 Technique每个 Technique 一个 PassPass 内部指定vertex_shader 函数(参数)与pixel_shader 函数(参数)。解析器会按 Pass 自动生成着色器。跨文件复用color.effect 本身不含任何 Technique只提供srgb_nonlinear_to_linear、rec709_to_rec2020、reinhardReinhard 色调映射、linear_to_st2084/st2084_to_linearPQ/HDR、HLG 等共享 HLSL 函数供default.effect、opaque.effect 等通过#include color.effect引入——这正是 effect 机制“共享函数与参数”的设计目标。解析逻辑由 effect-parser.c 完成其头部注释直接点明了工作方式“effect parser 接收一个 effect 文件为每个 technique 的每个 pass 转换成独立着色器它会自动把所有依赖的结构体/函数/参数写入着色器并为每个 pass 的每个着色器组件构建着色器文本”。三、创建与销毁注意缓存语义API 提供两个创建入口gs_effect_t *gs_effect_create_from_file(const char *file, char **error_string); gs_effect_t *gs_effect_create(const char *effect_string, const char *filename, char **error_string);参数说明fileeffect 文件路径effect_string直接传入 effect 的 HLSL 文本filenameeffect 字符串对应的虚拟文件名用于路径解析与缓存键error_string接收错误信息指针必须用bfree()释放传NULL则忽略该参数返回值成功返回 effect 对象失败返回NULL一个关键实现细节gs_effect_create_from_file会先查线程级缓存graphics.c#L825-L860同一文件路径第二次创建时直接返回缓存实例gs_effect_t *gs_effect_create_from_file(const char *file, char **error_string) { ... effect find_cached_effect(file); if (effect) return effect; file_string os_quick_read_utf8_file(file); ... effect gs_effect_create(file_string, file, error_string); ... }而在gs_effect_create中只要提供了effect_path新建的 effect 就会被挂入thread_graphics-first_effect缓存链表并置位cachedgraphics.c#L883-L893。与之配套销毁函数是这样实现的effect.c#L30-L36void gs_effect_destroy(gs_effect_t *effect) { if (effect) { if (!effect-cached) gs_effect_actually_destroy(effect); } }因此实践中必须记住凡是经过gs_effect_create_from_file或带filename的gs_effect_create创建的 effect 都是缓存实例调用gs_effect_destroy是空操作这类 effect 的生命周期由图形上下文管理。只有不携带文件名、完全临时的 effect 字符串才会被gs_effect_destroy真正释放。OBS 自身就是这样使用的视频系统初始化时通过obs_find_data_file定位内置文件逐个调用gs_effect_create_from_file加载default.effect、opaque.effect、solid.effect、bicubic_scale.effect、lanczos_scale.effect等十余个文件并挂到video-xxx_effect字段上供渲染管线全程使用obs.c#L505-L550且 OpenGL 后端会额外加载一份default_rect.effect。四、Technique 与 Pass 的执行流程执行一个 effect 的标准流程是取 technique →begin→ 逐 passbegin_pass/end_pass→endgs_technique_t *tech gs_effect_get_technique(effect, Draw); if (tech) { size_t num_passes gs_technique_begin(tech); // 返回该 technique 的 pass 数 for (size_t i 0; i num_passes; i) { if (gs_technique_begin_pass(tech, i)) { /* 此处进行绘制draw */ gs_technique_end_pass(tech); } } gs_technique_end(tech); }各函数职责与实现要点gs_effect_get_technique(effect, name)按名称线性查找 technique未找到返回NULLeffect.c#L38-L50。gs_effect_get_current_technique(effect)返回当前处于激活状态的 technique无则返回NULL。gs_technique_begin(tech)把该 technique 设为 effect 的cur_technique并绑定到图形上下文graphics-cur_effect返回 pass 数量effect.c#L101-L110。gs_technique_begin_pass(tech, idx)核心步骤。它加载该 pass 的顶点/像素着色器gs_load_vertexshader/gs_load_pixelshader然后upload_parameters(effect, false)全量上传该 pass 用到的所有 uniform 参数effect.c#L195-L212。pass 索引越界返回false。gs_technique_begin_pass_by_name(tech, name)按名称查找 pass 并转调begin_pass语义相同。gs_technique_end_pass(tech)结束当前 pass。实现上会清空该 pass 全部纹理参数clear_tex_params把所有GS_SHADER_PARAM_TEXTURE类型的着色器纹理置为NULLeffect.c#L230-L256避免下一帧绘制意外复用上一帧绑定的纹理。gs_technique_end(tech)结束 technique。调用前必须保证所有已开始的 pass 都已end_pass它会卸载着色器gs_load_vertexshader(NULL)等、清空cur_effect并把所有 effect 参数的cur_val重置为空、changed置falseeffect.c#L112-L135。参数上传还有一个增量优化upload_shader_params(..., changed_only)只上传changed true的参数pass 开始时的全量上传之后reset_params会把已上传参数的changed标志清零从而后续gs_effect_update_params只推送真正变化的值effect.c#L137-L193。gs_effect_loop官方推荐的简化写法对于单 Pass technique这是绝大多数情况文档给出的推荐用法是gs_effect_loop辅助函数for (gs_effect_loop(effect, my_technique)) { /* perform drawing here */ [...] }C 中对应的 while 形态在仓库各插件中随处可见例如 xshm-input.c、gpu-delay.c 的while (gs_effect_loop(effect, Draw))Lua 脚本 API 中同样是while obs.gs_effect_loop(effect, Draw) do ... endclock-source.lua。从 effect.c#L60-L99 的实现可以读到它的完整语义首次调用时它检查是否已有 effect 处于激活状态——gs_get_effect()非空则记录警告gs_effect_loop: An effect is already active并返回false找不到指定名称的 technique 时记录Technique xxx not found并返回false否则自动执行gs_technique_begin进入循环体返回true循环体再次执行完毕后的下一次调用先gs_technique_end_pass再尝试begin_pass(loop_pass)当 pass 用尽时自动gs_technique_end、复位循环状态并返回false结束 for 循环。也就是说gs_effect_loop把 technique 的 begin/end 和 pass 的 begin/end 全部包了进来返回true的每一次循环迭代内都可以直接绘制。五、参数访问与设置 API 全表参数gs_eparam_t是 effect 与着色器之间传递数据的唯一通道。以下按 API 文档逐组说明并结合 effect.c 的实现补充行为细节。5.1 参数查询函数说明size_t gs_effect_get_num_params(const gs_effect_t *effect)返回该 effect 的参数总数gs_eparam_t *gs_effect_get_param_by_idx(effect, size_t param)按下标取参数越界返回NULLgs_eparam_t *gs_effect_get_param_by_name(effect, const char *name)按名称取参数未找到返回NULLvoid gs_effect_get_param_info(const gs_eparam_t *param, struct gs_effect_param_info *info)查询参数的名称与类型参数类型与元信息结构在 API 文档中定义为enum gs_shader_param_type { GS_SHADER_PARAM_UNKNOWN, GS_SHADER_PARAM_BOOL, GS_SHADER_PARAM_FLOAT, GS_SHADER_PARAM_INT, GS_SHADER_PARAM_STRING, GS_SHADER_PARAM_VEC2, GS_SHADER_PARAM_VEC3, GS_SHADER_PARAM_VEC4, GS_SHADER_PARAM_INT2, GS_SHADER_PARAM_INT3, GS_SHADER_PARAM_INT4, GS_SHADER_PARAM_MATRIX4X4, GS_SHADER_PARAM_TEXTURE, }; struct gs_effect_param_info { const char *name; enum gs_shader_param_type type; };实现上gs_effect_get_param_info只填充name与type两个字段effect.c#L359-L366。Annotation注解参数effect 文件中的参数可以携带注解参数对应 HLSL 的[xxx(...)]属性语法API 提供函数说明size_t gs_param_get_num_annotations(const gs_eparam_t *param)注解数量gs_eparam_t *gs_param_get_annotation_by_idx(param, size_t annotation)按下标取注解参数对象越界返回NULLgs_eparam_t *gs_param_get_annotation_by_name(param, const char *annotation)按名称取注解参数对象未找到返回NULL实现见 effect.c#L292-L321每个gs_effect_param内部持有annotations动态数组注解本身也是gs_effect_param因此可以继续对它取值。5.2 设置参数所有 set 函数最终都汇聚到内部函数effect_setval_inlineeffect.c#L368-L391把新值 memcpy 进cur_val字节缓冲并仅在值或大小真正变化时置changed true——这就是“脏标记”配合第四节描述的增量上传未变化的参数不会反复推送到 GPU。函数设置的参数类型实现细节gs_effect_set_bool(gs_eparam_t *param, bool val)BOOL以sizeof(int)字节写入gs_effect_set_float(param, float val)FLOAT4 字节gs_effect_set_int(param, int val)INT4 字节gs_effect_set_matrix4(param, const struct matrix4 *val)MATRIX4X4sizeof(struct matrix4)gs_effect_set_vec2(param, const struct vec2 *val)VEC2sizeof(struct vec2)gs_effect_set_vec3(param, const struct vec3 *val)VEC3固定按float*3字节写入gs_effect_set_vec4(param, const struct vec4 *val)VEC4sizeof(struct vec4)gs_effect_set_color(param, uint32_t argb)便捷颜色函数参数为0xAARRGGBB形式的整数颜色值内部经vec4_from_bgra转成vec4后按 VEC4 写入gs_effect_set_texture(param, gs_texture_t *val)TEXTURE写入{tex, srgbfalse}gs_effect_set_texture_srgb(param, gs_texture_t *val)TEXTURE同上但srgbtrue即“优先使用 SRGB 视图采样”gs_effect_set_val(param, const void *val, size_t size)任意手动传原始数据指针与字节数适合自定义布局gs_effect_set_default(param)任意把参数恢复为 effect 文件中声明的默认值default_valgs_effect_set_next_sampler(param, gs_samplerstate_t *sampler)仅 TEXTURE为该纹理参数挂一个“下一次使用时的采样器”实现中只有param-type GS_SHADER_PARAM_TEXTURE才会生效effect.c#L547-L556纹理设置的实现effect.c#L473-L487表明纹理参数在cur_val中实际存的是一个gs_shader_texture结构{gs_texture_t *tex; bool srgb;}srgb标志决定 GPU 层是否切换到 SRGB 采样视图。gs_effect_set_next_sampler的“next”语义对应参数结构中的next_sampler字段它在下一次该参数被上传到着色器时生效upload_shader_params中先处理next_sampler再处理值effect.c#L146-L171且会被gs_technique_end清空不会跨 technique 残留。5.3 读取参数值函数返回值与约定void *gs_effect_get_val(gs_eparam_t *param)返回当前值的一份字节拷贝无当前值时返回NULL必须用bfree()释放void *gs_effect_get_default_val(gs_eparam_t *param)返回默认值的一份字节拷贝无默认值时返回NULL同样用bfree()释放size_t gs_effect_get_val_size(gs_eparam_t *param)当前值的字节大小size_t gs_effect_get_default_val_size(gs_eparam_t *param)默认值的字节大小实现见 effect.c#L494-L540gs_effect_get_val用bzalloc(size)分配内存后 memcpycur_val。这意味着你无法拿到参数内部的指针引用而是拿到一个可自由支配的副本读取前应先调用gs_effect_get_val_size确定字节长度。5.4 矩阵参数快捷入口函数说明gs_eparam_t *gs_effect_get_viewproj_matrix(const gs_effect_t *effect)返回 effect 中视图投影矩阵参数即viewproj的对象供直接gs_effect_set_matrix4gs_eparam_t *gs_effect_get_world_matrix(const gs_effect_t *effect)返回世界矩阵参数即world的对象从源码结构看struct gs_effect中预置了view_proj、world、scale三个gs_eparam_t *成员effect.h#L157解析阶段会为常用矩阵名建立快捷引用。OBS 渲染管线中场景坐标变换正是通过gs_effect_get_viewproj_matrix拿到该参数后每帧写入matrix4的。六、在 OBS 插件与脚本中的实际调用方式API 文档是 libobs 的 C 接口说明OBS 的代码库本身就是最权威的调用示例集合C 插件xshm-input.c 中创建 effect 后以while (gs_effect_loop(effect, Draw)) { gs_effect_set_texture(image, tex); /* ... 提交顶点与 draw ... */ }的模式渲染 X11 共享内存帧缓冲decklink-ui-main.cpp 的 DeckLink 输出预览、nvidia-videofx-filter.c 的 NVIDIA 滤镜、transition-fade-to-color.c 的转场都遵循同一套gs_effect_create_from_file→gs_effect_loop→ 设置参数 → 绘制的流程。Lua 脚本obs-scripting 层把同一批函数暴露为obs.gs_effect_*仓库自带的 clock-source.lua 展示了脚本内创建自定义 effect 并用while obs.gs_effect_loop(effect, Draw)绘制时钟数字的完整例子。libobs 自身如第五节所述obs.c初始化时加载的全部内置 effect 文件就是这套 API 在核心渲染管线中的使用样板。七、调用流程小结与易错点把前文串起来一个完整的最小调用序列是#include graphics/graphics.h char *error_string NULL; gs_effect_t *effect gs_effect_create_from_file(my.effect, error_string); if (!effect) { /* 处理 error_string记得 bfree */ } gs_eparam_t *image gs_effect_get_param_by_name(effect, image); gs_eparam_t *mult gs_effect_get_param_by_name(effect, multiplier); gs_eparam_t *vp gs_effect_get_viewproj_matrix(effect); for (gs_effect_loop(effect, Draw)) { gs_effect_set_texture(image, texture); gs_effect_set_float(mult, 1.0f); gs_effect_set_matrix4(vp, view_proj_matrix); /* 提交顶点缓冲并 gs_draw */ } /* 无需 gs_effect_destroy来自文件的 effect 为缓存实例销毁是空操作 */易错点汇总均有源码依据不要对文件创建的 effect 依赖gs_effect_destroy——缓存实例的销毁是 no-opeffect.c#L30-L36error_string、gs_effect_get_val/gs_effect_get_default_val的返回值都必须bfree()同一时刻只允许一个 effect 处于激活状态嵌套使用gs_effect_loop会触发警告并直接返回falseeffect.c#L69-L73pass 必须按begin_pass → 绘制 → end_pass成对出现gs_technique_end前所有 pass 须已结束end_pass会自动解绑纹理下一帧使用前记得重新gs_effect_set_texturegs_effect_set_color的入参是0xAARRGGBB整数内部经 BGRA 转vec4不要误传0xRRGGBBAA纹理参数想换采样方式时用gs_effect_set_next_sampler它只对GS_SHADER_PARAM_TEXTURE类型生效且仅在下次上传时生效。掌握以上内容后你就能按 API 参考文档 中列出的每一个gs_effect_*函数在 OBS 插件、滤镜或脚本中正确加载 effect 文件、驱动 technique/pass、设置和回读全部类型的 uniform 参数并理解参数脏标记、缓存实例与纹理自动解绑这些底层行为对调试渲染问题的意义。【免费下载链接】obs-studioOBS Studio - Free and open source software for live streaming and screen recording项目地址: https://gitcode.com/GitHub_Trending/ob/obs-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考