本文へスキップ
503 bad gateway ロゴ
X YouTube
実装解説

はじめての人にも分かる Background Effect Tools 実装解説

・ 読了目安 約15分 ・ 503 bad gateway

はじめての人にも分かる Background Effect Tools 実装解説

煙・火の粉・キラキラ、そして音楽に反応するビジュアライザー。OBSプラグイン Background Effect Tools の中身を、ソースコードを引きながら解説します。OBSプラグインの骨格、パーティクル、FFTによる音声解析、決定論的な乱数まで——作り手自身による技術ドキュメント。

画面いっぱいに「煙」「火の粉」「雲」「キラキラ」「雨・雪」「オーディオスペクトラム」などの動く背景を表示できる、OBS Studio 用のプラグインBackgroundEffectTools の中身を、ソースコードを引きながら解説します。

OBS Studio は配信・録画の定番ソフトで、画面に映すもの(カメラ・画像・文字など)を「ソース」と呼びます。その種類はプラグインで増やせます。BackgroundEffectTools は「動く背景演出」を 1 つのソースとして追加するプラグインで、OBS の「ソースを追加」メニューに「背景エフェクト (Background Effects)」 という名前で出てきます。出力はアルファ(透明度)付きなので、他のソースの上にも下にも自由に重ねられます。

このプラグインの一番の見どころは、音楽に反応することです。指定した音声をリアルタイムに解析し、低音・中音・高音の強さやビート(拍)を検出して、背景の動きや色に反映させます。記事の後半ではこの「音声解析」の仕組みをじっくり追います。

専門用語は出てくるたびにかみくだいて説明します。


0. 前提とする開発環境

このプラグインは Windows 専用で、次のツールでビルド(ソースコードから実行ファイルを作ること)します。

項目内容
OSWindows 10 / 11(64bit)
コンパイラVisual Studio 2022(または 2026)の C コンパイラ
ビルド構成ツールCMake 3.28 以上
依存ライブラリOBS Studio のソース・ライブラリ(libobs)
言語C 言語(C++ ではありません)

用語をかみくだくと:

  • CMake … 「どのファイルをどうコンパイルして 1 つのプラグインにまとめるか」の手順書を書くツール。CMakeLists.txt がその手順書です。
  • libobs … OBS 本体が提供する、プラグインから使う機能の集まり(描画・音声・設定など)。

姉妹プラグインの「FontEffectTools」(文字に演出をかけるもの)と違い、こちらは FreeType(フォント処理ライブラリ)を使いません。文字を扱わないぶん、依存ライブラリは OBS 本体だけとシンプルです。

ビルドは付属の build.bat を実行するだけで、構成 → コンパイル → 配布用フォルダ package\ への配置まで自動で行われます。出来上がるのはbackground-effect-tools.dll というプラグイン本体と、演出ごとの設定ファイル群です。

プラグインのモジュール名(DLL のファイル名)は background-effect-toolsです。OBS にインストールする際は、データフォルダ名も DLL と同じbackground-effect-tools にそろえる必要があります。この記事では製品名を「BackgroundEffectTools」、内部のファイル名を background-effect-tools と書き分けます。


1. OBS プラグインの「骨格」

OBS のプラグインは、「こういう時にこの関数を呼んでね」という関数(コールバック)を OBS に登録することで動きます。入口は短く、自作ソースを登録するだけです(src/plugin-main.cobs_module_load)。

登録の実体は、たくさんのコールバックを 1 つの表(構造体)に詰めて OBS に渡すこと(src/bgfx-source.c:356)。一部を抜き出すと:

static struct obs_source_info s_bgfx_source_info = {
    .id             = "background_effect_source",   /* このソースの内部 ID */
    .type           = OBS_SOURCE_TYPE_INPUT,        /* 入力ソースですよ     */
    .output_flags   = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW,
    .get_name       = bgfx_get_name,
    .create         = bgfx_create,                  /* ソース作成時         */
    .destroy        = bgfx_destroy,                 /* 削除時               */
    .update         = bgfx_update,                  /* 設定が変わった時     */
    .video_tick     = bgfx_video_tick,              /* 毎フレーム(時間進行)*/
    .video_render   = bgfx_video_render,            /* 毎フレーム(描画)    */
    .get_width      = bgfx_get_width,
    .get_height     = bgfx_get_height,
    .get_properties = bgfx_properties,              /* 設定画面を組み立てる  */
    .get_defaults   = bgfx_defaults,
    .show           = bgfx_show,
};

