第3回クリエイティブ・コーディング:シェーダーの魔法

第3回の主役は、シェーダー(GLSL)です。

「Shadertoy」という魔法の鏡

AD – 読み進める前のひとやすみ

シェーダーの世界は数学と物理の塊、一からすべてを理解しようとすると、入り口で立ち往生してしまいます。
そこで登場するのが、世界中の天才たちが魔法の数式を公開している聖地 Shadertoy

Shadertoy とは?:ブラウザ上で直接 GLSL(シェーダーコード)を書き、その結果をリアルタイムで確認できるサイト。 天才たちが作った魔法のコードが、公開されています。

Shadertoy からの「翻訳マニュアル」

AD – 読み進める前のひとやすみ

Shadertoy のコードはそのままコピペしても動きません。なぜなら、Shadertoy 独自の「方言」で書かれているからです。ここでは、標準的なp5.jsで動くようにするための翻訳ルールを整理。

基本の書き換えルール表

役割 Shadertoy p5.js (WEBGL)
魔法の入り口 void mainImage(…) void main()
最終的な色 fragColor gl_FragColor
経過時間 iTime uTime
画面の解像度 iResolution uResolution
画面の座標 fragCoord.xy / iResolution.xy vTexCoord
💡 ここがハマりポイント!
p5.jsでシェーダーを書く時は、ファイルの1行目に必ず precision highp float; と書きます。

AI を「専属翻訳家」にして効率アップ!

自力で書き換えるのも勉強になりますが、この翻訳作業こそ AI の得意分野です。

💡 AI へのプロンプト例(p5.js版)

「以下の Shadertoy のコードを、p5.js の WEBGL モードで動くように翻訳してください。
JavaScript 内の定数(バッククォート)として vertfrag の変数に格納する形式にし、uTimeuResolutionvTexCoord を使うように調整してください。初心者にも分かりやすいように日本語でコメントを入れてください。」

(ここに Shadertoy のコードを貼り付ける)

GLSLの基礎:JavaScriptとは違う「魔法の国のルール」

AD – 読み進める前のひとやすみ

シェーダーを書くための専用言語 GLSL (OpenGL Shading Language) の仕組み。

JavaScriptの常識が通用しないこの世界には、独自のルールと「癖」が存在します。

頂点とフラグメント:二人の専属魔法使い

シェーダーの舞台裏では、役割の違う二人の担当者がチームを組んで働いている。

📐 頂点シェーダー (Vertex Shader)

担当:場所と形(Where)

3Dモデルの「点」が画面のどこにあるかを決める数学担当。「画面いっぱいに、この板(Plane)を広げておいて!」と指示する舞台設営の役割。

🎨 フラグメントシェーダー (Fragment Shader)

担当:色(Color)

ピクセルの一つひとつに「お前はこの色になれ!」と命じる色彩担当。Shadertoyで私たちが書くコードの正体はこれ。 私たちが目指す「色の魔法使い」の本体。

💡Shadertoyでは、最初から「画面いっぱいに板を広げる」という頂点シェーダーの仕事が終わった状態からスタート、面倒な準備を飛ばして、いきなり「色の計算(フラグメントシェーダー)」だけに集中できるキャンバスです。

初心者がハマる「3つの癖」

GLSLには、JavaScriptに慣れた人ほど驚くような独特の「癖」があります。

  • 💧 「.0」を忘れると即エラー(小数の呪い)
    JSなら 11.0 も同じだが、GLSLは型に超厳しい。小数を扱う時は必ず 1.0 と書くこと。整数と小数を混ぜて計算するだけで、魔法はエラーで止まってしまう。
  • 🧘 隣のピクセルに相談できない(孤独なピクセル)
    「左のピクセルが赤だから自分も……」という相談は一切できない。全ピクセルが同時に、孤独に、自分の色だけを計算している。この「超並列処理」こそが、GPUのパワーの秘密。
  • 🕰️ 昨日のことは覚えていない(一期一会)
    前のフレームの値を保存して使い回すことはできない。常に「今の時間(iTime)」と「今の場所(座標)」というヒントだけを頼りに、毎フレームゼロから色を計算し直している。

実践編

AD – 読み進める前のひとやすみ

1ファイル完結テンプレート

/**
 * クリエイティブ・コーディング 第2回 実践編
 * 「1ファイルで完結!シェーダー召喚テンプレート」
 */

