ショートストーリー: 「驚くほど短いコードの魔法」
東京の喧騒が少し落ち着いた夕方、プログラマーの悠斗(ゆうと)はコーヒーを片手に、自宅のデスクに向かっていた。彼は新しいウェブアプリの開発に夢中で、特に最近興味を持っていたのはシェーダーという特殊なプログラミング技術だった。映像に豊かな表現力を加えられるこの技術、そしてGPUを直接操作できるこの技術を、どうにかして簡単に扱えないかと試行錯誤していた。
「シェーダーって、すごく難しいんだよな…もっとシンプルにできたらいいのに。」
悠斗はデスクトップに散らばった複雑なシェーダーコードを見ながら溜息をついた。そのとき、彼の頭に浮かんだのは、最近触り始めたp5.jsというJavaScriptのライブラリだった。
「p5.jsなら、もっとシンプルに書けるかも?」
試しに、彼はエディタを開き、p5.jsを使ったシェーダーのコードを書き始めた。あまり期待はしていなかったが、驚くべきことに、ほんの数行で動き始める美しいグラフィックスが画面に広がった。
function setup() {
createCanvas(400, 400, WEBGL);
noStroke();
}
function draw() {
background(0);
rotateY(millis() / 1000);
sphere(150);
}
たったこれだけのコードで、3Dの球体がゆっくりと回転しながら美しく描画されている。悠斗は驚いた。これまで何日もかけて複雑なコードを調整していたシェーダーが、p5.jsではまるで魔法のように短いコードで動いていた。
「信じられない…こんなに簡単にできるなんて。」
彼は興奮し、さらなる実験を始めた。p5.jsでシェーダーコードを呼び出し、色彩や形状を変化させる。ほんの少しのコードを加えるだけで、次々と美しいビジュアルが生成されていった。
let myShader;
function setup() {
createCanvas(400, 400, WEBGL);
noStroke();
let vertexShader = `
attribute vec3 aPosition;
void main() {
gl_Position = vec4(aPosition, 1.0);
}
`;
let fragmentShader = `
precision mediump float;
void main() {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
`;
myShader = createShader(vertexShader, fragmentShader);
}
function draw() {
shader(myShader);
rotateY(millis() / 1000);
sphere(150);
}
悠斗の画面には、青く輝く回転する球体が映し出されていた。複雑に思えていたシェーダーが、p5.jsの力でシンプルに、そして直感的に実装できたのだ。
「p5.jsって、本当にすごい。シェーダーがこんなに短いコードで書けるなんて…。」
悠斗はその瞬間、自分が新しい何かを手に入れたことを感じた。プログラマーとして、難解な技術を簡単に扱える力を。そしてそれは、東京という巨大な街の一角で、小さなデスクに座る一人のプログラマーの新たな冒険の始まりだった。
その夜、彼は新しいアイデアが次々と湧いてきて、夢中でキーボードを叩き続けた。
p5.jsとシェーダを使ってカラフルなアニメーションを作成するためのシンプルなサンプルコードを紹介します。この例では、カラフルな波模様がアニメーションとして表示され、時間とマウスの位置に応じて色や形が変化します。
カラフルなアニメーションのサンプルコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.js + シェーダーによるカラフルなアニメーション</title>
<!-- p5.jsライブラリのインポート -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
<main>
<h1>カラフルな波模様のアニメーション</h1>
<p>時間とマウスの位置に応じて色が変わるカラフルなアニメーション。</p>
</main>
<script>
let myShader;
function setup() {
// WEBGLモードでキャンバスを作成
createCanvas(400, 400, WEBGL);
noStroke(); // 描画に輪郭線をつけない
// 頂点シェーダー
let vertexShader = `
attribute vec3 aPosition;
uniform mat4 uProjectionMatrix;
uniform mat4 uModelViewMatrix;
uniform float uTime;
varying vec2 vTexCoord;
void main() {
vTexCoord = aPosition.xy;
vec4 position = vec4(aPosition, 1.0);
gl_Position = uProjectionMatrix * uModelViewMatrix * position;
}
`;
// フラグメントシェーダー
let fragmentShader = `
precision mediump float;
uniform float uTime;
uniform vec2 uResolution;
varying vec2 vTexCoord;
void main() {
// マウス位置と解像度に基づいてカラーを生成
vec2 st = gl_FragCoord.xy / uResolution.xy;
vec3 color = vec3(0.0);
// 時間と位置に基づいて波模様を生成
float wave = sin(10.0 * (st.x + uTime)) + sin(10.0 * (st.y + uTime));
color.r = 0.5 + 0.5 * sin(uTime + st.x * 5.0);
color.g = 0.5 + 0.5 * cos(uTime + st.y * 5.0);
color.b = 0.5 + 0.5 * sin(uTime + wave * 2.0);
gl_FragColor = vec4(color, 1.0);
}
`;
// シェーダーを作成
myShader = createShader(vertexShader, fragmentShader);
// シェーダーを適用
shader(myShader);
}
function draw() {
// シェーダーに時間と解像度の情報を渡す
myShader.setUniform('uTime', millis() / 1000.0);
myShader.setUniform('uResolution', [width, height]);
// 平面を描画(フルスクリーン)
rect(-width / 2, -height / 2, width, height);
}
</script>
</body>
</html>
解説:
頂点シェーダー:
ここではシンプルに頂点の位置を指定しているだけで、aPosition(各頂点の位置)を利用して頂点を描画します。
フラグメントシェーダー:
uTimeは時間の変数で、アニメーション効果を出すために使います。
uResolutionは画面の解像度です。これを使って、描画する位置に応じた色の変化を作ります。
波模様を生成し、時間と共に変化するカラフルな効果を生み出しています。赤、緑、青の各成分が時間や座標に応じて変化します。
シェーダーの適用:
createShader()で頂点シェーダーとフラグメントシェーダーを作成し、shader()関数で適用しています。
rect()関数でフルスクリーンの四角形を描画しており、画面全体にカラフルなアニメーションを表示します。
このコードを実行すると:
カラフルな波模様が時間とともに変化し、マウスの位置に応じて色の変化が見られます。
p5.jsを使って、ワイヤーフレームのオブジェクトを描画するサンプルコードを紹介します。ここでは3Dオブジェクト(球体)のワイヤーフレームを回転させるアニメーションを作成します。
ワイヤーフレームのサンプルコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.jsによるワイヤーフレームの球体アニメーション</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
<main>
<h1>ワイヤーフレームの球体アニメーション</h1>
</main>
<script>
function setup() {
// WEBGLモードでキャンバスを作成
createCanvas(400, 400, WEBGL);
// ストローク(輪郭線)を有効にして、ワイヤーフレーム表示を可能にする
noFill(); // 中身は塗りつぶさない
stroke(255); // ワイヤーフレームの色を白に設定
}
function draw() {
background(0); // 背景を黒に設定
// Y軸回りの回転アニメーション
rotateY(millis() / 1000);
// 球体を描画(ワイヤーフレーム表示)
sphere(150); // 半径150の球体
}
</script>
</body>
</html>
解説:
noFill():
ワイヤーフレーム表示にするために、オブジェクトの中身を塗りつぶさない設定です。
stroke(255):
球体の輪郭線(ワイヤーフレーム)の色を白に設定します。255はRGBカラーで白を表します。
rotateY():
millis()関数を使って、経過時間に基づいたY軸回転を行います。これにより球体がゆっくりと回転します。
sphere():
半径150の球体を描画します。この球体はワイヤーフレーム表示になります。
このコードを実行すると:
黒い背景に白いワイヤーフレームの球体が描画され、球体がY軸を中心にゆっくりと回転するアニメーションが表示されます。
p5.jsでシェーダーを使用して形状を変化させるサンプルコードを紹介します。この例では、時間に応じて球体の形状が変化します。具体的には、サイン波を使って球体の表面を変形させます。
シェーダーコードを使った形状変化のサンプル
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.js + シェーダーによる形状変化アニメーション</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
<main>
<h1>シェーダーによる形状変化のアニメーション</h1>
</main>
<script>
let myShader;
function setup() {
createCanvas(400, 400, WEBGL);
noStroke();
// 頂点シェーダー
const vertexShader = `
attribute vec3 aPosition;
uniform float uTime;
varying vec3 vPosition;
void main() {
float offset = sin(aPosition.x * 5.0 + uTime * 2.0) * 0.2; // サイン波で変形
vec3 pos = vec3(aPosition.x, aPosition.y + offset, aPosition.z);
vPosition = pos;
gl_Position = vec4(pos, 1.0);
}
`;
// フラグメントシェーダー
const fragmentShader = `
precision mediump float;
varying vec3 vPosition;
void main() {
// 簡単なグラデーション色を設定
float colorValue = vPosition.y * 0.5 + 0.5; // Y位置に基づく色
gl_FragColor = vec4(colorValue, 0.5, 1.0 - colorValue, 1.0); // RGBA
}
`;
myShader = createShader(vertexShader, fragmentShader);
}
function draw() {
background(0);
shader(myShader); // シェーダーを適用
myShader.setUniform('uTime', millis() / 1000.0); // 時間をシェーダーに渡す
// 球体を描画
sphere(150);
}
</script>
</body>
</html>
解説:
頂点シェーダー:
uTimeを使用して、サイン波によって球体のY座標を変化させます。これにより、上下に波打つような変形が実現されます。
フラグメントシェーダー:
Y位置に基づいて色を設定します。Y座標の値を使って色のグラデーションを作成しています。
シェーダーの適用:
createShader()でシェーダーを作成し、shader()関数で適用します。
setUniform():
uTimeに経過時間を渡すことで、アニメーションを実現しています。
このコードを実行すると:
球体が上下に波打つように変形し、色もY座標に応じて変化します。時間の経過に伴って形状と色がダイナミックに変わるアニメーションが楽しめます。
別のシェーダーコードのサンプル。色彩の変化と波の動きを持つ球体のアニメーションを作成します。
シェーダーを使った色彩変化のサンプルコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.js + カラフルな波動球体アニメーション</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
<main>
<h1>シェーダーによるカラフルな波動球体のアニメーション</h1>
</main>
<script>
let myShader;
function setup() {
createCanvas(400, 400, WEBGL);
noStroke();
// 頂点シェーダー
const vertexShader = `
attribute vec3 aPosition;
uniform float uTime;
varying vec3 vPosition;
void main() {
float wave = sin(aPosition.x * 5.0 + uTime * 3.0) * 0.1; // 波の動き
vec3 pos = aPosition + vec3(0.0, wave, 0.0); // Y軸方向に波を加える
vPosition = pos;
gl_Position = vec4(pos, 1.0);
}
`;
// フラグメントシェーダー
const fragmentShader = `
precision mediump float;
varying vec3 vPosition;
void main() {
// 色をY位置に基づいて変化させる
float r = 0.5 + 0.5 * sin(vPosition.y * 5.0);
float g = 0.5 + 0.5 * sin(vPosition.y * 5.0 + 2.0);
float b = 0.5 + 0.5 * sin(vPosition.y * 5.0 + 4.0);
gl_FragColor = vec4(r, g, b, 1.0); // RGBA
}
`;
myShader = createShader(vertexShader, fragmentShader);
}
function draw() {
background(0);
myShader.setUniform('uTime', millis() / 1000.0); // 時間をシェーダーに渡す
// シェーダーを適用して球体を描画
shader(myShader);
sphere(150); // 半径150の球体を描画
}
</script>
</body>
</html>
解説:
シェーダーの設定:
頂点シェーダー: Y軸方向にサイン波を追加して球体の形状を変化させます。uTimeに基づいて波の動きがアニメーションします。
フラグメントシェーダー: Y位置に基づいて色を変化させ、赤、緑、青の値をサイン波で設定しています。
draw関数:
背景を黒に設定し、シェーダーを適用して球体を描画します。uTimeを使ってアニメーションを実現しています。
参考。
基本的には p5.js の方が Three.js と比べて短いコードで同様の表現が可能です。これにはいくつかの理由があります:
シンプルなAPI:
p5.jsは、芸術的表現やクリエイティブコーディングに特化して設計されており、3D描画の基本的な要素(例えば、図形や色の変化など)を簡単に扱えるような関数が用意されています。setup()やdraw()のような基本関数を使うだけで、自動的にフレームを描画するループが組み込まれています。
高レベルの抽象化:
p5.jsは3D描画の処理をWebGLベースで行いますが、内部処理を抽象化していて、開発者は詳細を意識せずに3D表現ができます。例えば、sphere()やrotateX()、rotateY()といったシンプルな関数を呼び出すだけで、3Dの図形描画や回転が行えるようになっています。
Three.jsは強力な機能が多い:
Three.jsはより複雑な3Dシーンの構築に強力で、例えば光源や影、物理シミュレーション、カメラの高度な制御など、より細かい制御が可能です。その分、設定や処理のコードが長くなる傾向があります。Three.jsでは、ジオメトリ、マテリアル、シーン、カメラなどを細かく定義する必要があります。
比較の例:
Three.jsでは、scene、camera、rendererなどを手動で設定し、パーティクルの位置や色の属性をBufferGeometryを使って定義する必要があります。
p5.jsでは、createCanvas()、rotateX()、sphere()など、簡潔な関数呼び出しで同様の結果が得られます。
したがって、簡単な3D表現やアニメーションを作成する場合は、p5.jsの方がシンプルで短いコードで済むことが多いです。ただし、より複雑な3Dシーンや高性能なレンダリングを必要とする場合、Three.jsの方が適しています。
p5.jsは、非常に多くの関数を提供しており、2Dや3Dのグラフィックス、インタラクティブなアート、アニメーション、データビジュアライゼーションなど、幅広い用途で使うことができます。ここでは、p5.jsで利用できる主要な関数のカテゴリを紹介し、それぞれの代表的な関数をリストします。また、Perlinノイズの関数もありますので、それについても詳しく説明します。
主なp5.jsの関数カテゴリ
描画(Drawing)
line(x1, y1, x2, y2) - 線を描画
rect(x, y, w, h) - 矩形を描画
ellipse(x, y, w, h) - 楕円を描画
background(r, g, b) - 背景を設定
fill(r, g, b) - 塗りつぶしの色を設定
stroke(r, g, b) - 線の色を設定
noStroke() - 線を非表示に
beginShape() / endShape() - カスタムシェイプの描画
3D描画(3D Shapes)
box(size) - 立方体を描画
sphere(radius) - 球体を描画
rotateX(angle) / rotateY(angle) / rotateZ(angle) - 3D回転
translate(x, y, z) - 位置の移動
色(Color)
color(r, g, b) - 色を作成
lerpColor(c1, c2, amt) - 色の補間
alpha(c) - 色のアルファ値を取得
入力(Input)
mouseX / mouseY - マウスの現在位置
keyIsPressed - キーが押されているか確認
mousePressed() - マウスがクリックされた時の動作を定義
keyPressed() - キーが押された時の動作を定義
数学(Math)
sin(angle) / cos(angle) / tan(angle) - 三角関数
dist(x1, y1, x2, y2) - 2点間の距離
map(value, start1, stop1, start2, stop2) - 値の範囲を別の範囲にマッピング
random(min, max) - ランダムな数値を生成
noise(x, y, z) - パーリンノイズ(後述)
norm(value, start, stop) - 値を範囲内で正規化
時間(Time)
millis() - プログラム開始からの経過時間をミリ秒単位で取得
frameCount - フレーム数をカウント
画像(Images)
loadImage(path) - 画像をロード
image(img, x, y, w, h) - 画像を描画
tint(r, g, b) - 画像に色の効果を適用
アニメーション(Animation)
frameRate(fps) - フレームレートを設定
loop() / noLoop() - アニメーションのループを制御
delay(time) - 指定した時間だけ処理を遅延させる
フォントとテキスト(Fonts and Text)
text(str, x, y) - テキストを描画
textSize(size) - テキストのサイズを設定
loadFont(path) - フォントをロード
サウンド(Sound) (p5.sound.jsライブラリを使用する必要があります)
loadSound(path) - サウンドをロード
play() - サウンドを再生
setVolume(value) - ボリュームを設定
パーリンノイズ(Perlin Noise)
p5.jsには、パーリンノイズを生成するための組み込み関数があります。パーリンノイズは、より自然なランダムな動きを作り出すために使われ、雲、地形、波などの表現に広く利用されています。
noise(x, [y], [z])
1次元、2次元、3次元のパーリンノイズ値を返します。x、y、zに渡す座標に応じて滑らかなノイズ値が得られます。
function setup() {
createCanvas(400, 400);
noLoop(); // 描画を一度だけ行う
}
function draw() {
background(255);
noFill();
beginShape();
for (let x = 0; x < width; x++) {
let y = noise(x * 0.01) * height; // xに基づいてパーリンノイズを計算
vertex(x, y); // ノイズに基づいて頂点を設定
}
endShape();
}
この例では、noise(x * 0.01)を使用して、滑らかなノイズに基づく波線を生成しています。
noiseDetail(lod, falloff)
パーリンノイズの精度(レベルオブディテール)や減衰率を制御します。これにより、ノイズの細かさや変動の幅を調整できます。
noiseSeed(seed)
ノイズのシード値を設定します。同じシードを使うと、同じノイズパターンが再現されます。
まとめ
p5.jsは、非常に簡潔なコードで2Dや3Dのグラフィックス、アニメーション、インタラクティブなアートを作成するための強力なツールです。特に、パーリンノイズのような自然なランダム性を持つアニメーションや表現を行う際に便利な関数が揃っています。
p5.js でも シェーダー を使用することが可能です。p5.jsはWebGLベースのレンダリングをサポートしており、シェーダーコード(GLSL)を使用してカスタムなレンダリング効果を適用できます。p5.jsでは、createShader() 関数を使ってシェーダーを作成し、それをキャンバス上に適用することができます。
p5.jsには、頂点シェーダーとフラグメントシェーダー(ピクセルシェーダー)の両方をサポートする仕組みが組み込まれています。つまり、GLSLシェーダーコードをp5.jsの中で直接使えるということです。