プラグイン作りの大半は「この表の各関数を埋めていく」作業です。ここで早めに押さえておきたい OBS の作法が 3 つあります。

作法① 「自分で描く」宣言

output_flagsOBS_SOURCE_CUSTOM_DRAW は、「描画は全部こちらでやります、OBS の自動処理は使いません」という宣言です。煙を半透明で重ねたり、火の粉を加算合成(光を足し合わせる描き方)したりと、描き方を細かく自分で切り替えるので、このフラグが必要です。

作法② 横幅・高さに「0」を返さない

OBS は get_width / get_height でソースの大きさを聞いてきます。本プラグインは設定で指定された解像度(既定 1920×1080)を返しますが、万一 0 にならないよう最低 1 を返します(src/bgfx-source.c:197)。0 を返すと OBS 側の計算が壊れてソースが消えたように見える、という定番の落とし穴を避けるためです。

作法③ 「時間を進める係」と「描く係」は別

毎フレーム呼ばれる関数が 2 つあります。

  • video_tick(dt)dt は前フレームからの経過秒。煙を少し動かすなど状態を進める役。
  • video_render … 実際に画面へ描く役。

この 2 つは前提が違います。特に重要なのが「グラフィックスロック」(GPU を触ってよい順番待ちの権利)を持っているかどうか。描画系の関数(後述の gs_〜)はこの権利がある間しか呼べません。video_render・作成・破棄は権利を持った状態で呼ばれ、video_tick・設定更新は持たずに呼ばれます(src/bgfx-effect.h:35 にコメントで明記)。これを取り違えて権利の無い所でGPU を触ると即クラッシュします。


2. 「1 つのソースに演出を 8 種類」載せる仕組み

BackgroundEffectTools は、1 つのソースの中に煙・火の粉・雲…と複数の演出を同居させ、ドロップダウンで切り替えられます。これを実現するため、演出 1 つを「規格化された部品」として抽象化しています。

演出の「共通の型」

すべての演出は同じ形(struct bg_effectsrc/bgfx-effect.h:41)に従います。いわば「背景演出プラグインの差込口の規格」です。

struct bg_effect {
    const char *id;        /* 設定に保存する名前。例 "smoke"          */
    const char *name_key;  /* 選択メニューに出す表示名のキー           */

    void *(*create)(void);                          /* 部品を用意する    */
    void  (*destroy)(void *state);                  /* 後始末            */
    void  (*load_graphics)(void *state);            /* GPU 資源を読み込む */
    void  (*update)(void *state, obs_data_t *s);    /* 自分の設定を読む   */
    void  (*tick)(void *state, const struct bg_ctx *ctx, float dt);
    void  (*render)(void *state, const struct bg_ctx *ctx);
    void  (*reset)(void *state, uint32_t seed);     /* 状態を初期化       */
    /* …get_properties / get_defaults… */
};

void (*render)(...) のような書き方は「関数を指す変数(関数ポインタ)」で、「この欄に自分の描画関数を入れておいてね」という意味です。各演出は自分専用のファイル(effect-smoke.c など)でこの欄を埋めるだけ。用意した演出はレジストリ(一覧の配列)に並べておきます(src/bgfx-registry.c:16)。

static const struct bg_effect *const k_effects[] = {
    &bgfx_smoke, &bgfx_embers, &bgfx_clouds, &bgfx_sparkles,
    &bgfx_gravity, &bgfx_vortex, &bgfx_spectrum, &bgfx_audioviz,
};

この設計の嬉しさ: 新しい演出を足すとき、やることは「演出ファイルを 1 本書く」「この配列に 1 行足す」「ビルド手順書に 1 行足す」の 3 つだけ。本体(ホスト)側のコードには一切触りません。機能追加のために既存コードをいじらない、保守しやすい形を、特別なフレームワークなしの素の C で実現しています。

毎フレーム、エフェクトに渡す「コンテキスト」

