mcpField
S
shader-glsl

shader-glsl

github.com/iart-aiクリエイティブ・メディア451.5k 回インストール18 週間前に更新
リポジトリ

shader-glsl について

生成ビジュアル、画像トランジション、ポストプロセス向けの GLSL フラグメントシェーダー作成を支援するスキルです。シェーダー技法、Three.js の設定、性能上の注意点に加え、再現可能なフレームの描画・確認手順を扱います。

主な機能

  1. 01アニメーション勾配・ノイズ・パターン
  2. 02SDF 形状とアンチエイリアス処理
  3. 03ディゾルブ・変位・グリッチのトランジション
  4. 04Three.js ShaderMaterial の設定
  5. 05モバイルとシェーダーの性能指針

活用シーン

  1. 01アニメーション背景の作成
  2. 02画像トランジションの構築
  3. 03ポストプロセス効果の追加
  4. 04固定時刻でのシェーダーフレーム確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/iart-ai/webgl-animation-skills --skill shader-glsl

インストール後、Claude Code で/shader-glslを入力して読み込まれたか確認します。

同梱ファイルREADME.mdreferences/glsl-cookbook.md

詳細

シェーダー/GLSL

ジェネレーティブな動き、グラデーション、トランジション、ポストプロセス用の GPU フラグメントシェーダーを作成します。フラグメントシェーダーはピクセルごとに並列で実行されるため、全画面のジェネレーティブな動きを実現する最も高性能な方法です。

使用する場面

  • アニメーションするグラデーション、ノイズ、プラズマ、オーロラの背景。
  • 画像トランジション:ディゾルブ、ディスプレイスメント、グリッチ、リップル、ワイプ。
  • 歪み、色収差、ジェネレーティブパターン、SDF シェイプ。
  • レンダリング済みのシーンに適用するポストプロセスパス。

フラグメントシェーダーの基本形

すべてのフラグメントシェーダーは、1 ピクセル分の色を計算します。座標を正規化し、アスペクト比を補正してから、色を組み立てます。

precision highp float;
uniform float u_time;
uniform vec2  u_resolution;
uniform vec2  u_mouse;

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution.xy;     // 0..1
  vec2 p  = uv * 2.0 - 1.0;                         // -1..1, centered
  p.x *= u_resolution.x / u_resolution.y;           // aspect-correct
  vec3 col = 0.5 + 0.5 * cos(u_time + p.xyx + vec3(0.0, 2.0, 4.0));
  gl_FragColor = vec4(col, 1.0);
}

上記のコサインパレットの行(Inigo Quilez のパレット)は、見栄えのよいアニメーショングラデーションを作る最速の方法です。調整可能な a,b,c,d の vec3 を使った a + b*cos(2π*(c*t + d)) です。

基本的な構成要素

smoothstep + mix は基本となる関数です。smoothstep(e0, e1, x) は滑らかな 0→1 のランプを返し、mix(a, b, t) は線形にブレンドします。1 ピクセル分の幅でエッジをアンチエイリアスします:

float px = fwidth(d);                    // screen-space derivative
float mask = smoothstep(px, -px, d);     // crisp AA edge from SDF distance d

ハッシュ + バリューノイズ(テクスチャは不要です):

float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
float noise(vec2 p){
  vec2 i = floor(p), f = fract(p);
  vec2 u = f * f * (3.0 - 2.0 * f);                // smooth interpolation
  return mix(mix(hash(i), hash(i + vec2(1,0)), u.x),
             mix(hash(i + vec2(0,1)), hash(i + vec2(1,1)), u.x), u.y);
}
float fbm(vec2 p){                                  // fractal noise, organic
  float v = 0.0, a = 0.5;
  for (int i = 0; i < 5; i++){ v += a * noise(p); p *= 2.0; a *= 0.5; }
  return v;
}

完全なバリューノイズ/シンプレックスノイズと fbm のバリエーションは references/glsl-cookbook.md にあります。

SDF シェイプを使うと、解像度に依存しない鮮明なジオメトリを作成できます。距離は内側で負、外側で正になります:

float sdCircle(vec2 p, float r){ return length(p) - r; }
float sdBox(vec2 p, vec2 b){ vec2 d = abs(p) - b; return length(max(d,0.0)) + min(max(d.x,d.y),0.0); }
// render: float m = smoothstep(fwidth(d), -fwidth(d), d);

ドメインワーピングで流動的な大理石模様を作成できます。ノイズをノイズに入力します:

vec2 q = vec2(fbm(p), fbm(p + vec2(5.2, 1.3)));
float n = fbm(p + 4.0 * q + u_time * 0.1);

画像トランジション

2 つのテクスチャをサンプリングし、進行度を示す uniform u_progress(0→1)でピクセルごとにブレンドします。

ディゾルブ/ノイズワイプ — ノイズをしきい値処理して表示します:

float n = noise(uv * 20.0);
float edge = smoothstep(u_progress - 0.05, u_progress, n);
gl_FragColor = mix(texture2D(tex0, uv), texture2D(tex1, uv), 1.0 - edge);

ディスプレイスメント — サンプリング前にディスプレイスメントマップを使って UV をずらします:

float disp = texture2D(dispTex, uv).r;
vec2 d0 = uv + vec2(disp * u_progress * 0.3, 0.0);
vec2 d1 = uv - vec2(disp * (1.0 - u_progress) * 0.3, 0.0);
gl_FragColor = mix(texture2D(tex0, d0), texture2D(tex1, d1), u_progress);

グリッチ — 時間に応じて行をブロック単位でずらし、RGB チャンネルを分割します(色収差):

float row = floor(uv.y * 20.0);
float shift = (hash(vec2(row, floor(u_time * 12.0))) - 0.5) * 0.1 * u_glitch;
vec2 g = uv + vec2(shift, 0.0);
vec3 c;
c.r = texture2D(tex, g + vec2(0.005, 0.0)).r;       // channel offset
c.g = texture2D(tex, g).g;
c.b = texture2D(tex, g - vec2(0.005, 0.0)).b;
gl_FragColor = vec4(c, 1.0);

Three.js ShaderMaterial の接続

import * as THREE from 'three';
const uniforms = {
  u_time:       { value: 0 },
  u_resolution: { value: new THREE.Vector2(innerWidth, innerHeight) },
  u_mouse:      { value: new THREE.Vector2(0, 0) },
};
const material = new THREE.ShaderMaterial({
  uniforms,
  vertexShader: `void main(){ gl_Position = vec4(position, 1.0); }`,
  fragmentShader: FRAG_SRC,            // your GLSL string
});
// Full-screen triangle/quad: a plane that covers clip space.
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
const scene = new THREE.Scene(); scene.add(mesh);
const camera = new THREE.Camera();   // no projection needed for clip-space quad
const renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);

const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
  uniforms.u_time.value = clock.getElapsedTime();
  renderer.render(scene, camera);
});
addEventListener('resize', () => {
  renderer.setSize(innerWidth, innerHeight);
  uniforms.u_resolution.value.set(innerWidth, innerHeight);
});

プレーンなカメラを使った全画面パスでは、上記のように頂点シェーダーで position を直接出力し、投影処理を省略します。実際のジオメトリに適用するシェーダーでは、頂点シェーダーから varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } を使って vUv を渡します。

モバイル/パフォーマンス

  • highp が不要な場合は、モバイルで precision mediump float; を宣言します。一部のエフェクト(大きな座標、深い fbm)には highp が必要です。
  • ピクセル比を制限します:renderer.setPixelRatio(Math.min(devicePixelRatio, 2))。負荷の高いシェーダーでは、低解像度のターゲットにレンダリングしてから拡大します。
  • GLSL ES ではループの境界値を定数にする必要があります。動的なループ回数は使えません。fbm のオクターブ数は 5~6 以下にします。
  • 負荷の高い処理では if/分岐を避け、mix/step/smoothstep を優先します。texture2D の呼び出しを最小限にし、可能な場合は依存するテクスチャ読み取りを避けます。
  • WebGL2/GLSL ES 3.00 では texelFetch、整数演算、textureLod が使えます。#version 300 es を宣言し、in/out/fragColor を使用します。

提供と検証(単体 HTML)

パッケージ化されたヘルパー(scripts/):scripts/seek-shot.sh anim.html 0 1.5 3 は ?t=N ハーネスを使って各時点の描画を固定し、スクリーンショットを撮影します。scripts/contact-sheet.sh sheet.png frame-*.png は、それらをタイル状に並べて一目で確認できるようにします。scripts/README.md を参照してください。

シェーダーを自己完結させる場合(グラデーション/ノイズ背景、トランジション、ジェネレーティブなループ)、成果物は ブラウザーで直接開ける 1 つの HTML ファイルです。CDN から importmap 経由で Three.js を読み込み、全画面のクワッドを 1 つ、レンダーループを 1 つ用意し、ビルド工程は不要です。シェーダーには単一ファイルが適切な規模です。1 つのファイルで済むなら、バンドラーを使う必要はありません。

