JA EN

WebGL で実現するリアルタイム画像エフェクト - シェーダー入門から実践まで

· 約 9 分で読めます

WebGL による画像処理の基本概念 - GPU の並列処理能力を活用する

WebGL は Web ブラウザから GPU (Graphics Processing Unit) にアクセスするための API です。画像処理において WebGL が強力なのは、GPU の大規模並列処理能力を活用できる点にあります。CPU が逐次的にピクセルを処理するのに対し、GPU は数千のコアで同時に複数ピクセルを処理できるため、リアルタイムのエフェクト適用が可能になります。

Canvas 2D API との性能差: 1,920 × 1,080の画像にガウシアンぼかしを適用する場合、Canvas 2D API (CPU) では 50-200ms かかる処理が、WebGL (GPU) では 1-5ms で完了します。この 10-100 倍の速度差が、60fps のリアルタイムエフェクトを実現する鍵です。

WebGL 画像処理のパイプライン:

フラグメントシェーダーは GLSL (OpenGL Shading Language) で記述します。各ピクセルに対して独立に実行されるため、隣接ピクセルの情報が必要なぼかし処理でも、テクスチャサンプリングで周囲のピクセル値を参照できます。WebGL 2.0 では GLSL ES 3.0 が使用可能で、整数演算やテクスチャ配列など、より高度な機能が利用できます。

WebGL の初期セットアップ - テクスチャ描画の最小構成

WebGL で画像エフェクトを実装するための最小限のセットアップコードを示します。頂点シェーダーは画面全体を覆うクワッドを描画し、フラグメントシェーダーでエフェクトを適用する構成です。

頂点シェーダー (vertex shader):

attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}

基本フラグメントシェーダー (パススルー):

precision mediump float;
uniform sampler2D u_image;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}

JavaScript 側の初期化: WebGL コンテキストの取得、シェーダーのコンパイル・リンク、テクスチャのアップロード、頂点バッファの設定が必要です。ボイラープレートが多いため、実務では twgl.js (WebGL ユーティリティライブラリ、gzip 後 12KB) や regl を使うと記述量を大幅に削減できます。

const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
// シェーダーコンパイル、プログラムリンク、テクスチャ設定...

テクスチャのアップロードでは、gl.texImage2D に HTMLImageElement を直接渡せます。NPOT (Non-Power-Of-Two) テクスチャも WebGL 2.0 では制限なく使用可能です。画像サイズが大きい場合は、gl.texSubImage2D で部分更新することでメモリ効率を改善できます。

色調補正エフェクト - 明るさ、コントラスト、彩度の調整

最も基本的な画像エフェクトは色調補正です。各ピクセルの色値を数学的に変換するだけなので、テクスチャサンプリングは 1 回で済み、非常に高速に動作します。

明るさ (Brightness) 調整:

uniform float u_brightness; // -1.0 ~ 1.0
vec4 color = texture2D(u_image, v_texCoord);
gl_FragColor = vec4(color.rgb + u_brightness, color.a);

コントラスト (Contrast) 調整:

uniform float u_contrast; // 0.0 ~ 2.0 (1.0 が元の状態)
vec4 color = texture2D(u_image, v_texCoord);
vec3 adjusted = (color.rgb - 0.5) * u_contrast + 0.5;
gl_FragColor = vec4(adjusted, color.a);

彩度 (Saturation) 調整: RGB をグレースケール値と混合することで彩度を制御します。

uniform float u_saturation; // 0.0 (グレー) ~ 2.0 (過飽和)
vec4 color = texture2D(u_image, v_texCoord);
float gray = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
vec3 adjusted = mix(vec3(gray), color.rgb, u_saturation);
gl_FragColor = vec4(adjusted, color.a);

これらのエフェクトを組み合わせることで、Instagram のようなフィルタ効果を実現できます。セピア調、ヴィンテージ風、ハイコントラストなど、カラーマトリクス変換で多彩な表現が可能です。uniform 変数をスライダー UI と連動させれば、ユーザーがリアルタイムに調整できるインタラクティブな画像エディタが構築できます。

ぼかしエフェクト - ガウシアンブラーの効率的な実装

ガウシアンぼかしは画像処理で最も頻繁に使われるエフェクトの一つですが、カーネルサイズが大きくなるとテクスチャサンプリング回数が急増するため、効率的な実装が重要です。

ナイーブな実装の問題: 半径 r のガウシアンぼかしは (2r+1)x(2r+1) のカーネルを使用します。半径 10 の場合、1 ピクセルあたり 441 回のテクスチャサンプリングが必要で、1,920 × 1,080の画像では約 9 億回のサンプリングになります。

2 パス分離フィルタ: ガウシアンカーネルは分離可能 (separable) なので、水平方向と垂直方向の 2 パスに分割できます。これにより、サンプリング回数が (2r+1)^2 から 2*(2r+1) に削減されます。半径 10 の場合、441 回が 42 回に減少します。