ホストは毎フレーム、今選ばれている演出へ「今の状況」をまとめた箱(struct bg_ctx)を渡します(src/bgfx-effect.h:15)。中身は、

  • time … 開始からの経過秒
  • width / height … キャンバスの大きさ
  • seed / seed_locked … 乱数の種(後述)
  • audio / fft解析した音声の情報(後述)

演出側はこの箱を読むだけで描けます。状態は全演出ぶんを同時に持ち、設定変更(update)も全演出に対して行うので、演出を切り替えても各自の設定が保たれます(src/bgfx-source.c:84)。この「全部持っておく」割り切りはFontEffectTools と同じ考え方です。


3. 2 通りの描き方 ―― シェーダで塗るか、粒を撒くか

演出の描き方は大きく 2 種類です。用語を 2 つだけ:

  • シェーダ … GPU 上で動く小さなプログラム。画面の各点を何色にするかを超高速に並列計算します。
  • テクスチャ … GPU が扱う画像のこと。

描き方その①: シェーダで画面全体を塗る(雲)

「雲」は入力画像を一切使わず、シェーダだけで模様を生成します(data/effects/clouds.effect)。やっていることは、

  1. 時間とともに形が変わる「ゆらぎ模様」(ノイズ)を計算する
  2. 2 色を混ぜて雲のまだら模様にする
  3. 明るい所をさらに光らせ、方向性のあるグラデーションを掛ける

ノイズは fbm5 という関数で、複数の細かさの模様を重ねて自然な雲を作ります。しかも各層の流れる速度を少しずつ変えることで、ただ平行移動するのでなく「形が変わりながら」流れるように見せています(clouds.effect:54)。

ここで出力の作法に注意点が 1 つ。シェーダの最後はこうなっています(clouds.effect:107)。

float a = opacity;
return float4(col * a, a);   /* 色にも a を掛けて返す */

色(RGB)に透明度 aあらかじめ掛けて返しています。これを「プリマルチプライド(乗算済み)アルファ」と呼びます。OBS で半透明のものをきれいに重ねるための定番の形で、これを忘れると縁に黒や白のフチが出ます。

描き方その②: CPU で粒を撒く(煙・火の粉・雨…)

煙や火の粉は、CPU で 1 粒ずつ位置を計算して動かす「パーティクル(粒子)システム」です(src/bgfx-particles.c)。打ち上げ花火の火花が、上がって、広がって、消えていく、あの動きをプログラムで再現するものです。

煙の動きを例にとると(src/effect-smoke.c)、

  • 発生: 毎フレーム、一定の割合で新しい粒を画面の下端などに生む(smoke_tickemit_accum)。
  • 動き: 各粒に「上昇」「風」「渦のような横ゆれ」の力を足して動かす。煙が上るにつれて膨らむよう、grow(拡大率)も設定する。
  • 消滅: 寿命が尽きた粒は配列から消す。

実務的な工夫として、発生数をフレームレートに依存させない仕組みがあります(src/effect-smoke.c:187)。

sys->emit_accum += s->common.rate * dt;   /* 経過時間ぶん「発生したい数」を貯める */
while (sys->emit_accum >= 1.0f && sys->live < cap) {
    sys->emit_accum -= 1.0f;
    smoke_spawn(s, ctx);                  /* 1 を超えたぶんだけ実際に生む */
}

「1 秒あたり何粒」という割合に経過秒を掛けて小数で貯め、1 を超えた整数ぶんだけ生みます。こうすると 30fps でも 60fps でも同じペースで煙が出ます。フレームレートが変わると煙の濃さが変わる、というありがちなバグを防ぐ定石です。

描画は「全部まとめて 1 回」

生きている全粒は、bg_particles_rendersrc/bgfx-particles.c:111)で1 回の描画指示にまとめて描きます。粒ごとに命令を分けると遅いので、粒の個性(明るさ・年齢・色)は各頂点に相乗りさせて運びます。煙は半透明の通常合成、火の粉は加算合成(光って見える)、と演出ごとに重ね方を変えます(src/effect-smoke.c:266)。

gs_blend_state_push();                                  /* 重ね方を退避  */
gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);  /* 通常の重ね方  */
bg_particles_render(s->sys, s->sprite, BG_SHAPE_PUFF, NULL, &ctx->audio);
gs_blend_state_pop();                                   /* 元に戻す      */

