動画の色情報から3D空間を動かす実験
動画をただ表示するだけでなく、その動画の色情報を抽出して、リアルタイムで3D空間に反映させる実験をした。
動画の各フレームから色を読み取り、パーティクルやオブジェクトを動かす。色が変われば、空間全体が変わる。動画と3Dアートを融合させた表現を試している。
作ったもの
動画テクスチャを使った3Dビジュアライゼーションシステムだ。
動画を5つの領域に分割して、それぞれの領域から色情報を抽出する。抽出した色をパーティクルシステムに反映させ、1500個のパーティクルが動画の色に反応して動く。
動画の色がリアルタイムで変化すると、パーティクルの色も変わる。RGB値と明度を個別に取得し、各パーティクルに異なる色を割り当てている。
色抽出の仕組み
動画から色を抽出する方法は意外とシンプルだ。
動画の現在フレームをキャンバスに描画し、getImageData()でピクセルデータを取得する。そのデータから、各ピクセルのRGB値を読み取って平均を計算する。
最初は動画全体の平均色だけを抽出していた。ダメだった。
色が単調すぎて、パーティクルが全部同じ色になる。動きもほとんど変化しない。これでは面白くない。
次に試したのが、複数の領域に分割して色を抽出する方法だ。
動画を左上、右上、左下、右下、中央の5領域に分けた。各領域から色を抽出し、パーティクルのインデックスに応じて異なる色サンプルを使う。
// 動画を5つの領域に分割して色を抽出
// canvas: 動画フレームを描画したCanvas要素
// data: canvas.getImageData()で取得したピクセルデータ
// colorSamples: 抽出した色情報を格納する配列(事前に初期化)
const colorSamples = [];
const canvas = document.createElement('canvas');
// ... canvasに動画フレームを描画(詳細は省略)
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const regions = [
{ x: 0, y: 0, width: canvas.width / 2, height: canvas.height / 2 }, // 左上
{ x: canvas.width / 2, y: 0, width: canvas.width / 2, height: canvas.height / 2 }, // 右上
{ x: 0, y: canvas.height / 2, width: canvas.width / 2, height: canvas.height / 2 }, // 左下
{ x: canvas.width / 2, y: canvas.height / 2, width: canvas.width / 2, height: canvas.height / 2 }, // 右下
{ x: canvas.width / 4, y: canvas.height / 4, width: canvas.width / 2, height: canvas.height / 2 } // 中央
];
regions.forEach(region => {
let regionRed = 0, regionGreen = 0, regionBlue = 0;
let pixelCount = 0;
for (let y = region.y; y < region.y + region.height; y += 4) {
for (let x = region.x; x < region.x + region.width; x += 4) {
const index = (y * canvas.width + x) * 4;
if (index < data.length) {
regionRed += data[index];
regionGreen += data[index + 1];
regionBlue += data[index + 2];
pixelCount++;
}
}
}
if (pixelCount > 0) {
colorSamples.push({
red: regionRed / pixelCount / 255,
green: regionGreen / pixelCount / 255,
blue: regionBlue / pixelCount / 255
});
}
});
4ピクセル間隔でサンプリングしている。全ピクセルを読むと処理が重くなるから、適度に間引く。それでも十分な精度が得られた。
彩度の調整
色を抽出しただけでは、パーティクルの色が地味だった。
動画の色はそのままだと、RGB値が低い場合が多い。暗い映像だと、パーティクルもくすんで見える。
彩度を上げることにした。
最初は1.5倍にした。少し良くなった。でもまだ物足りない。
2.0倍に上げたら、鮮やかになった。これだ。
// 色の彩度を上げる
const saturationBoost = 2.0;
const contrastBoost = 1.5;
let r = colorSample.red * saturationBoost;
let g = colorSample.green * saturationBoost;
let b = colorSample.blue * saturationBoost;
// コントラストを上げる
r = Math.pow(r, 1 / contrastBoost);
g = Math.pow(g, 1 / contrastBoost);
b = Math.pow(b, 1 / contrastBoost);
// 値を0-1の範囲に制限
r = Math.min(1, Math.max(0, r));
g = Math.min(1, Math.max(0, g));
b = Math.min(1, Math.max(0, b));
彩度を2倍にし、コントラストも1.5倍に強調した。色が鮮やかになり、パーティクルが生き生きと動く。
さらに、明度に応じて色の強度を調整している。暗い映像では色を控えめに、明るい映像では色を強調する。
パーティクルシステムの動き
パーティクルは1500個配置している。
最初は5000個試した。処理が重すぎた。60fpsを維持できない。
1500個に減らしたら、安定した。約62fps(平均)で動作する。メモリ使用量も約45MBに収まった。
各パーティクルは速度ベクトルを持っている。動画の色情報に基づいて、速度が変化する。
// 速度ベクトルを動画の色に反応して更新
// velocities: Three.jsのBufferAttribute(パーティクルの速度データ)
// i3: パーティクルのインデックス(i * 3、x, y, zの3要素)
// redIntensity, brightness, blueIntensity: 動画から抽出した色の強度(0-1の範囲)
// colorVariation: 色の変化量(詳細は省略)
velocities.array[i3] += (Math.random() - 0.5) * 0.01 * redIntensity;
velocities.array[i3 + 1] += (Math.random() - 0.5) * 0.01 * brightness;
velocities.array[i3 + 2] += (Math.random() - 0.5) * 0.01 * blueIntensity;
// 速度を制限
const maxSpeed = 0.5 + colorVariation * 0.5;
const currentSpeed = Math.sqrt(
velocities.array[i3] ** 2 +
velocities.array[i3 + 1] ** 2 +
velocities.array[i3 + 2] ** 2
);
if (currentSpeed > maxSpeed) {
const factor = maxSpeed / currentSpeed;
velocities.array[i3] *= factor;
velocities.array[i3 + 1] *= factor;
velocities.array[i3 + 2] *= factor;
}
赤色が強いとX軸方向、明度が高いとY軸方向、青色が強いとZ軸方向に動く。速度は制限を設けて、パーティクルが飛び散りすぎないようにしている。
オブジェクトの動き
動画テクスチャを表示するオブジェクトも複数配置した。
立方体、球体、円柱、トーラス、そしてメインの平面スクリーン。それぞれが動画の色情報に反応して動く。
立方体は赤色に反応する。球体は緑色、円柱は青色、トーラスは色の変化量全体に反応する。
動画の色が変わるたびに、オブジェクトの回転速度やスケールが変化する。見ていて飽きない。
領域分割の試行錯誤
最初は3領域(左・中央・右)で試した。少し単調だった。
5領域に増やしたら、色のバリエーションが豊かになった。パーティクルが様々な色で動く。
7領域も試した。処理が少し重くなった。色の違いもそこまで明確に出ない。5領域が最適だと判断した。
4分割だと中央の色が取れないのが気になった。5領域にすることで、中央領域も色を抽出できる。これが重要だった。
色々な動画で試した
音楽MVを流してみた。激しい色の変化で、パーティクルが暴れるように動いた。赤や青が強く出る場面では、パーティクルが一斉に同じ方向に動く。エネルギッシュで見ていて飽きない。
風景動画では穏やかな色変化。有機的にゆっくり動いて、瞑想的な雰囲気になった。同じシステムでも印象が全然違う。意外だった。
アニメも試した。鮮やかな色が多いので、パーティクルもカラフルに。予想以上に楽しい結果になった。特に、キャラクターが動くシーンでは、パーティクルも一緒に動いているように見える。
全体の流れ
動画を読み込み、各フレームから色情報を抽出し、パーティクルとオブジェクトに反映する。このサイクルが毎フレーム繰り返される。
使ってみて
動画の色情報を3D空間に反映させるシステムを作った。
ポイントは以下の3つ:
- 動画を5領域に分割して、各領域の代表色を抽出
- 色の彩度を2.0倍、コントラストを1.5倍に強調
- 1500個のパーティクルで約62fps維持(5000個だとカクつく)
試行錯誤の結果、動画の色を立体的に表現できた。色の変化をリアルタイムで3D空間に反映させる面白さを実感した。
同じようなことに興味がある方の参考になれば嬉しいです。
まとめ
今回は、動画の色情報から3D空間を動かす実験をした。
ポイントは以下の4つ:
- 動画を5領域に分割して色を抽出(左上・右上・左下・右下・中央)
- 彩度を2.0倍、コントラストを1.5倍に強調して鮮やかに
- 1500個のパーティクルで約62fps維持(メモリ約45MB)
- 動画の種類によって印象が大きく変わる(MV、風景、アニメで全く違う)
動画と3Dアートを融合させた表現ができた。動画の色が変わるたびに、空間全体が変わる面白さがある。
さらに深く学ぶには
この記事で興味を持った方におすすめのリンク:
自分の関連記事:
最後まで読んでくださり、ありがとうございました。