WebGL2 とは何か
このサイトでは、three.js のような抽象化レイヤーを使わず、ブラウザのWebGL2RenderingContextと GLSL ES 3.00 を直接扱って、GPU による描画を土台から学びます。three.js ユーザーの視点で言えば、これまで renderer.render() の 1 行に隠れていた内側を、全部自分の手で書けるようになる旅です。
第1章はほとんどコードを書かない概念の章です。WebGL がどの層にいて、GPU が何をする装置で、WebGL の API がなぜあのような書き味なのか — 先に地図を持っておくと、第2章以降のすべてのコードの意味が変わります。
この章で学ぶこと:
- WebGL2 がソフトウェアスタックのどこにいるか(そして何をしてくれないか)
- GPU はなぜ速いのか — 「同じプログラムを大量のデータに同時適用する」並列モデル
- WebGL の API 設計の核: 状態機械 (state machine)
- WebGL1 / WebGL2、GLSL ES 1.00 / 3.00 のバージョン関係
1. WebGL2 の位置づけ
WebGL は、ブラウザ上の JavaScript から GPU を使って HTML の<canvas>に描画するための低レベル API です。独自に発明されたものではなく、 組み込み機器向けのグラフィックス API である OpenGL ESをブラウザに移植したもので、WebGL 2.0 は OpenGL ES 3.0 に対応します。 バッファ、シェーダー、テクスチャといった用語や API の設計は、この OpenGL の系譜から来ています。
ここで大事なのは、WebGL はエンジンではないということです。three.js が当たり前に提供していた概念 — シーン、カメラ、メッシュ、マテリアル、ライト、モデルローダー — は、WebGL には 1 つも存在しません。WebGL が提供するのは、おおよそ次の 3 つだけです。
- GPU のメモリにデータ(頂点、画像など)を置く手段
- GPU 上で動く小さなプログラム(シェーダー)をコンパイルして走らせる手段
- その結果を canvas のピクセルとして書き出す手段
カメラも 3D らしさも、この道具立ての上に「自分で計算して作る」ものです。逆に言えば、この道具を直接握ることが、 three.js では手が届かなかった表現(独自のパイプライン、GPU パーティクル、フィードバック系)への入口になります。
2. GPU はなぜ速いのか — 並列モデル
フル HD の画面は 1920 × 1080 = 約 207 万ピクセルです。これを毎秒 60 回描き換えるなら、 色の計算は毎秒約 1.2 億回必要になります。しかも 1 回の「色の計算」自体が、 後の章で扱うライティングやテクスチャ参照が入ると数十〜数百の演算になります。合計すると 毎秒数十億〜数百億の演算で、1 つずつ順番に処理する書き方では現実的に間に合いません。
GPU はこの問題を、「単純な演算器を数百〜数千個並べ、全部に同じプログラムを実行させる」という設計で解決します。1 個ずつは CPU のコアより単純で遅くても、数千個が同時に動けば総量で圧倒できます。
このモデルには、これからずっと付き合うことになる 2 つの帰結があります。
- シェーダーは「1 要素分」だけを書く— 画面全体をループで塗るコードは書きません。「1 つの頂点をどこに置くか」「1 つのピクセルを何色にするか」だけを書くと、GPU が全要素に適用してくれます。
- 各要素は互いの結果に依存できない— 実行順序が保証されず、他の要素の計算結果を読む手段も基本的にないため、「隣のピクセルの結果を見て自分の色を決める」ような書き方はできません(隣接ピクセルとの差分を取る組み込み関数など、限定的な例外はあります)。 この制約をどう乗り越えるかが、後半の面白いテーマになります(第29章のフィードバックなど)。
3. 役割分担 — CPU が段取り、GPU が描く
GPU は強力ですが、自分からは何もしません。何をどう描くかの段取りはすべて CPU 側、 つまりあなたが書く TypeScript の仕事です。毎フレーム、おおよそ次のことをします。
- 描きたい形のデータ(頂点など)を GPU のメモリへ転送しておく
- GPU で動かすシェーダーを渡してコンパイルさせておく
- 「どのデータを」「どのシェーダーで」描くかを設定する
- 「描け」と命令する(ドローコール)
ドローコールを受けた GPU 側では、頂点処理 → ピクセル分解 → 色計算という一連の流れ(描画パイプライン)が走ります。この中身は第3章で図解しながら全部手で書くので、いまは「CPU が材料と手順書を用意し、GPU が大量並列で実行する」という分担だけ覚えてください。
4. WebGL は状態機械
ここがこの章の核心で、three.js との書き味の違いが最も出るところです。WebGL の API は状態機械 (state machine)として設計されています。巨大なスイッチ盤を思い浮かべてください。
- ほとんどの API 呼び出しは、盤面のスイッチを設定するだけです。呼んだ瞬間には何も描かれません。
clearやdrawArraysのような実行系の命令が来たとき、その時点の盤面の設定に従って描画が行われます。
// 「クリアに使う色」という状態を設定するだけ。画面はまだ変わらない
gl.clearColor(0.25, 0.55, 0.85, 1.0);
// 実行系の命令。その時点で設定されている色で、描画バッファを塗りつぶす
gl.clear(gl.COLOR_BUFFER_BIT);clearColorに「実行」の意味はなく、clearの瞬間に盤面が読まれる — つまり呼び出しの順序が意味を持ちます。この構造は WebGL 全体を貫いていて、第3章では「いまバインドされているバッファ」「いま使用中のプログラム」 という、より重要なスイッチを操作することになります。
three.js の宣言的なスタイルと並べると、違いがはっきりします。
// three.js: 宣言的 — 「こういうシーンです」と伝えると、renderer が段取りしてくれる
scene.add(new THREE.Mesh(geometry, material));
renderer.render(scene, camera);
// 生の WebGL2: 命令的 — 状態を 1 つずつ設定してから「描け」と言う
gl.useProgram(program); // このシェーダーを使う、という状態
gl.bindVertexArray(vao); // この頂点の配線を使う、という状態
gl.drawArrays(gl.TRIANGLES, 0, 3); // 実行(第3章で全部書きます)three.js が「シーンの記述」を受け取って内部でスイッチ盤を適切に設定し直してくれていたのに対し、 生の WebGL ではその設定をすべて自分で並べます。デバッグの勘所も変わります。何かが描かれないとき、 原因の多くは「実行した瞬間の盤面が、思っていた状態と違う」ことです。
5. 最初の WebGL コード(全文)
冒頭のデモの全文です。canvas を見つけ、WebGL2 のコンテキスト(= gl オブジェクト。以降すべての API の入口)を取得し、一色でクリアしています。
// 第1章: WebGL2 とは何か
// この章のデモは「WebGL2 が動くことの確認」だけ。
// 使う API は、コンテキストの取得と、画面のクリアの 2 つ。
const canvas = document.querySelector<HTMLCanvasElement>('#demo');
if (!canvas) {
throw new Error('canvas 要素 #demo が見つかりません');
}
// WebGL2 の入口。ここで得られる gl オブジェクトに、以降すべての API が生えている。
// 対応していない環境では null が返る(取得の作法と引数は第2章)
const gl = canvas.getContext('webgl2');
if (!gl) {
// 対応していなければ、デモの figure ごとメッセージに差し替える
// (canvas だけ消すと figcaption の説明が宙に浮くため)
const message = document.createElement('p');
message.textContent = 'このブラウザは WebGL2 に対応していません。';
(canvas.closest('figure') ?? canvas).replaceWith(message);
} else {
// 状態機械の最初の一歩:
// clearColor は「クリアに使う色」という状態を設定するだけで、画面はまだ変わらない
gl.clearColor(0.25, 0.55, 0.85, 1.0);
// clear が実行されて初めて、その時点で設定されている色で描画バッファが塗りつぶされる
gl.clear(gl.COLOR_BUFFER_BIT);
}getContext('webgl2')は、対応していない環境ではnullを返すため、必ず分岐を書きます。もっとも、WebGL2 は 2017 年に Chrome / Firefox、2021 年の Safari 15 で有効化されており、現在のモダンブラウザではまず利用できます。 コンテキスト取得の詳しい作法(取得時に渡せるオプション、解像度の管理)は第2章で扱います。
6. WebGL のバージョンと GLSL
本サイトで扱うバージョンを整理しておきます。
| ベース | シェーダー言語 | 本サイト | |
|---|---|---|---|
| WebGL 1.0 (2011) | OpenGL ES 2.0 | GLSL ES 1.00 | 扱わない |
| WebGL 2.0 (2017) | OpenGL ES 3.0 | GLSL ES 3.00 | これのみ |
WebGL2 では、VAO(第3章)・多彩なテクスチャ形式・インスタンシング・Transform Feedback など、表現の幅に直結する機能が標準になり、シェーダー言語も現代的になりました。 注意したいのは、ウェブ上の WebGL 記事の多くが WebGL1 時代のものであることです。 シェーダーにattribute/ varying /gl_FragColorが出てきたら WebGL1(GLSL ES 1.00)の記事なので、 本サイトの書き方(in/ out)へ読み替えが必要です(対応表は第5章)。
three.js との対応
three.js が提供していた主な概念は、本サイトでは次の章で「自作」します。 このサイト全体の地図としても使えます。
| three.js | 本サイトで自作する章 |
|---|---|
WebGLRenderer と render() | 第3章(最小形) → 第34章(ミニエンジン) |
BufferGeometry(球・トーラスなど) | 第3・13・14章 |
ShaderMaterial | 第3章 |
PerspectiveCamera / lookAt | 第11・12章 |
OrbitControls | 第15章 |
Texture / TextureLoader | 第16章 |
ライト(DirectionalLight など)とマテリアルの陰影 | 第17・18章(PBR は第22章) |
GLTFLoader | 第19章 |
WebGLRenderTarget | 第20章 |
影(castShadow) | 第21章 |
Points / パーティクル | 第30・32章 |
InstancedMesh | 第31章 |
EffectComposer(ポストプロセス) | 第33章 |
手元で動かして、壊してみる
この章のコードは src/lessons/01-what-is-webgl2/にあります。数行しかありませんが、状態機械の感触を確かめるには十分です。
clearColorの 4 つの数値(RGBA、各 0.0〜1.0)を変えて、色の対応を確かめるgl.clear(...)の行を消す —clearColorだけでは何も起きない(=状態の設定にすぎない)ことを確認するclearColorとclearの順序を入れ替える— 塗られる色はどうなるか。「実行の瞬間の盤面が使われる」ことの確認です- alpha(4 つ目の値)を 0.5 にしてみる — canvas 自身の CSS 背景色(このデモでは暗色)が透けて見えます。ただしRGB の各値はアルファ以下にしてください(例:
clearColor(0.12, 0.28, 0.42, 0.5))。アルファを超えた色の 合成結果は仕様上未定義で、環境によって色が変わります(この仕組み — premultipliedAlpha — は 第2章で扱います)
まとめ
- WebGL2 は OpenGL ES 3.0 ベースの低レベル API。シーンもカメラもマテリアルもなく、「GPU にデータとプログラムを渡して塗らせる」道具だけがある
- GPU は「同じプログラムを大量のデータへ同時適用する」装置。シェーダーには 1 要素分の処理だけを書く
- CPU(TypeScript)が段取りし、ドローコールで GPU のパイプラインが走る
- WebGL は状態機械。設定系の命令は盤面を書き換えるだけで、実行系の命令がその時点の盤面を参照する
- 本サイトは WebGL 2.0 + GLSL ES 3.00 のみを扱う
次章では、コンテキスト取得の作法と canvas の解像度管理(CSS 上のサイズと描画バッファの関係、devicePixelRatio、リサイズ)を固めます。 地味なテーマですが、「なんだかぼやける」「リサイズで歪む」を二度と起こさないための土台です。