// --- 1. 頂点シェーダー (Vertex Shader) ---
// 画面の座標をそのままフラグメントシェーダーに渡す「おまじない」。
// 最小限の構成。varyingを使わず、キャンバスの設営だけに集中します。
const vert = `
precision highp float;
attribute vec3 aPosition;

void main() {
  vec4 positionVec4 = vec4(aPosition, 1.0);
  // p5.jsの平面を画面いっぱいに広げる
  positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
  gl_Position = positionVec4;
}`;

// --- 2. フラグメントシェーダー (Fragment Shader) ---
// ★ここを書き換える!
const frag = `
precision highp float;
uniform vec2 uResolution;
uniform float uTime;

void main() {
  // 【ここがポイント】
  // gl_FragCoord(ピクセル位置)から -1.0 〜 1.0 の座標を作ります。
  // 短い方の辺を基準にするため min() を使ってアスペクト比を補正します。
  vec2 uv = (gl_FragCoord.xy * 2.0 - uResolution.xy) / min(uResolution.x, uResolution.y);

  vec3 col = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0, 2, 4));
  
  gl_FragColor = vec4(col, 1.0);
}`;
// ★ここまで

let myShader;

function setup() {
  // WEBGLモード。pixelDensity(1)を入れると計算がズレにくくなります。
  createCanvas(windowWidth, windowHeight, WEBGL);
  pixelDensity(1); 
  
  myShader = createShader(vert, frag);
  noStroke();
}

function draw() {
  shader(myShader);

  // シェーダーに現在の「画面サイズ」と「時間」を教えてあげる
  myShader.setUniform('uResolution', [width, height]);
  myShader.setUniform('uTime', millis() / 1000.0);

  // キャンバスとなる四角形。中心原点なのでこの範囲で画面全体を覆います。
  rect(-width / 2, -height / 2, width, height);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

⚠️貼り付ける時の注意!

シェーダーを書くとき、コードを ` `(バッククォート)で囲みます。これは JavaScript の「テンプレートリテラル」という機能です。

Shadertoy のコードをコピーしてきたとき、貼り付ける場所は大きく分けて 2 箇所あります。

// — 1. ここは「関数の置き場」 —
// Shadertoy で mainImage の「外」に書いてある関数はここに貼る

void main() {
  // — 2. ここは「メインロジックの置き場」 —
  // mainImage の「中身」はここに書き込む
  gl_FragColor = …
}

万華鏡エフェクトでシェーダーの真髄に触れる

フラグメントシェーダーの部分をこちらに書き換えてみてください。
kishimisuさんのYoutubeで詳しく解説されているものです。

// --- 2. フラグメントシェーダー (Fragment Shader) ---
const frag = `
precision highp float;
uniform vec2 uResolution;
uniform float uTime;

vec3 palette(float t) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.000, 0.333, 0.667);
    return a + b * cos(6.28318 * (c * t + d));
}

void main() {
    vec2 uv = (gl_FragCoord.xy * 2.0 - uResolution.xy) / min(uResolution.x, uResolution.y);

    vec2 uv0 = uv;
    vec3 finalColor = vec3(0.0);

    for (int i = 0; i < 4; i++) {
        float fi = float(i); 
        uv = fract(uv * 1.5) - 0.5;
        float d = length(uv) * exp(-length(uv0));
        vec3 col = palette(length(uv0) + fi * 0.4 + uTime * 0.4);
        d = sin(d * 8.0 + uTime) / 8.0;
        d = abs(d);
        d = pow(0.01 / d, 1.2);
        finalColor += col * d;
    }
    gl_FragColor = vec4(finalColor, 1.0);
}`;

平面から「空間」へ。

AD – 読み進める前のひとやすみ

今の私たちはまだ「1 枚の板」の上に絵を描いてる状態。
もし、この魔法の質感を、Blender で作った 3D キャラクターに纏わせたいとしたら?
あるいは、奥行きのある宇宙空間をカメラで自由自在に飛び回りたいとしたら?

そんな「平面」を超えた「空間」の魔法が必要になったとき
舞台はp5.jsからはなれて、Three.js (React Three Fiber) に移すのがベストだと思います。

第4回では、 React Three Fiber を使って、Shadertoy の芸術的なコードを自分のプロジェクトへと繋ぎ込む『クリエイティブ・コーディング』に挑戦します!

最後まで読んでいただき、ありがとうございました。
おつかれさまでした 🍵

スポンサーリンク