3D RENDERING HISTORY
CHAPTER 04 · TEXTBOOK

先取り編 OpenGLとWebGL――CPUの処理をGPUへ

目標:第1・2章でCanvas 2DとJavaScriptに書いた処理がOpenGL・WebGLのどこに移るかを理解する。WebGL 2で深度テストを切り替え、背面カリングとの違いを実験する。 学習順の注意:本編第3章(ラスタライズとZ-buffer)は未公開。ここは先取り編で、番号順の必修章ではない。 操作サンプル / 第2章(面とカリング)

1. 「立方体を描く」を誰に任せるか

第1章では頂点の回転と透視投影をJavaScriptで計算し、Canvas 2Dで線を描いた。第2章では面法線を計算し、描く面を選んだ。しかし大量の三角形を毎フレーム処理するには、GPUの利用が重要になる。

OpenGLは、アプリケーションがグラフィックスハードウェアに頂点・描画状態・シェーダーを渡すためのAPIである。WebGLはOpenGL ESに基づき、ブラウザのJavaScriptからCanvasへ描画するAPIである。デスクトップ版OpenGLをそのままWebに移植したものではない。

処理の概略:

  1. 頂点バッファをGPUへ渡す。
  2. 頂点シェーダーで座標を変換する。
  3. 三角形を組み立て、クリッピング・ラスタライズする。
  4. フラグメントシェーダーで色を計算する。
  5. 深度テストなどを経て、フレームバッファに書く。

第1章の「回転と投影」は主に手順2へ移る。第2章の「裏面を消す」はGPUの背面カリングへ移る。カリング(裏向き面の除外)と深度テスト(奥行きによる遮蔽)は別物である。

2. OpenGLの歴史:固定機能とプログラム可能な処理

OpenGLは1992年から続くグラフィックスAPI。初期のOpenGLでは、座標変換や照明計算は固定機能パイプラインとして用意され、アプリケーションは設定値を渡した。

以下は古いOpenGLの描き方(レガシーAPI。現代のCore Profileでは使えない)。

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -5.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-1.0f, -1.0f, 0.0f);
glVertex3f( 1.0f, -1.0f, 0.0f);
glVertex3f( 0.0f,  1.0f, 0.0f);
glEnd();

その後、プログラマブルシェーダーで自分の頂点変換・色計算を記述する方向に変わった。GLSL(OpenGL Shading Language)と頂点バッファを使う。OpenGL 3.0で従来の固定機能は非推奨となり、OpenGL 3.1以降のCoreからは除かれた(互換プロファイルには残る)。

// 現代的なデスクトップOpenGLの描画呼び出しの一部分
glUseProgram(shaderProgram);
glBindVertexArray(vao);
glEnable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);

これらはネイティブアプリ用C/C++の断片で、ブラウザでは直接動かない。プログラムやVAOの準備も省略している。

3. OpenGL / OpenGL ES / WebGLの整理

WebGLはブラウザの安全性を保つための検証・制約・拡張機構を持ち、WebGL 2 = デスクトップOpenGL 4.xではない。ブラウザやGPUにより使える機能も異なる。

4. WebGL 2と深度テスト

WebGLはCanvas 2Dとは異なる描画コンテキストを使う。次は初期化の一部分。

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2', { depth: true });
if (!gl) throw new Error('WebGL 2 が利用できません');
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.enable(gl.CULL_FACE);
gl.frontFace(gl.CCW);
gl.cullFace(gl.BACK);
// シェーダー、バッファ、属性、描画呼び出しは別途必要

WebGLの深度テストは、新しいフラグメントの深度とその画素に保存された値を比較する。標準的な深度バッファで LESS を用いる場合は、概念的に次の判定に近い。

\[ z_{\mathrm{new}} < z_{\mathrm{stored}} \]

実際には透視投影と深度範囲への変換、バッファの精度、比較関数なども関係する。本編第3章ではこの仕組みを自前で実装する。

5. 頂点とフラグメントの2種類のシェーダー

WebGL 2ではGLSL ES 3.00を使う。頂点シェーダー:

#version 300 es
in vec3 aPosition;
uniform mat4 uProjection;
void main() {
  gl_Position = uProjection * vec4(aPosition, 1.0);
}

フラグメントシェーダー:

#version 300 es
precision highp float;
out vec4 outColor;
void main() {
  outColor = vec4(0.4, 0.85, 1.0, 1.0);
}

第1章の透視投影は uProjection に含められる。GPUは三角形内をラスタライズし、各フラグメントの色を計算する。ただしシェーダーだけでは画面は描けず、頂点属性・バッファ・プログラム・描画呼び出し等が必要。

WebGL 1のGLSL ES 1.00では attribute、varying、gl_FragColor を使う。WebGL 2の in・out の例と混ぜるとコンパイルエラーになる。

6. 2個の立方体で試す

WebGL 2の実験を開く。手前が橙色、奥が水色で、初期状態では手前を先に、奥を後に描く。

  1. 深度テストON:後から描いた奥の面が手前を不正に上書きしない。
  2. 深度テストOFF:後から描いた奥の水色が橙色を塗りつぶす。
  3. 描画順を反転:深度テストなしでも一部が正しく見えるが、面が交差する複雑な場面を順番だけで解決できない。
  4. 背面カリングOFF:裏向きの三角形も描く。カリングだけでは他物体の遮蔽を扱えない。
  5. 回転角変更:同じバッファから頂点シェーダーで座標を変換し直す。

このサンプルではWebGLが実装済みのZ-bufferを使う。第3章で自分で実装する理由は、APIの機能とアルゴリズムの区別を理解するためである。

7. Three.jsとWebGPUはどこにつながるか

Three.jsは、カメラ・シーン・メッシュ・材質等を扱いやすくする高水準のライブラリで、WebGLなどの描画バックエンドを利用する。一方WebGPUはWebGLの別名や「WebGL 3」ではない。GPUパイプラインやバインドグループ、計算処理を明示的に扱う別のWeb APIで、主にWGSLを使う。

8. 演習と一次資料

参考:

デモは外部ライブラリなしの app.js。WebGL 2が利用できないブラウザでは案内を表示する。

実際に立方体を操作するMarkdown原本