落とし穴: 重ね方(ブレンド)の設定は OBS の中で全ソースが共有する 1 つの設定です。push で退避せずに変えると他のソースの描画にまで影響が漏れます。使う前に退避、使い終わったら復元、が鉄則です。


4. 音楽に反応させる ―― 音声解析の仕組み

ここからが BackgroundEffectTools の真骨頂です。指定した音声をリアルタイムに解析し、その結果を背景に反映させます。担当は bgfx-audio.c です。

まず立ちはだかる「2 つのスレッド」問題

音声と映像は、OBS の中で別々のスレッド(並行して動く処理の流れ)で動いています。

  • 音声スレッド: 音のデータが届くたびに呼ばれる
  • 映像スレッド: 毎フレームの描画で動く

音声スレッドが受け取った音を、映像スレッドが描画時に使いたい。でも 2 つのスレッドが同じデータを同時に触ると壊れます(データ競合)。これは並行プログラミングの古典的な難所です。

本実装はこれを「リングバッファ + ミューテックス」で解決しています。

  • リングバッファ: 固定長の輪っか状のメモリ。新しい音を書き込むと、古いものが上書きされていく。常に「直近の音」が入っている。
  • ミューテックス: 「今このデータは私が触っています」という札。札を持っている間は他方は待つので、同時アクセスが起きない。

音声スレッド側はこう書きます(capture_cbsrc/bgfx-audio.c:61)。

static void capture_cb(void *param, obs_source_t *source,
                       const struct audio_data *ad, bool muted)
{
    struct bg_audio_meter *m = param;
    const float *ch = (const float *)ad->data[0];   /* 1 ch 目(モノラル)*/

    pthread_mutex_lock(&m->mutex);                  /* 札を取る           */
    uint32_t w = m->widx;
    for (uint32_t i = 0; i < ad->frames; ++i) {
        m->ring[w & RING_MASK] = (ch && !muted) ? ch[i] : 0.0f;
        ++w;                                        /* 輪っかに書き込む    */
    }
    m->widx = w;
    pthread_mutex_unlock(&m->mutex);                /* 札を返す           */
}

この関数を音声に紐づけるのが obs_source_add_audio_capture_callbacksrc/bgfx-audio.c:147)。「この音声ソースに音が来たら、この関数を呼んで」と OBS にお願いする仕組みです。

ちょっとした実務上の罠: 設定を読み込んだ時点では、指定した音声ソースがまだ存在しないことがあります(シーンの読み込み順の都合)。そこで毎フレーム「まだ繋がっていなければ繋ぐ」という遅延接続を行います(src/bgfx-audio.c:211)。これを怠ると「起動直後だけ音に反応しない」という再現しにくい不具合になります。

音を「成分」に分解する ―― FFT

映像スレッドは毎フレーム、リングバッファから直近の音を取り出して解析します(bg_audio_ticksrc/bgfx-audio.c:203)。中心にあるのが FFT(高速フーリエ変換) です。

FFT をひと言で言うと、「混ざった音を、低い音〜高い音の成分に分解する」計算です。スーパーのレシートのように「この音には低音がこれくらい、中音がこれくらい、高音がこれくらい入っている」という内訳を出してくれます。グラフィックイコライザーの棒グラフが上下するのは、まさにこの分解結果です。

実装では、

  1. 窓を掛ける: 取り出した音の両端をなめらかに 0 へ落とす(ハン窓)。ぶつ切りにすると解析にノイズが乗るのを防ぐ前処理です(src/bgfx-audio.c:250)。
  2. FFT を実行: 自前の radix-2 FFT(fft_runsrc/bgfx-audio.c:168)。毎回使う計算表(回転因子・ビット反転表)は起動時に一度だけ作っておき、実行時は表引きで速くする、という定番の最適化をしています(src/bgfx-audio.c:86)。
  3. 対数で棒に振り分ける: 人間の耳は周波数を対数的に感じる(低音側ほど細かく聞き分ける)ので、棒グラフの各バーを対数間隔で区切ります(src/bgfx-audio.c:267)。
  4. 低・中・高にまとめる: バー群を 3 つの帯域(bass / mid / treble)に集計します(src/bgfx-audio.c:296)。

拍(ビート)を見つける

