OBS图形及渲染
+ -

OBS渲染到预览窗口

2024-12-12 19 0

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更新实现的。

0 篇笔记 写笔记

作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!