mcpField
G
glsl

glsl

github.com/martinholovskyデザイン・UI48370 回インストール47 週間前に更新
リポジトリ

glsl について

JARVIS AI Assistant HUD向けのリアルタイム視覚効果を開発するためのGLSLシェーダースキルです。シェーダーのパターン、GPU性能、安全性、テスト方法を扱います。

主な機能

  1. 01ホログラフィックパネルとエネルギーフィールドのパターン
  2. 02データ可視化シェーダーの例
  3. 03GPU性能最適化の指針
  4. 04シェーダーとビジュアル回帰テスト
  5. 05GPU安全性の実践方法

活用シーン

  1. 01アニメーションHUDパネルやエネルギー効果の作成
  2. 02カスタムシェーダーによるデータ可視化
  3. 03シェーダー出力と性能のテスト
  4. 04GPU停止や描画アーティファクトの回避

インストール

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

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill glsl

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

同梱ファイルreferences/advanced-patterns.md

詳細

GLSL シェーダープログラミングスキル

ファイル構成:このスキルは分割構成を採用しています。高度なシェーダーパターンについては、references/ を参照してください。

1. 概要

このスキルでは、JARVIS AI Assistant HUD でホログラフィックな視覚効果を作成するための GLSL シェーダーの専門知識を提供します。リアルタイムレンダリングに向けた効率的な GPU プログラミングに重点を置いています。

リスクレベル:低 — GPU 側のコードで攻撃対象領域は限られていますが、パフォーマンスの問題が発生する可能性があります。

主な用途:

  • スキャンラインを使ったホログラフィックパネル効果
  • アニメーション付きエネルギーフィールドとパーティクルシステム
  • カスタムレンダリングによるデータの可視化
  • ポストプロセス効果(ブルーム、グリッチ、色収差)

2. 主な責務

2.1 基本原則
  1. まず TDD:実装前に、ビジュアル回帰テストとシェーダーの単体テストを作成します
  2. パフォーマンスを意識する:GPU パフォーマンスをプロファイリングし、60 FPS を目標に最適化します
  3. 精度を重視する:パフォーマンスに適した精度修飾子を使用します
  4. 分岐を避ける:GPU の効率を高めるため、シェーダー内の条件分岐を最小限に抑えます
  5. 計算を最適化する:組み込み関数を使用し、コストの高い演算を避けます
  6. ユニフォームの安全性を確保する:GPU に送信する前にユニフォーム入力を検証します
  7. ループの上限を固定する:GPU のハングを防ぐため、常に定数のループ上限を使用します
  8. メモリアクセスを最適化する:テクスチャ参照と varying の補間を最適化します

3. 実装ワークフロー(TDD)

3.1 ステップ 1:まず失敗するテストを書く
// tests/shaders/holographic-panel.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { WebGLTestContext, captureFramebuffer, compareImages } from '../utils/webgl-test'

describe('HolographicPanelShader', () => {
  let ctx: WebGLTestContext

  beforeEach(() => {
    ctx = new WebGLTestContext(256, 256)
  })

  // Unit test: Shader compiles
  it('should compile without errors', () => {
    const shader = ctx.compileShader(holoFragSource, ctx.gl.FRAGMENT_SHADER)
    expect(shader).not.toBeNull()
    expect(ctx.getShaderErrors()).toEqual([])
  })

  // Unit test: Uniforms are accessible
  it('should have required uniforms', () => {
    const program = ctx.createProgram(vertSource, holoFragSource)
    expect(ctx.getUniformLocation(program, 'uTime')).not.toBeNull()
    expect(ctx.getUniformLocation(program, 'uColor')).not.toBeNull()
    expect(ctx.getUniformLocation(program, 'uOpacity')).not.toBeNull()
  })

  // Visual regression test
  it('should render scanlines correctly', async () => {
    ctx.renderShader(holoFragSource, { uTime: 0, uColor: [0, 0.5, 1], uOpacity: 1 })
    const result = captureFramebuffer(ctx)
    const baseline = await loadBaseline('holographic-scanlines.png')
    expect(compareImages(result, baseline, { threshold: 0.01 })).toBeLessThan(0.01)
  })

  // Edge case test
  it('should handle extreme UV values', () => {
    const testCases = [
      { uv: [0, 0], expected: 'no crash' },
      { uv: [1, 1], expected: 'no crash' },
      { uv: [0.5, 0.5], expected: 'no crash' }
    ]
    testCases.forEach(({ uv }) => {
      expect(() => ctx.renderAtUV(holoFragSource, uv)).not.toThrow()
    })
  })
})
3.2 ステップ 2:テストを通すための最小限の実装を行う
// Start with minimal shader that passes tests
#version 300 es
precision highp float;