音楽に合わせて「ドンッ」と反応させたい。そのためにビート検出もしています(src/bgfx-audio.c:307)。

float bass_now = m->fft.bass;
m->bass_avg += (bass_now - m->bass_avg) * 0.12f;   /* 低音の移動平均 */
if (bass_now > m->bass_avg * 1.35f && bass_now > 0.12f &&
    m->since_beat > 0.12f) {                       /* 平均を急に超えた瞬間 */
    m->fft.beat = 1.0f;                            /* → ビート!          */
    m->since_beat = 0.0f;
}

考え方はシンプルで、「低音の最近の平均を覚えておき、それを急に大きく(1.35 倍)超えた瞬間を拍とみなす」というもの。さらに「直前の拍から0.12 秒は次の拍を出さない」という間隔制限で、二重検出を防いでいます。

なめらかに追従させる ―― アタックとリリース

解析した値をそのまま使うと、数値がチカチカ暴れて見た目が落ち着きません。そこで、値をなめらかに追従させます(src/bgfx-audio.c:240)。

float tc = (lvl > m->level) ? attack : release;     /* 上がる時/下がる時 */
float sa = (tc > 1e-4f) ? 1.0f - expf(-dt / tc) : 1.0f;
m->level += (lvl - m->level) * sa;                  /* 目標へ少しずつ寄せる */

ポイントは、上がる時(attack)と下がる時(release)で追従の速さを変えること。音が鳴った瞬間は素早く反応し(attack を短く)、消える時はゆっくり余韻を残す(release を長く)、という調整ができます。これは音響機器のコンプレッサーなどと同じ考え方で、自然な反応に仕上がります。

反応のさせ方は「部品ごとに独立」

解析結果は、パーティクルの「サイズ」「色」「跳ね」の 3 つに、それぞれ独立にオン/オフ・量を設定して反応させられます(src/bgfx-particles.c:124 以降)。たとえば「音量でサイズが膨らむ」「ビートで跳ねる」「高音で色がピーク色へ寄る」を自由に組み合わせられます。

さらに「オーディオスペクトラム」演出は、この解析結果そのものを棒グラフや円形ビジュアライザーとして描く専用エフェクトです(src/effect-spectrum.c)。円形(Trap Nation 風)・バー(Monstercat 風)・波形リング・ビート連動の4 タイプを切り替えられます。


5. 「いつ見ても同じ」を選べる ―― 決定論的な乱数

パーティクルは乱数で動きをばらけさせています。ふつうは毎回違う動きになるほうが自然ですが、録画を撮り直すときには「さっきと寸分違わぬ同じ動き」が欲しいことがあります(別テイクと合成する場合など)。

そこで「乱数シード(種)を固定する」オプションがあります。仕組みのキモは、乱数を「種」から計算で作り出す点です。同じ種からは必ず同じ乱数列が出るので、種を固定すれば毎回同じ動きが再現できます。

ただし素朴にやると、画面に 2 つ置いた背景がまったく同じ動きで重なってしまう問題があります。本実装は、

  • インスタンスごとの塩(salt): ソースを作るたびに違う値を混ぜ、固定していない時は 2 つの背景が同期しないようにする(src/bgfx-source.c:21)。
  • 演出ごとに種を散らす: 1 つの種から、演出ごとに別々だが再現可能な種を計算する(effect_seedsrc/bgfx-source.c:37)。

という 2 段で、「固定すれば完全再現、固定しなければ自然にばらける」を両立させています。種を固定した瞬間や種を変えた瞬間には、全演出をリセットしてそこから決定論的に再生し直します(src/bgfx-source.c:92)。


6. 設定画面の組み立てと「接頭辞」テクニック

演出ごとの専用設定は、OBS の設定画面にいったん全部用意しておき、今選ばれている演出のものだけを表示します(on_effect_changedsrc/bgfx-source.c:236)。音声設定のグループも、「音声リアクティブを使う」をオンにした時だけ現れます(on_audio_enable_changed)。

ここで、複数の演出が同じような設定項目(サイズ・寿命・発生数・色…)を持つ問題があります。設定値には名前(キー)が必要ですが、煙の「サイズ」と火の粉の「サイズ」が同じキーだと衝突してしまいます。

