第 13 课:场景与合成
# 第 13 课:场景与合成
嘿,我是小方。 到这儿,你已经会"采集一帧"(第 10 课)、"把一个源画出来、还能加滤镜"(第 11、12 课)。可你打开任何一个直播,画面里从来不止一个源:游戏打底、右下角一个摄像头小窗、底下一行字幕、角落一个 logo…… 它们各自画好之后,是怎么按位置、大小、层级叠成最终那一幅画面的? 这就是"合成",打包它的东西叫"场景(scene)"。这一课我们拆开它,并且揭晓第 6 课埋的最后一个伏笔:场景,它自己居然也是个 source。这是模块 3 的收官。
# 13.1 背景:合成,就是把多个图层叠起来
📍接上一节:上一课我们会画一个源了,可直播画面从来不止一个源——这一节先建立"合成"的直觉:把多个图层摞起来。
先建立直觉。你最终看到的一帧,是好几个源"摞"在一起的结果:

合成(compositing) 这个词,在图形里就是指"把多个图层,按各自的位置、大小、透明度、上下层级,叠成最终一幅"。游戏画面铺满打底(最底层),摄像头缩小放右下角(上面一层),字幕压在下方,logo 盖在角上(最顶层)。
OBS 把"这一组源 + 每个源怎么摆"打包成一个东西,叫场景。你在 OBS 界面左下角切换的那些"场景 1""场景 2",就是它。一个场景,就是一套排布好的画面。
那"一个场景"在代码里长什么样?
# 13.2 数据模型:场景 = 一串"场景项"
📍我们在哪:有了合成的直觉,先看"一个场景"在代码里长什么样——它是一串"场景项"串成的链表。
一个场景(obs_scene)内部,是一串场景项(obs_scene_item) 串成的链表。每个场景项 = 一个源 + 它在画面里怎么摆:

翻开 struct obs_scene_item(obs-scene.h:30),它记着一个源,加上一堆"怎么摆"的信息:
// libobs/obs-scene.h:30 (节选)
struct obs_scene_item {
struct obs_scene *parent; // 属于哪个场景
struct obs_source *source; // 它包的那个源(游戏?摄像头?)
bool user_visible; // 界面上那个眼睛图标(用户点的显示/隐藏;下面的循环用的就是它)
struct vec2 pos; // 位置 (x, y) ┐
struct vec2 scale; // 缩放 │ 这三样就是「怎么摆」
float rot; // 旋转 ┘
struct obs_sceneitem_crop crop; // 裁剪
struct matrix4 draw_transform; // ★ 由上面几样算出来的「变换矩阵」(13.5 要用)
struct obs_scene_item *prev; // 链表:上一项 ┐
struct obs_scene_item *next; // 下一项 ┘ 顺序 = 层级!
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
抓两个重点:
source+pos/scale/rot—— 一个场景项,就是"哪个源"加上"它摆在哪、多大、转了没"。你在同一个摄像头源上建两个场景项,就能让它在两个场景里以不同大小出现。(这也印证了第 6 课:源是"蓝图/实例"里的实例,场景项引用的是实例。)prev / next(链表) —— 场景项串成一条链,这条链的顺序,就是图层的上下层级:排在前面的先画(在底层),后面的后画(盖在上面)。你在界面里拖动来源列表调顺序,改的就是这条链。
那 draw_transform 那个矩阵是干嘛的?先记住它,13.5 见分晓。现在先看一个更妙的东西。
# 13.3 大揭秘:场景,自己也是个 source
📍承上启下:数据模型看完,揭第 6 课埋的最后一个伏笔——场景本身也是一个 source,这决定了后面合成为什么这么简单。
第 6 课我列 source 四类型时,说过 OBS_SOURCE_TYPE_SCENE,还卖了个关子:"场景本身也是一个 source,留到第 13 课讲。"现在揭晓 —— 而且是实锤。
翻开 obs-scene.c,你会看到场景注册了一张 obs_source_info 登记表,和第 6 课任何一个源一模一样:

// libobs/obs-scene.c:1742
const struct obs_source_info scene_info = {
.id = "scene",
.type = OBS_SOURCE_TYPE_SCENE, // ← 四类型之一
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_COMPOSITE,
.get_name = scene_getname,
.video_render = scene_video_render, // ← 合成就发生在这个回调里
...
};
2
3
4
5
6
7
8
9
所以,"创建一个场景"本质就是"创建一个 id 为 "scene" 的 source":
// libobs/obs-scene.c:1794
obs_scene_t *obs_scene_create(const char *name)
{
return create_id(obs->data.main_canvas, "scene", name); // 就是第 6 课那条 obs_source_create!
}
2
3
4
5
obs_scene 和它底层的那个 obs_source,是两面一体的:obs_scene_get_source(scene) 拿到它的 source,obs_scene_from_source(source) 反过来拿回 scene。
这个设计有多妙?正因为场景是 source:
- 你能把"一个场景",当成源,放进另一个场景里 → 场景可以嵌套;
- 转场(第 6 课)就是在两个场景(两个 source)之间做过渡,不用为场景搞特殊机制;
- 渲染、属性面板、存档…… 引擎对待场景,和对待一个摄像头完全一样,零特殊处理。
一个"场景也是 source"的抽象,让"多源合成"这件复杂的事,几乎没给引擎添任何新负担。接下来看合成到底怎么画。
# 13.4 源码深读:合成,就是"逐项画一遍"
📍我们在哪:知道场景是 source 了,现在进它的
video_render,看"合成"这件事在现场到底是怎么一项项画出来的。
场景那个 video_render 回调 scene_video_render(obs-scene.c:1059),就是合成的现场。抽出核心:

// libobs/obs-scene.c:1059 (抽骨架)
static void scene_video_render(void *data, gs_effect_t *effect)
{
struct obs_scene *scene = data;
...
struct obs_scene_item *item = scene->first_item; // 从链表头开始
while (item) {
if (item->user_visible) // 跳过隐藏的项
render_item(item); // ★ 画这一项
item = item->next; // 下一项(更上面的层)
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
就是顺着链表,把每个可见的场景项挨个画一遍。而 render_item(obs-scene.c:897)画单独一项的核心,只有三行有意义的代码:
gs_matrix_push(); // 存一下当前坐标状态
gs_matrix_mul(&item->draw_transform); // ① 设好「这一项摆哪、多大」的矩阵
obs_source_video_render(item->source); // ② 画它包的那个源(第 11 课那套!)
gs_matrix_pop(); // 恢复
2
3
4
看第 ① 和 ② 行,一切就通了:
- ①
gs_matrix_mul(&item->draw_transform)—— 把这一项的变换矩阵(13.2 那个draw_transform)乘进当前坐标系。这就决定了"接下来画的东西,摆在画面的哪个位置、多大、转多少"。 - ②
obs_source_video_render(item->source)—— 调用这个项包的源的video_render。这不就是第 11、12 课那套嘛 —— 把源变成纹理、跑着色器画出来。摄像头也好、另一个场景也好、加了滤镜的源也好,都从这一句进去。
一句话:合成 = 顺着链表,给每个源"设好它的变换矩阵,然后调它的 video_render"。 场景本身不关心每个源具体怎么画(那是第 11、12 课的事),它只负责摆好位置、按顺序调用。而链表顺序决定谁在上谁在下 —— 先画的被后画的盖住,层级就这么来的。
💡 旁注 D:嵌套场景,是怎么"自动"就画对了的? 13.3 说场景能嵌套(场景 B 当成一个源放进场景 A)。它凭什么不用写任何特殊代码就能工作?看
render_item那句obs_source_video_render(item->source):如果这个item->source恰好是另一个场景,那这一句调进去的,就是那个场景的scene_video_render——于是它又会顺着它自己的链表逐项画一遍。这是一次递归:大场景画到"嵌套场景"这一项时,自然地钻进小场景,把小场景的每一项也画出来,画完再回到大场景继续下一项。整个过程没有一行"专门处理嵌套"的代码——因为场景是 source,obs_source_video_render一视同仁,递归就免费发生了。 这就是 13.3 说的"抽象的妙处"最实在的一次兑现。
# 13.5 你拖动一个源,改的到底是什么
📍承上启下:合成时那句
gs_matrix_mul用到的矩阵,是你鼠标拖出来的——这一节把"拖动一个源"和第 11 课的ViewProj接上。
现在来还 13.2 那个 draw_transform 的债,顺便把第 11~13 课合龙。
你在 OBS 里用鼠标拖动、缩放、旋转一个源时,背后是这么一条链:

- 你拖 / 缩 / 转 一个源;
- 界面调
obs_sceneitem_set_pos(...)之类,改这个场景项的pos/scale/rot; - 引擎把"位移 + 缩放 + 旋转"合成成一个 4×4 矩阵,存进
item->draw_transform; - 合成时(13.4),
gs_matrix_mul(&item->draw_transform)把这个矩阵设进当前坐标系; - 而这个矩阵,正是第 11 课着色器里那个
uniform float4x4 ViewProj—— 顶点着色器VSDefault里那句mul(v.pos, ViewProj),就是拿它把源的四个角摆到画面上正确的位置。
所以,"在 OBS 里拖一个源" = 改它那个 4×4 矩阵 = 改喂给顶点着色器的 ViewProj。 第 11 课学的"顶点着色器负责定位",到这一课终于知道那个定位矩阵从哪来、被谁改 —— 就是你的鼠标,经过场景项,变成了矩阵。
但上面第 3 步——"把位移+缩放+旋转合成成一个 4×4 矩阵"——听着还是有点像黑盒。别让它悬着,我们把它当场拆开。
# 13.6 拆开黑盒①:draw_transform 那个矩阵是怎么拼出来的
📍我们在哪:上一节说位移+缩放+旋转"合成成一个矩阵"还是个黑盒,这一节拆开它——五行代码怎么把
pos/scale/rot拼成那个 4×4。
# 先补一句:4×4 矩阵到底是个啥
如果你没系统学过线性代数,别慌,一句话够用了:
一个 4×4 矩阵,你就把它当成一个"打包好的变换盒子" —— 里面装着"缩放 + 旋转 + 平移"这一整套动作。一个点(顶点)只要"乘一下"这个盒子,这套动作就一次性、全套作用到它身上。
为什么非得用矩阵、而不是老老实实"先加个偏移、再乘个缩放"?两个实在的好处:
- 可组合:两个矩阵相乘 = 两套变换叠加。所以下面能"一步一步把动作乘进同一个盒子里"。
- GPU 原生支持:第 11 课那句着色器代码
mul(v.pos, ViewProj),mul就是"顶点乘矩阵",硬件一条指令就算完。
你不用会算矩阵乘法,只要知道它是"一个打包好的变换"就够了。

# 真实代码:五行,把 pos/scale/rot 拼成矩阵
这活儿发生在 update_item_transform(obs-scene.c:601)。函数前半段在算 scale / origin / position 这些数值(处理对齐、裁剪、画布缩放等细节),真正"拼矩阵"的,就是这连续五行(obs-scene.c:657):
// libobs/obs-scene.c:657
matrix4_identity(&item->draw_transform); // ① 从「白纸」开始
matrix4_scale3f(&item->draw_transform, &item->draw_transform, scale.x, scale.y, 1.0f); // ② 先按 scale 缩放
matrix4_translate3f(&item->draw_transform, &item->draw_transform, -origin.x, -origin.y, 0.0f); // ③ 移到对齐原点
matrix4_rotate_aa4f(&item->draw_transform, &item->draw_transform, 0.0f, 0.0f, 1.0f, RAD(item->rot)); // ④ 绕 z 轴转 rot 度
matrix4_translate3f(&item->draw_transform, &item->draw_transform, position.x, position.y, 0.0f); // ⑤ 平移到最终位置
2
3
4
5
6
一行一行读,你会发现它就是"照着人的直觉,一步步把动作乘进去":
- ①
matrix4_identity—— 把draw_transform重置成单位矩阵。单位矩阵就是"啥也不改的变换"(相当于数字里的 1),一张干净白纸,从这儿开始往上叠。 - ②
matrix4_scale3f(..., scale.x, scale.y, 1.0f)—— 先缩放。scale.x/scale.y就是这个源被你拉大/缩小的倍数;z 方向填1.0f(2D 合成,深度不缩)。 - ③
matrix4_translate3f(..., -origin.x, -origin.y, ...)—— 平移到对齐原点。origin是根据这个项的对齐方式(居中?左上角?)算出来的锚点;先减掉它,是为了让接下来的旋转绕着正确的中心转,而不是绕左上角瞎转。 - ④
matrix4_rotate_aa4f(..., 0,0,1, RAD(item->rot))—— 旋转。aa= axis-angle(轴 + 角度),(0,0,1)表示绕 z 轴(垂直于屏幕那根轴)转;RAD(item->rot)把你在界面里填的"度数"转成弧度。 - ⑤
matrix4_translate3f(..., position.x, position.y, ...)—— 最后平移到它在画布上真正该待的位置。position就是你拖出来的那个pos(经过画布坐标换算)。
顺序是有讲究的:先缩放、再旋转、最后平移。 这就是为什么你在 OBS 里旋转一个源,它是绕自己的中心转、而且转完还待在原地——因为先减 origin 把中心挪到了旋转轴上,转完再用 position 送回去。把顺序打乱,旋转就会"甩飞"到别处。
🔧 这里引入了什么"库"? 没有外部库。
matrix4_identity/matrix4_scale3f/matrix4_rotate_aa4f/matrix4_translate3f全是 libobs 自带的数学库(libobs/graphics/matrix4.c、math-defs.h里的RAD)。OBS 没有依赖 glm、Eigen 这类第三方数学库,自己写了一套精简的vec2/vec3/vec4/matrix4/quat——因为图形引擎里这点线代运算,自己维护比拉一个大依赖更划算。
💡 旁注 E:这五行什么时候才跑?(不是每帧都算!) 你可能担心:每合成一帧都要重算一次矩阵,岂不是很费?其实不会。
update_item_transform只在你真正改了pos/scale/rot(或源尺寸变了)时才被调用一次,算完把结果缓存在item->draw_transform里。合成时(13.4)只是gs_matrix_mul读这个缓存矩阵,不重算。函数开头那句if (os_atomic_load_long(&item->defer_update) > 0) return;(obs-scene.c:614)还做了"延迟合并"——你拖动过程中一连串的改动,可以先攒着、最后统一算一次。这就是典型的"惰性重算":变了才算,没变就吃缓存,所以拖再快也不卡。
# 13.7 拆开黑盒②:坐标系与 ViewProj 里的"投影"
📍承上启下:矩阵拼好了,可它输出的还是画布像素,离 GPU 要的 −1~1 还差一步"投影"——这一节补上
ViewProj里的 Proj。
上面矩阵拼好了,item->pos 的单位是画布像素(比如 (1920, 1080) 那套)。可第 11 课说过,GPU 最终要的顶点坐标是 −1~1 的裁剪空间(clip space),跟画布像素完全不是一回事。这中间少了一步"投影"——把画布像素坐标,映射到 GPU 认的 −1~1 方框里。这就是 ViewProj 里那个 Proj(projection,投影) 的来历。

干这件事的,是一句 gs_ortho。画整个画布前会先设一次(obs-display.c:218),场景给每个项做离屏渲染时也会设(obs-scene.c:935):
// libobs/obs-display.c:218 —— 每次开画一个画布前
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
// left right bottom top near far
2
3
ortho = orthographic(正交投影)。这一句的意思是:
"我接下来用的坐标,x 从
0到cx(画布宽),y 从0到cy(画布高)。你(GPU 底层)帮我把这个范围,线性映射到 −1~1 的裁剪空间去。"
于是坐标就对上了:你在画布像素空间里用 pos=(x,y) 摆源,gs_ortho 负责把 0~1920、0~1080 这套"人看得懂的像素坐标",换算成"GPU 要的 −1~1"。正交投影的特点是不带近大远小的透视——2D 合成正需要这样,一个源摆哪就是哪、多大就是多大,不会因为"离得远"就变小。
现在可以把第 11 课那个 ViewProj 彻底说透了:
ViewProj=draw_transform(你的定位,画布像素空间) ×ortho(投影,画布像素 → −1~1)。
draw_transform(13.6 那五行拼的)——负责"这个源摆在画布的哪、多大、转多少",输出还在画布像素坐标系;ortho(这一句gs_ortho)——负责把画布像素坐标投影到 GPU 的 −1~1;- 两者一乘,就是喂给顶点着色器的
ViewProj。第 11 课VSDefault里那句mul(v.pos, ViewProj),一步就把源的顶点从"本地坐标",经过"画布定位 + 投影",直接送到了 GPU 的裁剪空间。
第 11 课我们说"顶点着色器负责定位",当时 ViewProj 是个黑盒;这一课,它被拆成了**定位(draw_transform)+ 投影(ortho)**两半,两半都能在源码里指到具体那一行。
# 13.8 拆开黑盒③:这幅合成好的画,到底画到哪去了
📍收束:定位和投影都讲透了,最后一个悬念——这幅合成好的画落到哪儿去了?答案是一张纹理,它就是交给下一课的成品。
最后一个悬念:scene_video_render 把所有项都画完了,这幅合成好的画面,画到哪儿去了?画到屏幕上了吗?
并不是。 它画进的是一张纹理(render target,渲染目标)——你可以理解成"一块显存里的画布"。合成的成果先落在这张纹理上,然后一路给预览显示、一路送去编码。

顺着调用链看,合成是被谁"启动"的?根子在 obs_view_render(obs-view.c:118):
// libobs/obs-view.c:118 (抽核心)
void obs_view_render(obs_view_t *view)
{
...
for (size_t i = 0; i < MAX_CHANNELS; i++) {
struct obs_source *source = view->channels[i];
if (source && !source->removed)
obs_source_video_render(source); // ← 画这个通道的源
}
...
}
2
3
4
5
6
7
8
9
10
11
view 的某个"通道(channel)"上挂着当前场景那个 source。引擎的视频线程每帧调一次 obs_view_render → obs_source_video_render(当前场景) → 场景的 video_render 回调,也就是 scene_video_render(13.4)→ 逐项画。这就把"每帧产出一幅画"这件事,和"合成"接上了。
而这一整趟画下来,像素落在了 libobs 提前设好的渲染目标上(第 11 课末尾讲过 render target 的概念)。这幅"合成好的一帧",就是后面第 14 课要按帧率定时取走、送进编码器的那个"成品"。
所以闭环了:合成的输出不是屏幕,而是一张纹理;这张纹理,就是流水线里"采集→合成"这半程交出去的、送给"编码"那半程的一帧成品。 第 13 课末尾那个"下一站:编码"的接口,就在这张纹理上。
# 13.9 模块 3 收官:一帧画面的前半生
📍收束:视频链路的前半段全连起来了,这一节回头看这一路,把底座"一切皆 source"再点一次题。
到这一课,视频链路的前半段全连起来了。回头看看这一路:

- 采集(第 10 课):摄像头/屏幕 →
obs_source_frame; - 上显存(第 11 课):帧 → GPU 纹理;
- 画 / 滤镜(第 11、12 课):着色器把源画出来、还能加特效;
- 合成(第 13 课):多个源,按各自的矩阵和层级,叠成最终一幅画面。
而贯穿这四步的,是同一把钥匙 —— 一切皆 Source(第 6 课):
- 采集源、滤镜、场景…… 全是 source;合成时,场景只是"按矩阵和顺序,逐个调它们的
video_render"; - 而场景自己也是 source,于是能嵌套、能被转场、能当成更大画面里的一块。
一个朴素到有点"狡猾"的抽象(万物都是 source),配上一套统一的渲染(都是纹理 + 着色器),就把"多源、多层、可嵌套、可加特效的实时合成"这么复杂的事,撑了起来。这就是读到模块 3 末尾,你该有的那个"啊,原来如此"的畅快。
# 13.10 本课小结
- 合成 = 把多个源,按位置/大小/层级,叠成最终一幅;打包这套排布的东西叫场景。
- 数据模型:
obs_scene持有一串obs_scene_item(obs-scene.h:30/99)。每个场景项 = 一个source+ 变换(pos/scale/rot)+user_visible(眼睛图标),用prev/next串成链表;链表顺序 = 图层上下层级。 - 场景也是 source(第 6 课伏笔闭合):
scene_info(obs-scene.c:1742,id="scene"、type=SCENE)是一张正常的登记表;obs_scene_create就是obs_source_create一个"scene"源(:1794)。于是场景能嵌套、能被转场、被引擎一视同仁。嵌套之所以免费:obs_source_video_render(item->source)遇到嵌套场景就递归进它的scene_video_render(旁注 D)。 - 合成渲染(
scene_video_render,obs-scene.c:1059):顺着链表,对每个可见项render_item——gs_matrix_mul(&item->draw_transform)设变换,再obs_source_video_render(item->source)画它的源(第 11 课那套)。 - 拖动一个源 = 改
item->pos/scale/rot→ 算出draw_transform4×4 矩阵 → 合成时gs_matrix_mul设进去 → 它就是第 11 课着色器里的ViewProj。第 11~13 课在此合龙。 draw_transform不是黑盒:update_item_transform(obs-scene.c:657)五行——identity → scale → translate(-origin) → rotate → translate(position)——把pos/scale/rot拼成一个 4×4 矩阵。用的是 libobs 自带数学库(无第三方依赖);且惰性重算,变了才算、否则吃缓存(旁注 E)。ViewProj拆成两半:ViewProj = draw_transform(定位,画布像素空间)× ortho(投影,画布像素 → −1~1)。gs_ortho(0,cx,0,cy,...)(obs-display.c:218)负责把画布像素坐标映射到 GPU 的裁剪空间。- 合成画到哪:
obs_view_render(obs-view.c:118)每帧调obs_source_video_render(当前场景)→ 合成结果落在一张**纹理(render target)**上,一路给预览、一路给编码。这张纹理就是交给第 14 课的"一帧成品"。 - 模块 3 收官:采集 → 上显存 → 画/滤镜 → 合成,前半段全连起来,底座是"一切皆 source"。
# 13.11 动手 / 观察(本课作业)
- 看层级 = 链表顺序:OBS 里往一个场景加三个源,在"来源"列表里上下拖动它们,观察谁盖住谁。你拖的,就是 13.2 那条
prev/next链的顺序。 - 看变换 = 矩阵:选中一个源,拖动/缩放/旋转它,或按 Ctrl+E 打开"编辑变换"看到
pos/scale/rot的具体数值 —— 它们仨,就是被合成成draw_transform矩阵、喂给ViewProj的。 - 验证"场景是 source":新建两个场景,在场景 A 里,"添加来源"时你会发现能把场景 B 作为一个来源加进来 —— 这就是 13.3 说的嵌套,只有"场景是 source"才做得到。
- 翻源码:打开
libobs/obs-scene.c,找到scene_video_render(:1059),确认那个while (item) { ... item = item->next; }循环;再找到scene_info(:1742),确认场景真的是用obs_source_info注册的。 - 数一数拼矩阵那五行:跳到
update_item_transform(obs-scene.c:601),对着 13.6 把identity → scale → translate(-origin) → rotate → translate(position)这连续五行找出来;再想想:如果把 ③ 的translate(-origin)删掉,旋转一个源时它会绕着哪儿转?(答案:绕左上角,而不是中心。) - 找投影那一句:在
obs-display.c:218找到gs_ortho(0, cx, 0, cy, -100, 100),它就是把画布像素坐标投影到 GPU −1~1 的那一步;cx/cy正是你的画布分辨率(如 1920/1080)。这就是ViewProj里"Proj"的真身。
# 下一课预告
这一课我们把一堆源"合成"成了一幅画面。但有两个问题还没答:第一,这幅画布本身是怎么定时产出"一帧成品"、送进后面的编码的?第二,你从"场景 A"切到"场景 B"时,那个淡入淡出、滑动的过渡,是怎么做出来的?
第 14 课:从合成结果到一帧成品 —— 视频管线与转场 —— 我们会看 libobs/media-io/video-io 那条视频输出管线怎么按帧率定时取帧,以及**转场(transition)**这种"夹在两个场景之间的 source"是怎么把两幅画面混合过渡的。模块 3 真正的收尾,把"画面"交到"编码"手里。下节课见。
📁 本课配图:
imgs/13-01~imgs/13-09📌 源码锚点:libobs/obs-scene.h:30(obs_scene_item)、:99(obs_scene)、libobs/obs-scene.c:601(update_item_transform)、:657(拼 draw_transform 的五行)、:897(render_item)、:935(gs_ortho 逐项离屏)、:964(gs_matrix_mul draw_transform)、:980(obs_source_video_render)、:1059(scene_video_render 合成循环)、:1742(scene_info —— 场景即 source)、:1794(obs_scene_create)、libobs/obs-view.c:118(obs_view_render —— 每帧启动合成)、libobs/obs-display.c:218(gs_ortho 画布投影)
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言