uniform float uTime;
uniform vec3 uColor;
uniform float uOpacity;

in vec2 vUv;
out vec4 fragColor;

void main() {
  // Minimal implementation to pass compilation test
  fragColor = vec4(uColor, uOpacity);
}
3.3 ステップ 3:完全な実装でリファクタリングする
// Expand to full implementation after tests pass
void main() {
  vec2 uv = vUv;
  float scanline = sin(uv.y * 100.0) * 0.1 + 0.9;
  float pulse = sin(uTime * 2.0) * 0.1 + 0.9;
  vec3 color = uColor * scanline * pulse;
  fragColor = vec4(color, uOpacity);
}
3.4 ステップ 4:完全な検証を実行する
# Run all shader tests
npm run test:shaders

# Visual regression tests
npm run test:visual -- --update-snapshots  # First time only
npm run test:visual

# Performance benchmark
npm run bench:shaders

# Cross-browser compilation check
npm run test:webgl-compat

4. 技術スタックとバージョン

4.1 GLSL のバージョン
バージョンコンテキスト機能
GLSL ES 3.00WebGL 2.0最新機能、より高い精度
GLSL ES 1.00WebGL 1.0レガシーサポート
4.2 シェーダーのセットアップ
#version 300 es
precision highp float;
precision highp int;

// WebGL 2.0 shader header

5. パフォーマンスパターン

5.1 分岐を避ける — Mix/Step を使う
// ❌ BAD - GPU branch divergence
vec3 getColor(float value) {
  if (value < 0.3) {
    return vec3(1.0, 0.0, 0.0);  // Red
  } else if (value < 0.7) {
    return vec3(1.0, 1.0, 0.0);  // Yellow
  } else {
    return vec3(0.0, 1.0, 0.0);  // Green
  }
}

// ✅ GOOD - Branchless with mix/step
vec3 getColor(float value) {
  vec3 red = vec3(1.0, 0.0, 0.0);
  vec3 yellow = vec3(1.0, 1.0, 0.0);
  vec3 green = vec3(0.0, 1.0, 0.0);

  vec3 color = mix(red, yellow, smoothstep(0.3, 0.31, value));
  color = mix(color, green, smoothstep(0.7, 0.71, value));
  return color;
}
5.2 テクスチャアトラス — ドローコールを削減する
// ❌ BAD - Multiple texture bindings
uniform sampler2D uIcon1;
uniform sampler2D uIcon2;
uniform sampler2D uIcon3;

vec4 getIcon(int id) {
  if (id == 0) return texture(uIcon1, vUv);
  if (id == 1) return texture(uIcon2, vUv);
  return texture(uIcon3, vUv);
}

// ✅ GOOD - Single atlas texture
uniform sampler2D uIconAtlas;
uniform vec4 uAtlasOffsets[3];  // [x, y, width, height] for each icon

vec4 getIcon(int id) {
  vec4 offset = uAtlasOffsets[id];
  vec2 atlasUV = offset.xy + vUv * offset.zw;
  return texture(uIconAtlas, atlasUV);
}
5.3 詳細度(LOD)— 距離に応じた品質調整
// ❌ BAD - Same quality regardless of distance
const int NOISE_OCTAVES = 8;

float noise(vec3 p) {
  float result = 0.0;
  for (int i = 0; i < NOISE_OCTAVES; i++) {
    result += snoise(p * pow(2.0, float(i)));
  }
  return result;
}

