OBS渲染到预览窗口
OBS默认只有一个主窗口用于预览渲染输出结果,但其程序架构设计可以支持多个。
预览输出前置条件
在OBS场景中加入的视频图像等资源最终被输出到obs_core_video结构体video成员的render_texture纹理上。
struct obs_core_video *video;
tex = &obs->video.render_texture;
OBS中需要预览的输了使用obs_display结构体来表示,其由链表obs->data中。
render_displays函数如下:
struct obs_display* display = obs->data.first_display;
while (display) {
render_display(display);
display = display->next;
}
故对于每个预览渲染对象,调用obs_display(display)即可。
每个预览输出条件准备
前面知道,对于每个面要渲染的窗口使用obs_display来表示,其使用render_display函数来进行实现最终渲染。
obs_display中做了以下几件事:
- 1.从obs_display结构体中取得渲染窗口的大小,调用render_display_begin加载D3D11交换链、设置D3D11 ViewPort和着色器常量空间矩阵。
- 2.调用obs_display创建时的回调函数进行渲染,这里主窗品为OBSBasic::RenderMain
1.render_display_begin
render_display_begin主要调用2次D3D11的代码:
- ClearRenderTargetView
- RSSetViewports
- 准备着色器矩阵(实际没啥用,因为被次渲染时会自己提前设置自己需要的参数device->curProjMatrix)
- 暂存纹理渲染目标对象
- ID3D11Texture2D - GetBuffer(0)
- ID3D11RenderTargetView
- OMSetRenderTargets(实际在渲染时才会调用)
代码解析如下:
static inline void render_display_begin(struct obs_display *display,
uint32_t cx, uint32_t cy,
bool size_changed)
{
struct vec4 clear_color;
//设置窗口交换链,仅置参数到gs_device_t *device
gs_load_swapchain(display->swap);
if (size_changed)
gs_resize(cx, cy);
gs_begin_scene();
//设置清空窗口颜色,默认为0xf4fef4ff
vec4_from_rgba(&clear_color, display->background_color);
clear_color.w = 1.0f;
//使用上面的背景颜色调用ClearRenderTargetView清空
gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH | GS_CLEAR_STENCIL, &clear_color, 1.0f, 0);
gs_enable_depth_test(false);
/* gs_enable_blending(false); */
//设置裁剪模式GS_NEITHER
gs_set_cull_mode(GS_NEITHER);
//设置着色器参数device->curProjMatrix,cx cy为窗口大小
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
//调用d3d11的RSSetViewports设置视口大小,并记录参数
gs_set_viewport(0, 0, cx, cy);
}
主窗口渲染OBSBasic::RenderMain
1.首先根据缩放比例计算出实际的绘制区域大小
obs_video_info ovi;
obs_get_video_info(&ovi);
window->previewCX = int(window->previewScale * float(ovi.base_width));
window->previewCY = int(window->previewScale * float(ovi.base_height));
缩放比例的计算可参见:https://www.vaczh.com/article/detail-147.html
2.获取渲染目标窗口大小,计算绘制区域的相对坐标偏移
obs_display_t *display = window->ui->preview->GetDisplay();
uint32_t width, height;
obs_display_size(display, &width, &height);
float right = float(width) - window->previewX;
float bottom = float(height) - window->previewY;
3.绘制并设置视口大小
//paint main - curProjMatrix
gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),-100.0f, 100.0f);
//绘制区域-视口
gs_set_viewport(window->previewX, window->previewY, window->previewCX,window->previewCY);
//绘制内容
obs_render_main_texture_src_color_only();
obs_render_main_texture_src_color_only
obs_render_main_texture_src_color_only实现的是一次纹理渲染到窗口。使用的着色器为default文件中的Draw
相顶点和像素着色器代码分别如下:
default.effect-Draw[0].vs
uniform float4x4 ViewProj;
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;
}
VertInOut main(VertInOut vert_in)
{
return VSDefault(vert_in);
}
default.effect-Draw[0].PS
uniform texture2d image;
sampler_state def_sampler {
Filter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
struct VertInOut {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
float4 PSDrawBare(VertInOut vert_in)
{
return image.Sample(def_sampler, vert_in.uv);
}
float4 main(VertInOut vert_in) : TARGET
{
return PSDrawBare(vert_in);
}
1.首先获取到图像源纹理
struct obs_core_video *video;
video = &obs->video;
tex = video->render_texture;
2.获取编译后的着色器,并设置其image参数为图像源纹理
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
param = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture(param, tex);
得说明一下,这个不是rgb纹理,应是YUV的
void gs_effect_set_texture(gs_eparam_t *param, gs_texture_t *val)
{
struct gs_shader_texture shader_tex;
shader_tex.tex = val;
shader_tex.srgb = false;
effect_setval_inline(param, &shader_tex, sizeof(shader_tex));
}
effect_setval_inline最终也只是记录参数:
static inline void effect_setval_inline(gs_eparam_t *param, const void *data,
size_t size)
{
bool size_changed = param->cur_val.num != size;
if (size_changed)
da_resize(param->cur_val, size);
if (size_changed || memcmp(param->cur_val.array, data, size) != 0)
{
memcpy(param->cur_val.array, data, size);
param->changed = true;
}
}
3.更新参数混合模式 https://www.vaczh.com/article/detail-36.html
- src_c:GS_BLEND_ONE 针对颜色混合为(1,1,1),针对alpha值为1
- dest_c:GS_BLEND_ZERO 此外针对颜色混合,F为(0,0,0),针对alpha值,F为0
- src_a:GS_BLEND_ONE 针对颜色混合为(1,1,1),针对alpha值为1;
- dest_a:GS_BLEND_INVSRCALPHA 针对颜色混合为(1-As,1-As,1-As),针对alpha值为1-As;
颜色:DESC为颜色为默认为默,源颜色直接复制
ALPHA透明度:源为1,DESTO为1-1为0
所以结论为颜色直接复制
5.gs_effect_loop中就是调用gs_technique_begin_pass
VS PS相关的设置
- gs_load_vertexshader(cur_pass->vertshader);
- device->context->VSSetShader(shader, NULL, 0);
- device->context->IASetInputLayout(layout);
- device->context->VSSetConstantBuffers(0, 1, &constants);
- gs_load_pixelshader(cur_pass->pixelshader);
- device->context->PSSetShader(shader, NULL, 0);
- device->context->PSSetConstantBuffers(0, 1, &constants);
- device->context->PSSetSamplers(0, GS_MAX_TEXTURES, states);
- upload_parameters(tech->effect, false);
- upload_shader_params(vshader_params, changed_only);
- upload_shader_params(pshader_params, changed_only);
6.绘制gs_draw(GS_LINESTRIP, 0, 0);
最终调用的是
void device_draw(gs_device_t *device, enum gs_draw_mode draw_mode,
uint32_t start_vert, uint32_t num_verts)
{
device->FlushOutputViews(); //OMSetRenderTargets
gs_effect_t *effect = gs_get_effect();
if (effect)
gs_effect_update_params(effect);
device->LoadVertexBufferData();
device->UpdateBlendState();
device->UpdateRasterState();
device->UpdateZStencilState();
device->UpdateViewProjMatrix();
device->curVertexShader->UploadParams();
device->curPixelShader->UploadParams();
D3D11_PRIMITIVE_TOPOLOGY newTopology = ConvertGSTopology(draw_mode);
if (num_verts == 0)
num_verts = (uint32_t)device->curVertexBuffer->numVerts;
device->context->Draw(num_verts, start_vert);
}
这里涉及参数的更新,都是通过map更新实现的。