解決策は、キーに演出ごとの接頭辞を付けること。bg_key() ヘルパーが"smoke" + "size""smoke_size" のようにキーを組み立てます(src/effect-smoke.c:277 付近)。

bg_common_props(g, PRE, &k_spec);   /* PRE は "smoke"。共通項目をまとめて追加 */

bg-props.c には、こうした「どの演出でも使う共通設定(サイズ・寿命・色・風・ポストエフェクト等)」をまとめて追加・読み出しするヘルパーが集約されています。各演出はそれを呼ぶだけで、定型の設定群を接頭辞付きで一式そろえられ、重複コードを大幅に減らしています


7. まとめ ―― この実装から学べること

BackgroundEffectTools を貫く設計を整理します。

  1. 演出を「規格化された部品」にした。共通の型を 1 つ決め、新しい演出は「ファイルを 1 本書いて一覧に 1 行足す」だけ。本体には触りません。(姉妹プラグイン FontEffectTools と共通の考え方です。)
  2. 2 つの描き方を使い分ける。画面全体の模様はシェーダで生成し、粒の動きは CPU のパーティクルで作る。どちらも OBS の描画 API で 1 回にまとめて描きます。
  3. 音声解析を別スレッドの壁ごしに取り込む。音声スレッドが書き、映像スレッドが読む。その受け渡しをリングバッファとミューテックスで安全に行い、FFT・帯域分解・ビート検出・なめらか追従までを毎フレーム回します。
  4. 再現性を選べるようにした。乱数を種から作ることで、「固定すれば完全再現、しなければ自然にばらける」を両立させています。

OBS プラグインを作るうえで繰り返し出てきた落とし穴:

  • GPU を触ってよい場面かを常に意識する(権利の無い所で触るとクラッシュ)。
  • 重ね方の設定は使う前に退避、後で復元(全ソースで共有しているから)。
  • 横幅・高さに 0 を返さない(最低 1)。
  • 半透明の出力は「乗算済みアルファ」で返す(縁にフチが出ないように)。
  • 発生数はフレームレートに依存させない(経過時間ぶんを小数で貯めて使う)。
  • 音声を扱うなら、別スレッドとの受け渡しを必ず保護する(リングバッファ +ミューテックス)。音声ソースは遅れて現れることがあるので遅延接続する。
  • 解析値はそのまま使わずなめらかに追従させ、上がる時と下がる時で速さを変えると自然になる。

「動く背景」という一見シンプルなお題の裏に、並行プログラミング・信号処理(FFT)・パーティクル・GPU 描画と、幅広い要素が詰まっています。それらを特別なフレームワークに頼らず素の C でまとめ上げ、しかも「演出を足しやすい」構造に整理している点が、このプラグインの読みどころです。

他の読み物

はじめての人にも分かる BadAutoAlign 解説——ボーカル自動補正の理論と設計 実装解説

はじめての人にも分かる BadAutoAlign 解説——ボーカル自動補正の理論と設計

ガイドを基準にボーカルのタイミングとピッチを一括自動補正するツール BadAutoAlign。その設計ドキュメント一式をもとに、処理パイプライン、3度の失敗を経てたどり着いたタイミング補正の理論、ケロらせないピッチ補正、検出系、GUIの分業設計、数値検証までを通して解説します。作り手自身による技術ドキュメント。

読む
配信画面の背景に煙・火の粉・キラキラを足す方法——Background Effect Tools の使い方 ハウツー

配信画面の背景に煙・火の粉・キラキラを足す方法——Background Effect Tools の使い方

OBS Studio に「背景エフェクト」ソースを1つ足すだけで、配信画面に煙や火の粉、キラキラ、音に反応するビジュアライザーを重ねられる。無料・オープンソースの自作プラグイン Background Effect Tools の導入と使い方を、作り手自身が解説します。

読む
OBSで“動く装飾テロップ”を表示する方法——FontEffectTools の使い方 ハウツー

OBSで“動く装飾テロップ”を表示する方法——FontEffectTools の使い方

グロー・アウトライン・縦書きから、スライドインや震えといった動きのある演出まで。OBS Studio 用の無料・オープンソースプラグイン FontEffectTools で、配信のテロップを見やすく・かっこよく仕上げる方法を解説します。

読む