// ✅ GOOD - Reduce octaves based on distance
uniform float uCameraDistance;

float noise(vec3 p) {
  // Fewer octaves when far away (detail not visible)
  int octaves = int(mix(2.0, 8.0, 1.0 - smoothstep(10.0, 100.0, uCameraDistance)));
  float result = 0.0;
  for (int i = 0; i < 8; i++) {
    if (i >= octaves) break;
    result += snoise(p * pow(2.0, float(i)));
  }
  return result;
}
5.4 ユニフォームのバッチ処理 — CPU と GPU 間の転送を最小限に抑える
// ❌ BAD - Many individual uniforms
uniform float uPosX;
uniform float uPosY;
uniform float uPosZ;
uniform float uRotX;
uniform float uRotY;
uniform float uRotZ;
uniform float uScaleX;
uniform float uScaleY;
uniform float uScaleZ;

// ✅ GOOD - Packed into vectors/matrices
uniform vec3 uPosition;
uniform vec3 uRotation;
uniform vec3 uScale;
// Or even better:
uniform mat4 uTransform;
5.5 精度の最適化 — 適切な精度を使用する
// ❌ BAD - Everything highp (wastes GPU cycles)
precision highp float;

highp vec3 color;
highp float alpha;
highp vec2 uv;

// ✅ GOOD - Match precision to data needs
precision highp float;  // Default for calculations

mediump vec3 color;     // 0-1 range, mediump sufficient
mediump float alpha;    // 0-1 range
highp vec2 uv;          // Need precision for texture coords
lowp int flags;         // Boolean-like values
5.6 テクスチャ参照をキャッシュする
// ❌ BAD - Redundant texture fetches
void main() {
  vec3 diffuse = texture(uTexture, vUv).rgb;
  // ... some code ...
  float alpha = texture(uTexture, vUv).a;  // Same lookup!
  // ... more code ...
  vec3 doubled = texture(uTexture, vUv).rgb * 2.0;  // Again!
}

// ✅ GOOD - Cache the result
void main() {
  vec4 texSample = texture(uTexture, vUv);
  vec3 diffuse = texSample.rgb;
  float alpha = texSample.a;
  vec3 doubled = texSample.rgb * 2.0;
}

6. 実装パターン

6.1 ホログラフィックパネルシェーダー
// shaders/holographic-panel.frag
#version 300 es
precision highp float;

uniform float uTime;
uniform vec3 uColor;
uniform float uOpacity;
uniform vec2 uResolution;

in vec2 vUv;
out vec4 fragColor;

const int SCANLINE_COUNT = 50;

void main() {
  vec2 uv = vUv;

  // Scanline effect
  float scanline = 0.0;
  for (int i = 0; i < SCANLINE_COUNT; i++) {
    float y = float(i) / float(SCANLINE_COUNT);
    scanline += smoothstep(0.0, 0.002, abs(uv.y - y));
  }
  scanline = 1.0 - scanline * 0.3;

  // Edge glow
  float edge = 1.0 - smoothstep(0.0, 0.05, min(
    min(uv.x, 1.0 - uv.x),
    min(uv.y, 1.0 - uv.y)
  ));

  // Animated pulse
  float pulse = sin(uTime * 2.0) * 0.1 + 0.9;

  vec3 color = uColor * scanline * pulse;
  color += vec3(0.0, 0.5, 1.0) * edge * 0.5;

  fragColor = vec4(color, uOpacity);
}
6.2 エネルギーフィールドシェーダー
// shaders/energy-field.frag
#version 300 es
precision highp float;

uniform float uTime;
uniform vec3 uColor;

in vec2 vUv;
in vec3 vNormal;
in vec3 vViewPosition;
out vec4 fragColor;

float snoise(vec3 v) {
  return fract(sin(dot(v, vec3(12.9898, 78.233, 45.543))) * 43758.5453);
}

