エンジニアの副業は週1からでも可能?副業の例や探し方も解説
- ITエンジニア
- 副業
Webカメラは通常、ビデオチャットに用いられることが多く、あまりクリエイティブな用途で使うことはありません。
しかしThree.jsと組み合わせれば、ゆがみ効果のついた魅力的なオーディオビジュアライザーが作れます。
今回はシンプルなコードだけで作れる、ユーザーのWebカメラを使ったオーディオビジュアライザーの作りかたを、チュートリアル形式でお伝えします。
まず、Webカメラにアクセスして画像を取得します。
getUserMedia()
を使うと、Webカメラにアクセスできます。
<video id="video" autoplay style="display: none;"></video>
video = document.getElementById("video");
const option = {
video: true,
audio: false
};
// Get image from camera
navigator.getUserMedia(option, (stream) => {
video.srcObject = stream; // Load as source of video tag
video.addEventListener("loadeddata", () => {
// ready
});
}, (error) => {
console.log(error);
});
カメラにアクセスできたら、カメラから画像を取得してキャンバスに描画します。
const getImageDataFromVideo = () => {
const w = video.videoWidth;
const h = video.videoHeight;
canvas.width = w;
canvas.height = h;
// Reverse image like a mirror
ctx.translate(w, 0);
ctx.scale(-1, 1);
// Draw to canvas
ctx.drawImage(image, 0, 0);
// Get image as array
return ctx.getImageData(0, 0, w, h);
};
ctx.getImageData()
は、「RGBA」が順に並んだ配列を返します。
[0] // R
[1] // G
[2] // B
[3] // A
[4] // R
[5] // G
[6] // B
[7] // A...
これを活用して、ピクセルの色情報にアクセスします
for (let i = 0, len = imageData.data.length; i < len; i+=4) {
const index = i * 4; // Get index of "R" so that we could access to index with 1 set of RGBA in every iteration.?0, 4, 8, 12...?
const r = imageData.data[index];
const g = imageData.data[index + 1];
const b = imageData.data[index + 2];
const a = imageData.data[index + 3];
}
画像を中央に配置できるように、X座標とY座標を計算しましょう。
const imageData = getImageDataFromVideo();
for (let y = 0, height = imageData.height; y < height; y += 1) {
for (let x = 0, width = imageData.width; x < width; x += 1) {
const vX = x - imageData.width / 2; // Shift in X direction since origin is center of screen
const vY = -y + imageData.height / 2; // Shift in Y direction in the same way (you need -y)
}
}
THREE.Geometry()
と THREE.PointsMaterial()
を使って、パーティクルを生成しましょう。
各ピクセルは、頂点としてジオメトリに追加されます。
const geometry = new THREE.Geometry();
geometry.morphAttributes = {};
const material = new THREE.PointsMaterial({
size: 1,
color: 0xff0000,
sizeAttenuation: false
});
const imageData = getImageDataFromVideo();
for (let y = 0, height = imageData.height; y < height; y += 1) {
for (let x = 0, width = imageData.width; x < width; x += 1) {
const vertex = new THREE.Vector3(
x - imageData.width / 2,
-y + imageData.height / 2,
0
);
geometry.vertices.push(vertex);
}
}
particles = new THREE.Points(geometry, material);
scene.add(particles);
カメラから画像データを取得し、そこからグレースケール値を計算することによって、パーティクルを利用して画像を更新します。
すべてのフレームにおいてこのプロセスを呼び出し、画面をビデオのように更新し続けるという仕組みです。
const imageData = getImageDataFromVideo();
for (let i = 0, length = particles.geometry.vertices.length; i < length; i++) {
const particle = particles.geometry.vertices[i];
let index = i * 4;
// Take an average of RGB and make it a gray value.
let gray = (imageData.data[index] + imageData.data[index + 1] + imageData.data[index + 2]) / 3;
let threshold = 200;
if (gray < threshold) {
// Apply the value to Z coordinate if the value of the target pixel is less than threshold.
particle.z = gray * 50;
} else {
// If the value is greater than threshold, make it big value.
particle.z = 10000;
}
}
particles.geometry.verticesNeedUpdate = true;
ここからは、音の処理方法を解説します。
まずTHREE.AudioLoader()
を使ってオーディオを読み込みます。
const audioListener = new THREE.AudioListener();
audio = new THREE.Audio(audioListener);
const audioLoader = new THREE.AudioLoader();
// Load audio file inside asset folder
audioLoader.load('asset/audio.mp3', (buffer) => {
audio.setBuffer(buffer);
audio.setLoop(true);
audio.play(); // Start playback
});
analyser.getAverageFrequency()
を使って、平均周波数を取得しましょう。
この値をパーティクルのZ座標に適用すれば、ビジュアライザーに奥行きがつきます。
以下がオーディオの周波数を取得する方法です。
// About fftSize https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/fftSize
analyser = new THREE.AudioAnalyser(audio, fftSize);
// analyser.getFrequencyData() returns array of half size of fftSize.
// ex. if fftSize = 2048, array size will be 1024.
// data includes magnitude of low ~ high frequency.
const data = analyser.getFrequencyData();
for (let i = 0, len = data.length; i < len; i++) {
// access to magnitude of each frequency with data[i].
}
最後に、カメラの画像とオーディオデータを組み合わせます。
ここまでのプロセスを組み合わせることで、パーティクルを使用してWebカメラの画像を描画し、オーディオデータによってビジュアルを操作できます。
const draw = () => {
// Audio
const data = analyser.getFrequencyData();
let averageFreq = analyser.getAverageFrequency();
// Video
const imageData = getImageData();
for (let i = 0, length = particles.geometry.vertices.length; i < length; i++) {
const particle = particles.geometry.vertices[i];
let index = i * 4;
let gray = (imageData.data[index] + imageData.data[index + 1] + imageData.data[index + 2]) / 3;
let threshold = 200;
if (gray < threshold) {
// Apply gray value of every pixels of web camera image and average value of frequency to Z coordinate of particle.
particle.z = gray * (averageFreq / 255);
} else {
particle.z = 10000;
}
}
particles.geometry.verticesNeedUpdate = true; // Necessary to update
renderer.render(scene, camera);
requestAnimationFrame(draw);
};
これで完成です!
以上が、ユーザーのWebカメラを使ったオーディオビジュアライザーを作るプロセスです。複雑そうに思えますが、コードと仕組みは意外にシンプルですよね。
今回はTHREE.Geometry
と THREE.PointsMaterial
を使いましたが、デモ2ではシェーダーを使っています。
ぜひこのチュートリアルを参考に、オリジナルの作品を作ってみてください。
(執筆:Takemoto Ryota 翻訳:Nakajima Asuka 編集:内田一良 a.k.a. じきるう)
Three.jsでパーティクルアニメーションを作ってみよう
Workship MAGAZINE
チュートリアルをなぞるだけ!Three.jsでインタラクティブエフェクトを作ろう
Workship MAGAZINE