ショートストーリー:視覚を持った人工生命の夢
あるプログラマが新たな挑戦に取り組んでいた。名前は翔太。
ある日、翔太は画期的なアイデアを思いつく。それは、視覚を持った人工生命体を創り出す関数を作成することだった。この人工生命体は、周囲の環境をリアルタイムで解析し、視覚的に表現できる能力を持つことを目指していた。
翔太は自分のプログラムに情熱を注ぎ込み、カラフルなポリゴンを描く関数を設計した。この関数は、Webカメラからの映像をもとに、周囲の明るさや色を解析し、動的な形状を生成するものだった。プログラムが進化するにつれて、彼の人工生命体は自らの「視覚」を持ち、周囲の環境に応じて変化する能力を持ち始めた。
ある晩、翔太は自宅の小さな作業部屋でプログラムを実行していた。彼のパソコンの画面には、鮮やかな色彩を持つポリゴンが浮かび上がり、まるで生きているかのように動き回っていた。彼はこの瞬間を待ち望んでいた。ポリゴンが、周囲の明るさや動きに応じて、まるで自己意識を持っているかのように反応している姿に、彼は心を奪われた。
コードをメモ帳などのテキストエディタに貼り付け、ファイル名を「index.html」として保存します。その後、保存したファイルをブラウザで開けば、コードが実行されます。
Webカメラの映像データをパラメータとして関数内に組み込んでいます。つまり目を持っているということです。
ユーチューブの動画を見せてます。
明るさの合計を計算し、それを使ってアニメーションのスピードを調整してます。明るさが高いとスピードが速く、逆に明るさが低いとスピードが遅くなります。
参考文献。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生物的な動きを伴うWebカメラアニメーション</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
<!-- 変数の数をスライダーで選択 -->
<div>
<label for="numVariablesSlider">変数の数 (1-10): </label>
<input type="range" id="numVariablesSlider" min="1" max="10" value="2">
<span id="numVariablesValue">2</span> <!-- 初期値表示 -->
</div>
<!-- 解像度(エッジ数)をスライダーで選択 -->
<div>
<label for="resolutionSlider">エッジの数(解像度): </label>
<input type="range" id="resolutionSlider" min="5" max="100" value="20">
<span id="resolutionValue">20</span> <!-- 初期解像度を表示 -->
</div>
<script>
let t = 0; // 時間を管理する変数
let resolution = 20; // ポリゴンの解像度
let numVariables = 2; // 使用する動きの変数の数
let webcam; // Webカメラ映像用
let camWidth = 80; // Webカメラの映像幅
let camHeight = 60; // Webカメラの映像高さ
let numGridPoints = 8; // 8x8グリッドを使用(64点)
function setup() {
// キャンバスを800x800のWEBGLモードで作成
createCanvas(800, 800, WEBGL);
// Webカメラ映像を取得し、サイズを設定
webcam = createCapture(VIDEO);
webcam.size(camWidth, camHeight);
webcam.hide(); // 映像は直接表示せずに手動で処理
// 変数の数をスライダーで設定
let numVarSlider = select('#numVariablesSlider');
numVarSlider.input(() => {
numVariables = numVarSlider.value();
select('#numVariablesValue').html(numVariables);
});
// 解像度(エッジ数)をスライダーで設定
let resSlider = select('#resolutionSlider');
resSlider.input(() => {
resolution = resSlider.value();
select('#resolutionValue').html(resolution);
});
}
function draw() {
background(255); // 背景を白に設定
// Webカメラの映像を表示(左上に配置)
push();
translate(-width / 2 + 100, -height / 2 + 100, 0); // 映像を左上に移動
texture(webcam); // Webカメラ映像をテクスチャとして使用
plane(160, 120); // 映像を平面に描画
pop();
// マウスでカメラの視点を操作可能にする
orbitControl();
// 回転角度を設定して、カメラの動きを微調整
rotateX(-PI / 6 + sin(t) * 0.1);
rotateZ(PI / 4 + cos(t) * 0.1);
let scaleFactor = 100; // 描画スケールの調整
let xStart = -2; // x軸の開始位置
let xEnd = 2; // x軸の終了位置
let yStart = -2; // y軸の開始位置
let yEnd = 2; // y軸の終了位置
// Webカメラ映像のピクセルデータを取得
webcam.loadPixels(); // ピクセルデータを読み込む
let gridBrightness = []; // グリッドごとの明るさを格納する配列
let totalBrightness = 0; // 明るさの合計を初期化
// 8x8のグリッドでカメラ映像をサンプリングし、各点の明るさを計算
for (let y = 0; y < numGridPoints; y++) {
for (let x = 0; x < numGridPoints; x++) {
let index = ((y * Math.floor(camHeight / numGridPoints)) * camWidth + (x * Math.floor(camWidth / numGridPoints))) * 4;
let r = webcam.pixels[index]; // 赤成分
let g = webcam.pixels[index + 1]; // 緑成分
let b = webcam.pixels[index + 2]; // 青成分
let brightness = (r + g + b) / 3; // 平均して明るさを算出
gridBrightness.push(brightness); // 明るさを配列に追加
totalBrightness += brightness; // 合計明るさを計算
}
}
// 明るさの平均を求めてスピード調整に使用
let averageBrightness = totalBrightness / gridBrightness.length;
let speedFactor = map(averageBrightness, 0, 255, 0.01, 0.2); // 明るさに基づいてスピードを調整
// 動きのための変数を生成
let variables = [];
for (let i = 0; i < numVariables - 1; i++) {
// Webカメラの明るさによって変数に動きを追加
let brightnessFactor = gridBrightness[i % gridBrightness.length] / 255;
variables.push(sin(t * (i + 1)) * brightnessFactor); // 動きをスケール
}
variables.push(cos(t) * gridBrightness[0] / 255); // もう一つの変数を設定
// ポリゴンを描画(TRIANGLE_STRIPで描画)
for (let x = xStart; x <= xEnd; x += (xEnd - xStart) / resolution) {
beginShape(TRIANGLE_STRIP);
for (let y = yStart; y <= yEnd; y += (yEnd - yStart) / resolution) {
// 変数に基づいて高さを計算
let fValue1 = f(x, y, ...variables);
let fValue2 = f(x + (xEnd - xStart) / resolution, y, ...variables);
// 高さに基づいて色を計算
let color1 = getColor(fValue1);
let color2 = getColor(fValue2);
// 頂点を描画
fill(color1);
vertex(x * scaleFactor, y * scaleFactor, fValue1 * scaleFactor);
fill(color2);
vertex((x + (xEnd - xStart) / resolution) * scaleFactor, y * scaleFactor, fValue2 * scaleFactor);
}
endShape();
}
t += speedFactor; // 明るさに基づいて時間を進める
}
// 複数の変数に基づいて高さを計算する関数
function f(x, y, ...vars) {
let result = 1;
// すべての変数を掛け合わせて複雑な動きを生成
for (let v of vars) {
result *= v;
}
// 時間と座標によって動的に変わる関数値を計算
return sin(x * y * result + t * 0.5) + cos(x * y * result + t * 0.5);
}
// 高さに基づいて色を生成する関数
function getColor(value) {
let normalizedValue = map(value, -2, 2, 0, 255); // 値を0-255の範囲にマップ
return color(normalizedValue, 100, 255 - normalizedValue); // 明るさに基づいて色を設定
}
</script>
</body>
</html>
解説
Webカメラのピクセルを64点にサンプリング:
Webカメラのピクセルを8x8グリッドに分割し、各グリッドごとの明るさを計算することで、より複雑でランダム性のある動きを生成します。この明るさを動きの変数に取り入れることで、生物的な動きを実現しています。
複数の変数を使用した動的なポリゴン描画:
関数f(x, y, ...vars)では、変数の数に応じて複雑な動きを生成しています。Webカメラのデータに基づく変数を使用して、時間経過に応じて変化するダイナミックな動きを表現しています。
色の動的変更:
ポリゴンの高さに基づいて色を変えることで、動きがより視覚的に強調されるようにしました。これにより、動きと連動した色変化を実現しています。