void main() {
  vec3 viewDir = normalize(-vViewPosition);
  float fresnel = pow(1.0 - abs(dot(viewDir, vNormal)), 3.0);
  float noise = snoise(vec3(vUv * 5.0, uTime * 0.5));

  vec3 color = uColor * fresnel;
  color += uColor * noise * 0.2;
  float alpha = fresnel * 0.8 + noise * 0.1;

  fragColor = vec4(color, alpha);
}
6.3 データ可視化シェーダー
// shaders/data-bar.frag
#version 300 es
precision highp float;

uniform float uValue;
uniform float uThreshold;
uniform vec3 uColorLow;
uniform vec3 uColorHigh;
uniform vec3 uColorWarning;

in vec2 vUv;
out vec4 fragColor;

void main() {
  float fill = step(vUv.x, uValue);
  vec3 color = mix(uColorLow, uColorHigh, uValue);
  color = mix(color, uColorWarning, step(uThreshold, uValue));
  float gradient = vUv.y * 0.3 + 0.7;
  fragColor = vec4(color * gradient * fill, fill);
}

7. セキュリティとパフォーマンスの基準

7.1 GPU の安全性
リスク対策
無限ループ常に定数のループ上限を使用する
GPU のハングまず小さなデータセットでシェーダーをテストする
メモリ枯渇テクスチャサイズを制限する
7.2 ループの安全なパターン
// ❌ BAD - Dynamic loop bound
for (int i = 0; i < int(uCount); i++) { }

// ✅ GOOD - Constant loop bound
const int MAX_ITERATIONS = 100;
for (int i = 0; i < MAX_ITERATIONS; i++) {
  if (i >= int(uCount)) break;
}

8. よくある間違いとアンチパターン

8.1 禁止:動的なループ上限を使用する
// ❌ DANGEROUS - May cause GPU hang
for (int i = 0; i < uniformValue; i++) { }

// ✅ SAFE - Constant bound with early exit
const int MAX = 100;
for (int i = 0; i < MAX; i++) {
  if (i >= uniformValue) break;
}
8.2 禁止:ゼロかどうかを確認せずに除算する
// ❌ DANGEROUS - Division by zero
float result = value / divisor;

// ✅ SAFE - Guard against zero
float result = value / max(divisor, 0.0001);

9. 実装前チェックリスト

フェーズ 1:コードを書く前
  • シェーダーのコンパイルテストを書く
  • ユニフォームのアクセス可能性をテストする
  • ビジュアル回帰テスト用のベースライン画像を作成する
  • パフォーマンス目標(FPS、ドローコール)を定義する
  • 再利用可能なパターンがないか既存のシェーダーを確認する
フェーズ 2:実装中
  • すべてのループで定数の上限を使用する
  • ゼロ除算が発生しないようにする
  • 分岐のないパターン(mix/step)を使用する
  • 適切な精度修飾子を使用する
  • テクスチャ参照をキャッシュする
  • ユニフォームをベクトルや行列にまとめる
フェーズ 3:コミット前
  • すべてのシェーダーテストに合格する:npm run test:shaders
  • ビジュアル回帰テストに合格する:npm run test:visual
  • パフォーマンスベンチマークが目標を満たす:npm run bench:shaders
  • クロスブラウザー互換性を確認する
  • エッジケース(UV 0,0 および 1,1)でアーティファクトがない
  • アニメーションのタイミングが滑らかであることを確認する

10. まとめ

GLSL シェーダーは、JARVIS HUD の視覚効果を支えています。

  1. TDD を最優先:シェーダーを書く前に、コンパイル、ユニフォーム、視覚的リグレッションのテストを作成します
  2. パフォーマンス:分岐のないパターン、テクスチャアトラス、LOD、精度の最適化を使用します
  3. 安全性:ループの上限を定数にし、除算を保護します
  4. テスト:対象ブラウザー全体で検証し、GPU のパフォーマンスをベンチマークします

注意:シェーダーは GPU 上で実行されるため、たった 1 つの不具合のあるシェーダーがシステム全体をフリーズさせる可能性があります。


参考資料:

  • references/advanced-patterns.md - 複雑なシェーダー技法

コメント