glsl について
JARVIS AI Assistant HUD向けのリアルタイム視覚効果を開発するためのGLSLシェーダースキルです。シェーダーのパターン、GPU性能、安全性、テスト方法を扱います。
主な機能
- 01ホログラフィックパネルとエネルギーフィールドのパターン
- 02データ可視化シェーダーの例
- 03GPU性能最適化の指針
- 04シェーダーとビジュアル回帰テスト
- 05GPU安全性の実践方法
活用シーン
- 01アニメーションHUDパネルやエネルギー効果の作成
- 02カスタムシェーダーによるデータ可視化
- 03シェーダー出力と性能のテスト
- 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 基本原則
- まず TDD:実装前に、ビジュアル回帰テストとシェーダーの単体テストを作成します
- パフォーマンスを意識する:GPU パフォーマンスをプロファイリングし、60 FPS を目標に最適化します
- 精度を重視する:パフォーマンスに適した精度修飾子を使用します
- 分岐を避ける:GPU の効率を高めるため、シェーダー内の条件分岐を最小限に抑えます
- 計算を最適化する:組み込み関数を使用し、コストの高い演算を避けます
- ユニフォームの安全性を確保する:GPU に送信する前にユニフォーム入力を検証します
- ループの上限を固定する:GPU のハングを防ぐため、常に定数のループ上限を使用します
- メモリアクセスを最適化する:テクスチャ参照と 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.00 | WebGL 2.0 | 最新機能、より高い精度 |
| GLSL ES 1.00 | WebGL 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 の視覚効果を支えています。
- TDD を最優先:シェーダーを書く前に、コンパイル、ユニフォーム、視覚的リグレッションのテストを作成します
- パフォーマンス:分岐のないパターン、テクスチャアトラス、LOD、精度の最適化を使用します
- 安全性:ループの上限を定数にし、除算を保護します
- テスト:対象ブラウザー全体で検証し、GPU のパフォーマンスをベンチマークします
注意:シェーダーは GPU 上で実行されるため、たった 1 つの不具合のあるシェーダーがシステム全体をフリーズさせる可能性があります。
参考資料:
references/advanced-patterns.md- 複雑なシェーダー技法
コメント
Markdown 対応