出力要件:

  • .html ファイルを 1 つ用意します。importmap で three の CDN を指定し、GLSL 文字列、ShaderMaterial、全画面のクワッド、レンダーループを 1 つのインライン <script type="module"> に記述します。
  • シェーダーは uniform の純粋関数にします。すべてを u_time(トランジションでは u_progress も)で制御します。アニメーションはすべて、固定できる 1 つの uniform を通じて実行します。
  • シェーダー内の乱数は、すでに決定論的な hash(uv) によって生成されています。フレームごとのシード設定は不要です。u_time の外で壁時計の時刻を与えないでください。

シーク/固定ハーネス — スクリーンショット用に、固定時刻で 1 フレームを描画します。 ?t=N は u_time(必要に応じて u_progress も)を N に設定し、1 フレームを描画してループを停止します。これにより、決定論的な静止画が得られます。

<script type="module">
  // ... uniforms, material, full-screen quad, renderer ...
  const t = new URLSearchParams(location.search).get("t");
  function frame(time) {
    uniforms.u_time.value = time;
    uniforms.u_progress && (uniforms.u_progress.value = Math.min(time, 1)); // transitions
    renderer.render(scene, camera);
  }
  if (t !== null) {
    frame(parseFloat(t));            // one fixed frame, no loop
    window.__ready = true;
  } else {
    const clock = new THREE.Clock();
    renderer.setAnimationLoop(() => frame(clock.getElapsedTime()));
  }
</script>

検証ループ — 描画 → 固定 → スクリーンショット → 確認: 開始・中間・終了の 3 時点(?t=0、?t=<mid>、?t=<end>。トランジションでは u_progress を 0/0.5/1 に設定)で開き、それぞれスクリーンショットを撮影して、忠実度(要件との一致)とアーティファクトの両方を確認します。黒い/空白のキャンバスは、シェーダーのコンパイルエラーまたは解析エラーです(GLSL のログはコンソールで確認してください)。バンディング、NaN による異常描画(負の値に対する pow/log による白やゴミのようなピクセル)、トランジション用テクスチャの欠落(CDN/アセットの 404)も確認します。WebGL には GPU コンテキストが必要ですが、ヘッドレス環境では Playwright/Chromium がそれを提供します(swiftshader)。

npx playwright screenshot --wait-for-timeout=600 "file://$PWD/shader.html?t=2.0" frame-mid.png

完了前に確認すること:

  1. キャンバスが描画されること。黒や空白ではなく、シェーダーのコンパイルエラーやコンソールエラーがなく、CDN の 404 もないこと。
  2. ?t=N で再現可能なフレームが固定されること(同じ N なら同じピクセルになり、時計は u_time だけであること)。
  3. 開始/中間/終了(または進行度 0/0.5/1)をスクリーンショットで確認し、要件に一致していて、バンディング/NaN/黒い表示がないこと。
  4. SPA に埋め込む場合は、リソースが破棄され、リークがないこと(material.dispose()、geometry.dispose()、renderer.dispose() を呼び出し、ループを停止すること)。
  5. prefers-reduced-motion に対応すること。動きが装飾的な場合は、u_time を固定するか、アニメーションを遅くします。

クイックリファレンス

目的プリミティブ
アニメーションするグラデーションコサインパレット a + b*cos(...)
有機的なテクスチャfbm(uv * scale + time)
くっきりした形状SDF + smoothstep(fwidth(d), -fwidth(d), d)
流体/マーブルドメインワープ:ノイズをノイズに入力
リビールトランジションノイズのしきい値と u_progress を比較
グリッチ行ハッシュのシフト + RGB チャンネルのオフセット
アンチエイリアスのエッジ画面空間の幅に fwidth(d) を使用

参考ファイル

  • references/glsl-cookbook.md — value noise と simplex noise、および fbm の完全な実装、IQ コサインパレットのレシピ、ブーリアン演算と角の丸めに対応した SDF 形状ライブラリ一式、ドメインワーピング、3 種類の画像トランジション(ディゾルブ/ディスプレイスメント/グリッチ)の完全なシェーダー、Three.js の uniform/テクスチャの配線、GLSL ES 3.00 への移行、モバイル環境での精度に関する注意点を収録しています。

コメント