はじめに
WebGLについて、初学者の方向けに概要や背景などを説明する資料が欲しかったので書いています。
章に分けて読み物として書いている箇所もあるので、文章が長いと感じる方は興味がある箇所のみ読んでいただいても構いません。
WebGLはプログラミングに触れたことがある方でも、少し独特な記法に感じる方もいるかもしれません。記法だけ分かれば書けるのではなく、コンピュータグラフィックスや数学などの知識も必要なので、プログラミングがかける人の中でも適正がある方とない方を選ぶ技術分野であると感じています。とはいえ、しっかり勉強すれば作れるものなので、画像や3Dモデルを使ってリッチな表現を作りたい方は、楽しみながら諦めずに続けていただければと思います。
1. WebGLとは
1-1. WebGLの概要
WebGLは、Khronos Groups(クロノスグループ)という米国の非営利団体が策定している技術で、ウェブブラウザ上で動くグラフィックを描画するための仕組みやメモリの効率化を行っています。
WebGLはHTML5と同時期に登場した技術で、HTML5で策定された<canvas>
タグや<video>
タグに描画する際にWebGLが使われていますが、HTML5の仕様の中にWebGLが含まれているわけではありません。
WebGLを動かすためには、WebGLに対応したウェブブラウザが必要です。とはいえ、2022年時点での主流なGoogle Chrome、Microsoft Edge、Mozilla Firefox、Opera、Safariなどのウェブブラウザの最新バージョンではPCもスマホも含めて対応されていますので、これからWebGLに対応したウェブサイトやサービスを開発する方はあまり気にしなくても良いでしょう。
1-2. WebGLが必要となった背景
1990年代からインターネットが普及し始めてから数多くのアプリやサービスが開発されましたが、普及しだしてからしばらくは、アプリケーションの多くはパソコンのOSにインストールして実行されるネイティブアプリケーションと呼ばれる形式が主流でした。そのうち、パソコンにアプリをインストールしなくてもウェブブラウザ上で完結できる利便性が勝り、ウェブブラウザ上で動くウェブアプリケーションという形で、ショッピングサイトや読書、ゲーム、メール、地図なども見れるようになりました。インターネットに繋ぐことができる環境であれば、ウェブブラウザだけでネイティブアプリと同等のことが実現できるようになってきたのです。とはいえ、ネイティブアプリと比べるとまだまだできることの制約があります。その中のひとつが「高速で高画質なグラフィックス処理」です。ネイティブアプリはハードウェアに近い低レイヤーで動作するため、何層もレイヤーを挟むウェブブラウザよりも高速に動作するのですが、ウェブブラウザ上でも高速に動作させるための解決策として、GPU(Graphic Processing Unit)を使ったグラフィック描画を利用するためにWebGLの技術が策定されました。
1-3. WebGLの利点
WebGLで開発されるウェブアプリケーションには様々な利点があります。例えば、ネイティブアプリケーションであれば特定のOSに向けて作られたものは特定のOSでしか動かないのですが、ウェブアプリケーションであれば、OSが異なってもウェブブラウザが対応していれば動きます。開発についても同様に、OSごとに異なる言語でアプリケーションを開発する必要があったのをHTML/CSS/JavaScriptを使えばアプリケーションが作れるようになり、開発環境の設定や言語の習得のハードルが下がりましたので、手軽に3Dグラフィックを用いたアプリケーションを作りたいとなるとWebGLを使った開発を選択肢に入れるのも良いでしょう。
2. WebGLの開発環境の構築
2-1. 開発で使用するマシンについて
開発環境を作るために、まずはパソコンを用意してください。ノートパソコンでもデスクトップパソコンでも大丈夫です。
OS(オペレーティングシステム)
開発や表示にはインターネットブラウザが必須なので、OSはインターネットブラウザが搭載されているものを選んでください。特にこだわりがなければ、MacかWindowsのパソコンをおすすめします。できればOSのバージョンは最新のものを使うと良いでしょう。
メモリ
メモリの性能はパソコンが動作する際の処理速度に影響します。個人の趣味レベルの開発であればあまり大きなメモリは必要ありませんが、ストレスなくパソコンで開発するには8GBくらいは搭載しているパソコンを選ぶと良いと思います。
CPU(Central Processing Unit)
CPUの性能もパソコンが動作する際の処理速度に影響します。性能が良ければ良いほどもちろんサクサク動くので良いのですが、値段が高くなってしまいます。世代によってもコア数やクロック数など変わるので一概にどれが良いとは言えませんが、ミドルモデル以上のCPU(Core i5やRyzen 5、M1など)を搭載しているパソコンを選ぶと良いと思います。
GPU(Graphics Processing Unit)
グラフィックスの描画を扱うため、GPUを搭載しているパソコンを選んでください。GPUはグラフィックスの描画に特化したハードウェアです。最近のパソコンは、WindowsであればオンボードでIntel HD GraphicsのGPUが積まれていることも多いです。Macであれば、M2やM1のチップ搭載であればその中にGPUが含まれています。M2やM1のチップ搭載でなくてもIntel Iris Plus GraphicsなどのGPUが搭載されているので、2022年以降に発売されているパソコンであれば問題になることは少ないと思います。
ストレージ
ストレージは、パソコンが使用するデータを保存するために使います。ファイルを保存するにもストレージが必要ですし、OSもストレージに記録されています。HDDとSSDが選択肢としてありますが、HDDよりもSSDのほうが高速に動作するので、予算が許すのであればSSDの256GB以上を搭載しているパソコンをおすすめします。(SSDの種類などの細かい話は割愛します)
2-2. 開発で使用するインターネットブラウザについて
主流なウェブブラウザは2022年の時点移行では概ねWebGLを搭載しているので、MacかWindowsであれば、何かしら対応ブラウザは準備できると思います。特にこだわりがなければ、デバッグのしやすさと使用しているユーザーが多いという観点からGoogle Chromeをおすすめします。iPhoneで動作させる場合の実機検証をしたい場合はMacのSafariブラウザが必要になります。
2-3. 開発で使用するエディタについて
WebGLを使うためには、HTML/CSS/JavaScriptのファイルにコードなどを記述する必要があります。
いろんなテキスト編集のエディタがあり、好きなものを使っていただいても良いのですが、特にこだわりがなければVisual Studio Codeをおすすめします。Visual Studio CodeはMacでもWindowsでも無料で使用でき、開発に便利な拡張機能が豊富で、自動的にコードの字下げしてくれたり構文ハイライトで色付けして見やすくしてくれたりなど、開発する上で非常に使い勝手が良いエディタです。
3. WebGLの基本的な書き方
3-1. 本章の目標
本章では、まず簡単な三角形を表示するサンプルを書くのを目標に、実際に図形を描画してみたいと思います。物足りないと感じるかもしれませんが、3Dで立体物を表示する際はポリゴンと呼ばれる三角形や四角形の組み合わせで面が作られていることが多いので、三角形を描画することはとても重要かつ基本的な機能です。まずは小さな目標を立てて小さく達成するというサイクルを繰り返して、アウトプットを楽しみながら勉強していくことを意識してください。
3-2. HTMLファイルの作成
ここでは、インターネットブラウザにはGoogle Chrome、開発エディアにはVisual Studio Codeを使用して開発していきます。
ウェブブラウザにWebGLで画像を表示するために、<canvas>
タグを含んだHTMLファイルが最低限必要です。<canvas>
タグを使うことでブラウザの画面上にキャンバスを表示することができ、そのキャンバスの中にプログラムで実装した図形などを描画することができます。
Visual Studio Codeを開いてください。パソコンの中の好きな場所に開発作業用のフォルダを作っておくと管理しやすいので、WebGL
などの好きな名前のフォルダを作っておくと良いです。Visual Studio Codeの細かい使い方は割愛します。
作成したフォルダの直下に、新しくindex.html
というファイルを用意してください。
index.htmlを作成したら、次のように編集してください。
<!DOCTYPE html>
<html>
<head>
<title>WebGL入門</title>
script
</head>
<body>
<!-- bodyタグの中にCanvasを配置する -->
<canvas id="canvas"></canvas>
</body>
</html>
3-3. JavaScriptファイルの作成
HTMLで配置したcanvasに描画するためにはJavaScriptを使います。JavaScriptでcanvasタグが持っているコンテキストというものを取得し、このコンテキストを使用してcanvas上に図形や文字を描画したり、描画のための設定をすることができます。コンテキストはcanvasに描画するための司令塔のような役割を果たします。
index.html
と同じフォルダ内に、main.js
というJavaScriptのファイルを作成して、次のように編集してください。
document.addEventListener('DOMContentLoaded', function () {
// HTMLからcanvas要素を取得する
const canvas = document.getElementById('canvas');
// canvas要素からwebglコンテキストを取得する
const gl = canvas.getContext('webgl');
// WebGLコンテキストが取得できたかどうか調べる
if (!gl) {
alert('webgl not supported!');
return;
}
});
WebGLでcanvas内に描画するには、WebGLのコンテキストを取得する必要があります。タイミングによっては、JavaScriptのプログラムが実行されるときにHTMLのタグで設定したcanvas要素がまだ読み込まれていない可能性があるため、HTMLの要素の読み込みが終わったタイミングで通知される'DOMContentLoaded'
というイベントを受け取ってからcanvasを取得する処理を書くようにしています。
WebGLのコンテキストは、ユーザーのブラウザの設定やハードウェアが対応してない問題等の場合に取得できないこともあるので、取得できない場合の判定を行っています。
WebGLのコンテキストを格納する変数名は、ここではgl
という名前にしています。この変数は図形を描画する上で何度も書くことになるので文字数が少ないほうが便利なのと、Khronos Groupsの他のプラットフォーム向けの関数ではglから始まる名前を使用しているため、似たような書き方にすることで文献が調査しやすくなるメリットがあります。
JavaScriptファイルを編集して保存できたら、先ほど作成したindex.htmlと紐付けるために、index.htmlを編集して<head>
タグ内にmain.jsを読み込む処理を追加します。
<!DOCTYPE html>
<html>
<head>
<title>WebGL入門</title>
<script src="./main.js"></script> <!-- main.jsを読み込む -->
</head>
<body>
<!-- bodyタグの中にCanvasを配置する -->
<canvas id="canvas"></canvas>
</body>
</html>
3-4. canvasに色をつける
三角形を描画していきたいところですが、まずはキャンバスを特定の色で塗りつぶすところから始めます。
JavaScriptで操作していくため、main.js
を開いて次のように編集してください。
document.addEventListener('DOMContentLoaded', function () {
// HTMLからcanvas要素を取得する
const canvas = document.getElementById('canvas');
// canvas要素からwebglコンテキストを取得する
const gl = canvas.getContext('webgl');
// WebGLコンテキストが取得できたかどうか調べる
if (!gl) {
alert('webgl not supported!');
return;
}
// canvasを初期化する色を設定する
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// canvasを初期化する
gl.clear(gl.COLOR_BUFFER_BIT);
});
WebGLでは、色を指定するためにRGBAというデータ形式を使います。色を赤(R:Red)、緑(G:Green)、青(B:Blue)の三原色の度合いと、透明度(A:Alpha)の組み合わせとして表現する方式で、WebGLの場合は、それぞれ0.0〜1.0の値で指定します。
例えば、左から順番にR、G、B、Aの値を0.0〜1.0で指定するため、下記のような記述になります。
- 色を白にしたい場合: (1.0, 1.0, 1.0, 1.0)
- 色を赤にしたい場合: (1.0, 0.0, 0.0, 1.0)
- 色を黃にしたい場合: (1.0, 1.0, 0.0, 1.0)
- 色を黒にしたい場合: (0.0, 0.0, 0.0, 1.0)
- 色を透明度50%の半透明の黒にしたい場合: (0.0, 0.0, 0.0, 0.5)
gl.clearColor(0.0, 0.0, 0.0, 1.0);
という処理で、キャンバスを初期化するための色を黒に指定しています。
WebGLでは、図形が動いたりするアニメーションを表現するために、1秒間の間に何十回も画面の更新が行われます。キャンバス内を動き回る図形などを描画する際には、毎回キャンバスを描画を初期化してから新たな位置に図形を描画しないと過去の位置に描画した絵も残り続けてしまうため、毎回描画を初期化する必要があります。
gl.clearColor
は色の設定のみで、実際にキャンバスを初期化するにはgl.clear
のメソッドを呼びます。
上記のプログラムを記入したmain.jsファイルを保存して、index.htmlをGoogle Chromeブラウザで開くと、画面の左上に黒い四角形が描画されていると思います。ここまでできれば、WebGLで開発するための新たな一歩を踏み出せました。
3-5. canvasに三角形を描画する
WebGLで図形を描画するには、「点」「線」「面」のいずれかが必要になります。点も線も面も「点」の組み合わせで描画することができます。例えば、「点」であれば1つの点があればよいです。「線」を書きたい場合は2つの点があればそれをつなぐことで線になります。「面」は3つの点があればそれをつなぐと面(三角形)になります。これらの図形を描画するための点のことを「頂点」と呼びます。WebGLでは、基本的な線や面を「プリミティブ形状」と呼び、頂点をどういった形状としてつなぐのかを指定する仕組みがあります。
プリミティブ形状で指定できる種類は下記です。一般的には、gl.TRIANGLES
とgl.TRIANGLE_STRIP
が使われることが多いです。
プリミティブについて理解が深まったところで、実際に三角形を描画していきます。
書くプログラムの量が一気に増えますが、順を追って紐解いていきますので、まずはコピペでも構いません。index.htmlを開いて、下記の追記箇所を追加します。
<!DOCTYPE html>
<html>
<head>
<title>WebGL入門</title>
<script src="./main.js"></script> <!-- main.jsを読み込む -->
<!-- ここから追記 -->
<!-- create vertex shader -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 position;
void main(void){
gl_Position = vec4(position, 1.0);
}
</script>
<!-- create fragment shader -->
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
void main(void){
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
</script>
<!-- ここまで追記 -->
</head>
<body>
<!-- bodyタグの中にCanvasを配置する -->
<canvas id="canvas"></canvas>
</body>
</html>
頂点シェーダとフラグメントシェーダについて
WebGLでは、vertex shader(頂点シェーダ)とfragment shader(フラグメントシェーダ)を使うことで、GPUを使った図形や文字の描画を実行しています。頂点シェーダは名前の通り、頂点の場所を指定するために使います。フラグメントシェーダはキャンバスに描画された図形の各ピクセルにおける色を指定するものとここではざっくり理解していただければと思います。先ほどindex.htmlに追加したのは、頂点シェーダとフラグメントシェーダそれぞれの内容です。
WebGLのルールとして、頂点シェーダではgl_Position
という組み込み変数に頂点データを渡さなければなりません。この例では、頂点シェーダには後ほどJavaScriptのプログラムのほうで頂点を指定できるようposition
という変数を作ってgl_Position
という変数に指定しています。
また、WebGLのルールとして、フラグメントシェーダではgl_FragColor
という組み込み変数に色情報のデータを渡さなければなりません。この例では、簡易的に色を青色に設定していますので、gl_FragColor
にvec4(0.0, 0.0, 1.0, 1.0)
を指定しています。
WebGLのシェーダに使われているvec3
やvec4
はベクトル(Vector)の意味ですが、「ベクトルとはなんぞや?」という方はいったんvec3
は3つの値を保持できる変数の型、vec4
は4つの値を保持できる変数の型という理解でも大丈夫です。
シェーダの読み込みと使用設定
次に、作成した頂点シェーダとフラグメントシェーダをJavaScript側で呼び出して描画に使用します。main.js
に次の追記箇所を追記してください。
document.addEventListener('DOMContentLoaded', function () {
// HTMLからcanvas要素を取得する
const canvas = document.getElementById('canvas');
// canvas要素からwebglコンテキストを取得する
const gl = canvas.getContext('webgl');
// WebGLコンテキストが取得できたかどうか調べる
if (!gl) {
alert('webgl not supported!');
return;
}
// canvasを初期化する色を設定する
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// canvasを初期化する
gl.clear(gl.COLOR_BUFFER_BIT);
// ------------ ここから追記 ------------
// プログラムオブジェクトを作成する
const program = gl.createProgram();
// シェーダのソースを取得する
const vertexShaderSource = document.getElementById('vertexShader').textContent;
const fragmentShaderSource = document.getElementById('fragmentShader').textContent;
// シェーダをコンパイルして、プログラムオブジェクトにシェーダを割り当てる
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
gl.attachShader(program, vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
gl.attachShader(program, fragmentShader);
// シェーダをリンクする
gl.linkProgram(program);
// プログラムオブジェクトを有効にする
gl.useProgram(program);
// 3つの頂点の座標を定義する
const triangleVertexPosition = [
0.0, 0.8, 0.0,
-0.8, -0.8, 0.0,
0.8, -0.8, 0.0
];
// 頂点バッファを作成する
const triangleVertexBuffer = gl.createBuffer();
// 頂点バッファをバインドする
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBuffer);
// 頂点バッファに頂点データをセットする
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertexPosition), gl.STATIC_DRAW);
// Positionのロケーションを取得し、バッファを割り当てる
const positionLocation = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
// 描画する
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.flush();
// ------------ ここまで追記 ------------
});
頂点シェーダとフラグメントシェーダは必ずセットで使う必要があります。シェーダを管理するためにprogram
というプログラムオブジェクトを使います。まずはプログラムオブジェクトを作成しています。
// プログラムオブジェクトを作成する
const program = gl.createProgram();
次に、先ほどindex.html
で記述した頂点シェーダとフラグメントシェーダをid名を使って取得します。読み込んだシェーダの記述をGPUが解釈できるように、コンパイル
という作業と2つのシェーダを紐付けるためにリンクという作業をします。プログラムオブジェクトに読み込んだシェーダをそれぞれコンパイルしてから割り当ててリンクし、そのプログラムオブジェクトを使うという記述をすることで、先ほど作成したシェーダーの内容がキャンバスの描画に使えるようになります。
// シェーダのソースを取得する
const vertexShaderSource = document.getElementById('vertexShader').textContent;
const fragmentShaderSource = document.getElementById('fragmentShader').textContent;
// 頂点シェーダとフラグメントシェーダをコンパイルして、プログラムオブジェクトにシェーダを割り当てる
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
gl.attachShader(program, vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
gl.attachShader(program, fragmentShader);
// シェーダをリンクする
gl.linkProgram(program);
// プログラムオブジェクトを有効にする
gl.useProgram(program);
三角形の頂点データの定義
次に、頂点シェーダにわたす情報として、三角形の頂点を定義します。triangleVertexPosition
という変数に、頂点の座標を定義しておきます。この例では値が9つはいっていますが、1つの頂点に対してx, y, zの3軸で方向に対しての値を設定していますので、最初の3つの値が1つ目の頂点、次の3つの値が2つ目の頂点、最後の3つの点が3つ目の頂点の座標をxyzの順番で表しています。
WebGLでは、キャンバスの中心を座標系の原点(0, 0)とし、横方向の座標をx座標、縦方向の座標をy座標として、それぞれキャンバスに対して-1.0〜1.0の値で頂点を指定します。3次元の図形を描画する場合はz軸方向も考える必要がありますが、今回は2次元のみを考えたいので、z軸の値は0.0にしています。
今回はキャンバスに対して余白を0.2あけた、上向きの三角形を描画したいと思いますので、3点それぞれのxとyの値に0.8や-0.8などの値を入れています。
// 3つの頂点の座標を定義する
const triangleVertexPosition = [
0.0, 0.8, 0.0,
-0.8, -0.8, 0.0,
0.8, -0.8, 0.0
];
頂点の座標を定義したら、次に頂点データをGPUに渡すためのバッファというものを作成します。バッファは、画像処理プログラミング以外にもプログラムをしていたら頻繁に目にすると思いますが、簡単に言えば「GPUにデータを渡す方法を効率的にするための容れ物」のようなものです。
// 頂点バッファを作成する
const triangleVertexBuffer = gl.createBuffer();
// 頂点バッファをバインドする
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexBuffer);
// 頂点バッファに頂点データをセットする
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertexPosition), gl.STATIC_DRAW);
// positionのロケーションを取得し、バッファを割り当てる
const positionLocation = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.createBuffer
メソッドで頂点データをGPUに送るためのバッファを作り、gl.bindBuffer
とgl.bufferData
メソッドでWebGLのコンテキストに紐づけます。
次に、頂点シェーダで用意したposition
という変数に渡すために、シェーダーの該当の変数が格納されているポインタを取得し、そのポインタの箇所に頂点バッファを割り当てることで、頂点シェーダに頂点データを送ることができます。
三角形の描画
最後に、指定した頂点を描画するメソッドを呼び出すことでキャンバスに描画することができます。
三角形を描画したいので、プリミティブの中のgl.TRIANGESを使って描画をしています。
gl.flush
メソッドを呼び出すと、すぐにGPUに命令が送り込まれ、これまでに設定した描画の命令が実行されます。
// 描画する
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.flush();
以上の変更箇所を記述したindex.htmlとmain.jsのファイルを保存し、Google Chromeブラウザでindex.htmlを開くと、黒いキャンバスの中央に青い三角が描画されたことが確認できると思います。
おわりに
以上で、基本となる三角形の描画まで行うことができました。いきなりプログラムの量が増えて嫌になった方もいるかもしれませんが、一つ一つ紐解きながら理解するのを続ければ書けるようになると思いますので、3Dグラフィックス処理を仕事にしていきたい方は諦めずに楽しみながら勉強を続けてください。
次回は図形に色を付ける方法を説明したいと思います。