第1部 WebGL2 の基本 — 第1章

WebGL2 とは何か

このサイトでは、three.js のような抽象化レイヤーを使わず、ブラウザのWebGL2RenderingContextと GLSL ES 3.00 を直接扱って、GPU による描画を土台から学びます。three.js ユーザーの視点で言えば、これまで renderer.render() の 1 行に隠れていた内側を、全部自分の手で書けるようになる旅です。

第1章はほとんどコードを書かない概念の章です。WebGL がどの層にいて、GPU が何をする装置で、WebGL の API がなぜあのような書き味なのか — 先に地図を持っておくと、第2章以降のすべてのコードの意味が変わります。

この章の成果物: 一色に塗られた canvas。地味ですが、この青は CSS ではなく、WebGL2 経由で GPU が塗ったものです。この章の最後で、この数行のコードに WebGL の設計思想が既に現れていることを見ます。

この章で学ぶこと:

1. WebGL2 の位置づけ

WebGL は、ブラウザ上の JavaScript から GPU を使って HTML の<canvas>に描画するための低レベル API です。独自に発明されたものではなく、 組み込み機器向けのグラフィックス API である OpenGL ESをブラウザに移植したもので、WebGL 2.0 は OpenGL ES 3.0 に対応します。 バッファ、シェーダー、テクスチャといった用語や API の設計は、この OpenGL の系譜から来ています。

あなたのコードTypeScriptWebGL2 APIブラウザが提供 (OpenGL ES 3.0 ベース)GPU数百〜数千個の並列演算器このサイトで書く道ドライバ経由で命令とデータthree.js などエンジン層(不使用)
WebGL2 のいる場所。three.js は WebGL の代替ではなく、WebGL の上に作られた層です。 本サイトではその層を挟まず、WebGL2 API を直接呼びます。

ここで大事なのは、WebGL はエンジンではないということです。three.js が当たり前に提供していた概念 — シーン、カメラ、メッシュ、マテリアル、ライト、モデルローダー — は、WebGL には 1 つも存在しません。WebGL が提供するのは、おおよそ次の 3 つだけです。

カメラも 3D らしさも、この道具立ての上に「自分で計算して作る」ものです。逆に言えば、この道具を直接握ることが、 three.js では手が届かなかった表現(独自のパイプライン、GPU パーティクル、フィードバック系)への入口になります。

2. GPU はなぜ速いのか — 並列モデル

フル HD の画面は 1920 × 1080 = 約 207 万ピクセルです。これを毎秒 60 回描き換えるなら、 色の計算は毎秒約 1.2 億回必要になります。しかも 1 回の「色の計算」自体が、 後の章で扱うライティングやテクスチャ参照が入ると数十〜数百の演算になります。合計すると 毎秒数十億〜数百億の演算で、1 つずつ順番に処理する書き方では現実的に間に合いません。

GPU はこの問題を、「単純な演算器を数百〜数千個並べ、全部に同じプログラムを実行させる」という設計で解決します。1 個ずつは CPU のコアより単純で遅くても、数千個が同時に動けば総量で圧倒できます。

ピクセル (0, 0)ピクセル (1, 0)ピクセル (2, 0)同じプログラム(シェーダー)同じプログラム(シェーダー)同じプログラム(シェーダー)色 (R, G, B, A)色 (R, G, B, A)色 (R, G, B, A)⋮ 画面のピクセルの数(数百万)だけ実行される — 数千個ずつ、並列に
GPU のプログラミングモデル。「大量のデータの各要素に、同じ小さなプログラムを一斉に適用する」。 シェーダーとはこの「小さなプログラム」のことです。

このモデルには、これからずっと付き合うことになる 2 つの帰結があります。

3. 役割分担 — CPU が段取り、GPU が描く

GPU は強力ですが、自分からは何もしません。何をどう描くかの段取りはすべて CPU 側、 つまりあなたが書く TypeScript の仕事です。毎フレーム、おおよそ次のことをします。

ドローコールを受けた GPU 側では、頂点処理 → ピクセル分解 → 色計算という一連の流れ(描画パイプライン)が走ります。この中身は第3章で図解しながら全部手で書くので、いまは「CPU が材料と手順書を用意し、GPU が大量並列で実行する」という分担だけ覚えてください。

4. WebGL は状態機械

ここがこの章の核心で、three.js との書き味の違いが最も出るところです。WebGL の API は状態機械 (state machine)として設計されています。巨大なスイッチ盤を思い浮かべてください。

状態(スイッチ盤)クリア色: (0.25, 0.55, 0.85, 1)バインド中のバッファ: なし使用中のプログラム: なし…ほか多数gl.clearColor(...)設定: 盤面を書き換えるだけ(まだ何も起きない)gl.clear(...)実行: その時点の盤面を参照する描画バッファが塗られる
状態機械としての WebGL。設定系の命令が盤面を書き換え、実行系の命令が盤面を読んで描く。 この章のデモの 2 行が、そのままこの図です。
src/lessons/01-what-is-webgl2/main.ts(抜粋)
// 「クリアに使う色」という状態を設定するだけ。画面はまだ変わらない
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 の入口)を取得し、一色でクリアしています。

src/lessons/01-what-is-webgl2/main.ts
// 第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.0GLSL ES 1.00扱わない
WebGL 2.0 (2017)OpenGL ES 3.0GLSL 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本サイトで自作する章
WebGLRendererrender()第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/にあります。数行しかありませんが、状態機械の感触を確かめるには十分です。

まとめ

次章では、コンテキスト取得の作法と canvas の解像度管理(CSS 上のサイズと描画バッファの関係、devicePixelRatio、リサイズ)を固めます。 地味なテーマですが、「なんだかぼやける」「リサイズで歪む」を二度と起こさないための土台です。