// 水平パス
uniform vec2 u_direction; // vec2(1.0/width, 0.0)
vec4 sum = vec4(0.0);
for (int i = -RADIUS; i <= RADIUS; i++) {
float weight = gaussian(float(i), u_sigma);
sum += texture2D(u_image, v_texCoord + u_direction * float(i)) * weight;
}
gl_FragColor = sum;

リニアサンプリング最適化: GPU のバイリニアフィルタリングを活用し、隣接する 2 つのサンプルを 1 回のテクスチャフェッチで取得するテクニックがあります。これにより、サンプリング回数をさらに半分に削減できます。半径 10 の場合、実質 11 回のフェッチで済みます。

マルチパスダウンサンプリング: 大きなぼかし半径が必要な場合、画像を段階的に縮小してからぼかしを適用し、再度拡大する方法が効率的です。Kawase blur や Dual blur アルゴリズムはこの原理を応用しており、ゲームエンジンで広く使われています。

歪みエフェクト - UV 座標の操作による視覚効果

歪み (Distortion) エフェクトは、テクスチャ座標 (UV 座標) を数学的に変換することで実現します。ピクセルの色自体は変更せず、「どの位置のピクセルを読み取るか」を変えることで、波紋、渦巻き、魚眼レンズなどの効果を生み出します。

波紋 (Ripple) エフェクト:

uniform float u_time;
uniform float u_amplitude; // 0.01 ~ 0.05
uniform float u_frequency; // 10.0 ~ 30.0
vec2 uv = v_texCoord;
float dist = distance(uv, vec2(0.5));
uv += normalize(uv - vec2(0.5)) * sin(dist * u_frequency - u_time) * u_amplitude;
gl_FragColor = texture2D(u_image, uv);

渦巻き (Swirl) エフェクト:

uniform float u_angle; // 回転角度
uniform float u_radius; // 効果範囲
vec2 center = vec2(0.5);
vec2 uv = v_texCoord - center;
float dist = length(uv);
float factor = smoothstep(u_radius, 0.0, dist);
float angle = factor * u_angle;
uv = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)) * uv;
gl_FragColor = texture2D(u_image, uv + center);

魚眼レンズ (Fisheye) エフェクト: 中心からの距離に応じて座標を非線形に変換します。pow(dist, 2.0) で二次関数的な歪みを加えると、レンズの光学特性に近い効果が得られます。

歪みエフェクトでは、変換後の UV 座標が 0.0-1.0 の範囲外になる場合があります。gl_CLAMP_TO_EDGE テクスチャラッピングモードを設定するか、範囲外を透明にする処理を追加してください。u_time uniform を requestAnimationFrame で更新すれば、アニメーションする歪みエフェクトが実現できます。

パフォーマンス最適化とライブラリ活用 - 実務での WebGL 画像処理

WebGL 画像処理を本番環境で使用する際のパフォーマンス最適化と、開発効率を高めるライブラリの活用方法を紹介します。

パフォーマンス最適化のポイント:

推奨ライブラリ:

フォールバック戦略: WebGL が利用できない環境 (古いブラウザ、GPU ドライバの問題) では、CSS フィルタや Canvas 2D API にフォールバックする設計が必要です。canvas.getContext('webgl2') || canvas.getContext('webgl') で対応状況を確認し、非対応の場合は filter: blur() brightness() などの CSS フィルタで代替します。

関連記事

Canvas API 応用テクニック - フィルター、合成、ピクセル操作の実践

HTML5 Canvas API の応用テクニックを解説。カスタムフィルター、合成モード、ピクセル単位の画像操作など、ブラウザ上での高度な画像処理を実装します。

ブラウザでの画像処理の仕組み - Canvas API、ImageData、Web Workers 活用ガイド

ブラウザ内で画像処理を行う技術的な仕組みを解説。Canvas API によるピクセル操作、ImageData の構造、Web Workers によるオフスレッド処理、OffscreenCanvas の活用方法を紹介します。

ゲーム開発における画像フォーマット選定 - テクスチャ圧縮と描画パフォーマンスの最適化

ゲーム開発で使用される画像フォーマット (DDS, KTX2, ASTC, BC7) の特性を比較。GPU テクスチャ圧縮の仕組みとプラットフォーム別の最適な選択肢を解説します。

WebAssembly で高速画像処理を実現する - Wasm による画像変換とフィルタ適用

WebAssembly を活用したブラウザ内高速画像処理の実装方法を解説。Rust/C++ から Wasm へのコンパイル、Canvas API との連携、パフォーマンス比較を具体的なコード例で紹介します。

テクスチャ合成のアルゴリズムと応用 - パッチベースから深層学習まで

テクスチャ合成の主要アルゴリズムを網羅的に解説。パッチベース手法、Gram 行列による統計的手法、GAN ベースの最新技術まで原理と実装を詳しく紹介します。

画像に枠線と影を追加する方法 - CSS とツールで実現するテクニック

画像に枠線やドロップシャドウを追加する方法を CSS 実装からデザインツールまで網羅的に解説。見栄えの良い画像表現を実現しましょう。

関連用語