Welcome to Mashykom WebSite



WebGLを用いた3D コンピュータグラフィックス入門


 WebGLとは、Webブラウザ上(ホームページ上)で、三次元のCGをリアルタイムに表示して動かせるオープンソースのグラフィックAPIです。OpenGLから派生したOpenGL ESの3D APIをWebブラウザに移植したものということになります。

 OpenGLは、1990年代から歴史あるAPIで、長らく3DゲームやCADといった三次元処理を行えるAPIとして、広範に使用されてきました。2003年頃、高機能携帯電話などの組み込み機器の隆盛を受けて、そのOpenGLからサブセット版規格が生まれました。OpenGL ES(OpenGL for Embedded Systems)です。OpenGLの仕様が膨大になってきて、組み込み機器に搭載するのが大変だったOpenGLから、不要な機能をできるだけ削ぎ落としたバージョンです。WebGLはこのOpenGL ESをベースに、Webブラウザ向けに微調整されて出来た規格なのです。いわば、OpenGL ES for Webとでも言えます。

 WebGLにもバージョンがあり、現在、バージョン1と、バージョン2の二つがあります。WebGL1はOpenGL ES 2.0をベースにしています。WebGL2はOpenGL ES 3.0をベースにしています。WebGL2はWebGL1とどう違うのかというと、基本的なコンセプトはほぼ変わっておらず、WebGL1の機能増強版といったところです。現在、ほとんどすべてのブラウザにはWebGL1 がインストールされています。

 こうして、WebGL は、ウェブブラウザ上で OpenGL ES 相当の描画処理を行うことができる低レベル API なので、GPU を利用した高速で強力な描画性能をウェブにもたらします。セキュリティの観点から、ブラウザにはプラグイン等をインストールすることが敬遠されて久しいですが、WebGL は JavaScript の API として実装されているため、あらためてプラグイン等を一切インストールすること無く実行できます。ブラウザが WebGL を実行できるのであれば、通常のウェブページと同様に、PC だけでなくモバイル端末などでも同様のコンテンツを配信することができます。

 具体的には、OpenGL ES 2.0 に基づく API であるWebGLを用いて、 HTML ページのcanvas タグ内で 3D グラフィックスをレンダリングします。『レンダリング(rendering)とは、データ記述言語やデータ構造で記述された抽象的で高次の情報から、コンピュータのプログラムを用いて画像・映像・音声などを生成することをいう。元となる情報には、物体の形状、物体を捉える視点、物体表面の質感(テクスチャマッピングに関する情報)、光源、シェーディングなどが含まれる。』WebGL のプログラムは JavaScript で記述する制御コードと、コンピュータの Graphics Processing Unit (GPU) で実行する特殊効果コード (シェーダーコード) で構成されます。

 このページでは、WebGL の基礎を簡単に紹介します。ここでは、OpenGLでのシェーディング言語GLSL、および、OpenGL そのものの説明は行いません。OpenGLについては、OpenGLのページを読んでください。なお、JavaScript の理解が必須なので、JavaScript の予備知識がない場合には、JavaScript の Tutorials ページを参照してください。WebGLのためのJavaScriptライブラリである Three.js を用いた WebGL の利用については、Three.js のページを参照ください。

Last updated: 2019.10.20



JavaScriptを用いたWebGLのプログラム


 HTMLでグラフィック関連のタグとして追加された canvas は、その名の通り図形や画像を描画するためのキャンバスとして動作するタグで、javascript を使って操作することが可能です。もちろん、WebGL もこの canvas を利用することになるので、WebGL を記述する前に canvas タグについて簡単に解説します。

 canvas は 従来よりある img タグなどと同様、自由に大きさなどを指定できる矩形領域です。javascript によって矩形領域内に操作を加えることが可能で、図形、文字などを自由に描画することができます。それらに影をつけたり、色を塗ったり、あるいは回転させて描画させたりなど、かなり柔軟に操作できます。また、ウェブ上で一般的に使われているフォーマット(gif、jpg、png など)であれば、画像を使った描画処理も可能となっています。ただし、アニメーション処理を一括で管理するようなオブジェクトやメソッドは存在しません。つまり、動きのあるコンテンツを作成するためには、javascript による恒常的なループ処理を自前で実装してやる必要があります。

 3D 描画のため WebGL を使うにあたって始めに必要なものは、canvas タグの設定とスタイル設定のためのcss ファイルです。以下のコードは、WebGL入門のサイトから引用しました。この HTML では、bodyタグの中に

  <body>
 <canvas id="glcanvas" width="640" height="480"></canvas> 
  </body>
でcanvas タグを設定します。canvasの横幅と縦幅も設定します。 canvas には id を設定しておき、javascript から getElementById などで参照できるようにします。例えば、getElementById("glcanvas")のように設定します。いったん 参照が設定されれば、そこから先はすべて javascript 上でコードで操作することが可能です。スタイルは、例えば、webgl.css ファイルで設定をします。
const gl = canvas.getContext("webgl");

で、canvasで描画されるWebGL コンテキストの初期化が行われています。描画対象の初期化に成功すると、識別子 gl が描画コンテキストへの参照名になります。以下のように、ファイル名を"index.html"として保存します。


<!doctype html>
<html lang="en">
  <head>
    <title>WebGL Demo</title>
    <meta charset="utf-8">
    <link rel="stylesheet" href="webgl.css" type="text/css">
  </head>

  <body>
    <canvas id="glcanvas" width="640" height="480"></canvas>
  </body>

  <script>
    main();
    //
    // ここから始める
    //
    function main() {
	  const canvas = document.getElementById("glcanvas");
      // Initialize the GL context
      const gl = canvas.getContext("webgl");
      // Only continue if WebGL is available and working
      if (gl == null) {
        alert("Unable to initialize WebGL. Your browser or machine may not support it.");
        return;
      }
      // カラーバッファの色を指定
      gl.clearColor(1.0, 0.0, 1.0, 1.0);
      // カラー・バッファをgl.clearColor で指定された色にする
      gl.clear(gl.COLOR_BUFFER_BIT);
    }
  </script>
</html>

ここで、webgl.cssの中身は


canvas {
	border: 2px solid black;
	background-color: black;
}
video {
	display: none;
}

です。これをファイル名"webgl.css"として、上のコードが保存されているディレクトリと同じディレクトリに保存します。

if コード

 
if (gl == null) 

は、canvas に "webgl" という名前のコンテキストを要求し、それが成功したか否かを聞いています。この時点で、gl は null 値 (WebGL コンテキストが利用できない) 、または、レンダリングを行う WebGL コンテキストへの参照の、どちらかになります。失敗した場合は、alertを出します。

 以上で、Web GL コンテキストを正常に初期化するために充分なコードが揃いました。CodePen を用いて、作成したウインドウを表示しましょう。(最近、Codepen のセキュリティが厳格化されたので、以下の codopen を表示するためには、使用中のブラウザで一度 codepen にログインしておいて下さい。その後ログアウトしておきます)「Result」をクリックすると、以下のように紫色に塗りつぶされた大きな四角形が表示され、コンテンツの受け取りを待つ状態になっています。

See the Pen WebGL_1 by mashyko (@mashyko) on CodePen.

 三次元空間は、私たちが見ているこの現実空間に他なりません。三次元の世界では、すべてのものに縦と横、そして奥行きが存在します。それを再現しようとするのが、リアルタイム 3D レンダリングです。しかし、三次元空間を再現しようとするとき、その出力は二次元のディスプレイ上です。WebGL を使って擬似的に三次元空間を描写したとしても、ディスプレイという二次元平面に出力しなければいけない。奥行きによる前後関係や、遠近法による伸縮拡大を加味して、どこになにがどんなふうに描かれるのか、計算する必要があります。WebGLで採用されている clipspace 座標系では、座標の軸の範囲は[-1,+1]に制限されています。clipspace 座標系(x, y, z)は以下のようになっています。

clip-space-graph.svg

 三次元空間をPCのディスプレイ上の二次元平面に変換すために必要となるのが座標変換と呼ばれる一連の計算処理です。座標の変換には、大きく分けて三つの種類があり、これらを正しく組み合わせることで、最終的にディスプレイ上のどの位置にどんなものを描き出すのかが決まります。三次元の情報を二次元に変換するものの代表例としてカメラがあります。写真であれ映像であれ、カメラを通すとそれはすべて二次元の情報に変換され、最終的には写真や動画として平面上で表現できる形になります。

 三次元空間の情報を二次元の情報に変換するために、モデル変換、ビュー変換、プロジェクション変換という、3種類の座標変換が必要です。座標変換の一つ目は、モデル変換です。モデル変換とは、被写体となる物体が三次元空間のどの位置にあるのかを定義するための座標変換です。現実の世界とは異なり、プログラムのなかの三次元空間には世界の中心の基準となる原点という位置が定義されています。その原点から見て、被写体となるモデルが相対的にどの位置に存在しているのかを知るためにモデル変換が必要になります。

 ビュー変換では、実際にカメラがどの位置にあるのか、そしてカメラはどこを向いているのかなどを定義します。例えば、置かれているリンゴを見る場合、カメラがそのリンゴに向けられていなければ、リンゴが映し出されることはありません。他にも、たとえばカメラとリンゴとの距離が著しく離れていれば、仮にリンゴにカメラが向けられていたとしても映らない。カメラの位置や、その向けられている方角を決めるために行なう座標変換、それがビュー変換です。

 プロジェクション変換では、三次元空間のどの領域を撮影するのかを設定します。たとえば、横に幅広くパノラマとして撮影するのか、あるいは縦長の映像として撮影するのか、どのくらい遠くまでを撮影するのかなどを設定することができます。例えば、一番手前の位置と、一番遠方の位置はここまで、という撮影する領域を設定します。プロジェクション変換を行なうことによって、いわゆる遠近法の効果が得られます。近くにあるものは大きく、遠くにあるものは小さく描画されるようになります。

 WebGL の世界では、二次元平面の形状を描画する場合でも、すべては三次元空間で描画されることになります。また、WebGL には固定機能パイプラインは存在しません。固定機能パイプラインというのは、3D でレンダリングを行なう一連の処理の流れのことです。固定機能パイプラインのなかでは、プロジェクションの座標変換などをやってくれます。言い換えると、WebGLでは座標変換はすべて自前でやる必要があります。そして、この座標変換を記述するための仕組みこそがシェーダです。


シェーダー・コードを用いたWebGLのプログラム


 シェーダーは OpenGL ES Shading Language を使用して作成します。シェーダーは図形形状の頂点に関する情報と各ピクセルの色をスクリーン上にレンダーするためのデータを作成するプログラムです。だから、WebGLの内容をレンダリングするためには二種類のシェーダー(vertex and fragment shaders)が必要です。これらのプログラムをGLSL言語で書いて、WebGLのプログラムに組み込んで、GPUで実行させるためにコンパイルする必要があります。

 シェーダには、ポリゴンの頂点情報を扱う頂点シェーダ(またはバーテックスシェーダ)と、描画される際のピクセルの情報を扱うフラグメントシェーダ(またはピクセルシェーダ)の二つの種類があります。WebGL では、固定機能パイプラインが存在しないため、この頂点シェーダとフラグメントシェーダの二つのシェーダを用意する必要があります。

 シェーダーに関する詳しい説明は、このサイトを参照ください。

 上で触れたように、バーテックスシェーダーは、各頂点の位置や形状を定義します。簡単なプログラムを紹介します。aVertexPosition で定義された属性(attribute)から頂点の情報を読み取ります。uProjectionMatrix と uModelViewMatrixで2つの4行4列のマトリックスを設定しています。vertex positionを設定するとき、通常、描画の形状が2次元でも、3次元でも、4次元ベクトル vec4 attribute で設定します。それは、GL内での数学的な処理が4次元ベクトルで演算されるからです。gl_Positionが各バーテックスのデータを読み込んで、GPUでのclipspaceに対応する設定に変換して、保存します。


  // Vertex shader program

  const vsSource = `
    attribute vec4 aVertexPosition;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    void main() {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
    }
  `;

 フラグメントシェーダーは、形状の各ピクセルの色を決定します。ポリゴン内の各ピクセルは、GLSL の用語でフラグメントと呼びます。gl_FragColor はフラグメントの色として使用する、GLSL の組み込み変数です。gl_FragColor を用いて、以下のように値を設定することで、ピクセルの色を定義します。



  // Fragment shader program

 const fsSource = `
    void main() {
      gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
  `;

 次に、これらのシェーダーをWebGLに送り、コンパイルしてから、リンクを確立する必要があります。HTML 文書の中にあるシェーダーのscriptを読み込むようにコードを記述します。この役割を担うのが以下に定義するinitShaders() 関数です。

 2 つのシェーダープログラムを読み込みます。シェーダーのコードを読み込んだら、シェーダーオブジェクトがバーテックスシェーダー, あるいは,フラグメントシェーダーであることを判断するために MIME タイプの確認を行い、そして取り出したソースコードから適切なタイプのシェーダーを生成します。

//
// Initialize a shader program, so WebGL knows how to draw our data
//

function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

  
  // シェーダープログラムを作成
  
  shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  
  // シェーダープログラムを作成できない場合はアラートを表示
  
  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert("シェーダープログラムを初期化できません。");
  }
  
  gl.useProgram(shaderProgram);
  
  vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
  gl.enableVertexAttribArray(vertexPositionAttribute);
}

 最後に、ソースコードはシェーダーに渡され、そしてコンパイルされます。シェーダーのコンパイル時にエラーが発生した場合は、警告を表示して null 値を返します。エラーが発生しなかった場合は、コンパイル済みのシェーダーを呼び出し元に返します。以下のプログラムがこれを担います。



gl.shaderSource(shader, theSource);
    
  // シェーダープログラムをコンパイル
  gl.compileShader(shader);  
    
  // コンパイルが成功したかを確認
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {  
      alert("シェーダーのコンパイルでエラーが発生しました: " + gl.getShaderInfoLog(shader));  
      return null;  
  }
    
  return shader;
}

 例えば、正方形を描画するときに、その頂点情報を収めるバッファを作成しなければなりません。これは、initBuffers() 関数を呼び出して行います。以下の例は、2次元の四角形の頂点を設定しています。平面図形でも、座標は三次元表示にする必要があります。



var horizAspect = 480.0/640.0;

function initBuffers() {
  squareVerticesBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
  
  var vertices = [
    1.0,  1.0,  0.0,
    -1.0, 1.0,  0.0,
    1.0,  -1.0, 0.0,
    -1.0, -1.0, 0.0
  ];
  
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}

 シェーダーの確立とオブジェクトの構築を行ったら、実際にシーンを描画する段階になります。



function drawScene() {
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  
  perspectiveMatrix = makePerspective(45, 640.0/480.0, 0.1, 100.0);
  
  loadIdentity();
  mvTranslate([-0.0, 0.0, -6.0]);
  
  gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
  gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
  setMatrixUniforms();
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

 コンテキストを背景色でクリアします。そしてカメラの遠近感を定義します。視野角を 45 度、縦横の比を 640/480 (canvas の寸法) に設定しています。以上のコードをつなぎ合わせて、WebGLによるグラフィックス描画のJavascript プログラムは完成します。このチュートリアルは、MDNのWebGL-Tutorialsにあります。

 HTMLは以下のようなプログラムになります。詳細は、このサイトのsample2をみてください。



<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>WebGL Demo</title>
    <link rel="stylesheet" href="../webgl.css" type="text/css">
  </head>

  <body>
    <canvas id="glcanvas" width="640" height="480"></canvas>
  </body>

  <script src="../gl-matrix.js"></script>
  <script src="webgl-demo.js"></script>
</html>


このプログラムをCodepenを用いて実行してみます。Codepen 向けにコードを若干修正しています。この画面の「Result」および「Return」をクリックすると、ウインドウに白い四角形が描かれます。

See the Pen WebGL_2 by mashyko (@mashyko) on CodePen.


 JSをクリックすると、上で説明したJavascriptのコードが表示されます。4角形を書くだけで、これだけの長さのコードが必要です。アニメーションを描画するときのことを考えると、気が遠くなります。WebGL の詳しい日本語による解説はwgld.orgが参考になります。

 最後に、アニメーションを用いた例を挙げます。ただ、立方体が回転するグラフィクスを描くWebGLのJavaScriptプログラムは非常に長いので、コードの内容を説明することは控えます。結果のみを紹介します。CodePen を利用して表示すると以下のようになります。

See the Pen webgl_5 by mashyko (@mashyko) on CodePen.

実際に描かれるグラフィックスは、このGithubのexample5を開いてブラウザで開けば,上記のようなグラフィックスが表示できます。



Three.js を用いたWebGLの利用法



 Three.jsは、HTMLの3D技術WebGLを容易にしたフレームワークです。ウェブブラウザ上でリアルタイムレンダリングによる3次元コンピュータグラフィックスを描画する軽量なJavaScriptライブラリです。WebGLだけで3D表現をするためには、立方体一つ表示するだけでも多くのJavaScriptやGLSLコードを書く必要があり専門知識も必要です。Three.jsを使えばJavaScriptの知識だけで簡単に3Dコンテンツが作成できるため、手軽に扱えるようになります。言い換えると、three.jsは、WebGLのAPIを簡略化するためのラッパと言えます。

 Three.js のチュートリアルに沿って、使用方法を説明します。このチュートリアルのオリジナルはこのサイトにあります。始めに、日本語版とも言えるICS MEDIAさんのチュートリアル・サイトの説明を引用します。以下のコードをコピペして、example.html として保存してください。



<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
</head>
<body>
  <canvas id="myCanvas"></canvas>
</body>


  <script>
	// ページの読み込みを待つ
	window.addEventListener('load', init);

	function init() {

	  // サイズを指定
	  const width = 480;
	  const height = 320;

	  // レンダラーを作成
	  const renderer = new THREE.WebGLRenderer({
		canvas: document.querySelector('#myCanvas')
	  });
	  renderer.setPixelRatio(window.devicePixelRatio);
	  renderer.setSize(width, height);

	  // シーンを作成
	  const scene = new THREE.Scene();

	  // カメラを作成
	  const camera = new THREE.PerspectiveCamera(45, width / height);
	  camera.position.set(0, 0, +1000);

	  // 箱を作成
	  const geometry = new THREE.BoxGeometry(400, 400, 400);
	  const material = new THREE.MeshNormalMaterial();
	  const box = new THREE.Mesh(geometry, material);
	  scene.add(box);

	  tick();

	  // 毎フレーム時に実行されるループイベントです
	  function tick() {
		box.rotation.y += 0.01;
		renderer.render(scene, camera); // レンダリング

		requestAnimationFrame(tick);
	  }
	}
  </script>
</html>

Three.jsはHTML5のcanvas要素を利用して、canvas要素はコンテンツを表示する描画エリアを確保します。canvas要素には属性としてid(ID値)を設定する必要があります。以下のように、記述します。

<body>
  <canvas id="myCanvas"></canvas>
</body>

 canvas要素の内容はJavaScriptを使って設定します。

 Three.min.jsはJavaScriptのライブラリですが、このファイルを読み込むことによってはじめてThree.jsが利用できるようになります。CDN(コンテンツ・デリバリー・ネットワーク)で提供されているURLを使います。以下のように、'head’タグの中に記述します。



<head>
  <meta charset="utf-8"/>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
  </head>

 このファイル example.html を開けば、ブラウザに回転する立方体が描けます。ここでは、CodePenを用いて、描画してみます。画面上の「Result」をクリックすると、以下のように、立方体が回転する3Dグラフィックスが描かれます。コードはかなり単純化されています。

See the Pen threes_1 by mashyko (@mashyko) on CodePen.


 JavaScriptの記述の内容について大まかな説明をしましょう。WebGLの処理は'three.min.js'のページの読み込みが終わってから実行させます。addEventListener()関数を使って、ページが読み込み終わったときに実行させたい関数を指定します。この関数init()の中にThree.jsのコードを書いていきます。まずは、レンダラーとシーンを作成します。描画する立体図形の映像を撮るカメラの位置と方向を定めるために、カメラを作成します。「箱を作成」の部分は、直方体を描画しています。'BoxGeometry'が箱を意味します。直方体ではなくて、例えば、球体を描画するときは、'SphereGeometry'を使います。'tick();'の部分は、描画した直方体を回転させる為の記述です。コマンドなど記述の詳細はこのように書くものだ理解します。この中身を少しづつ修正していくと、複雑なグラフィックスも描けるようになります。

 JavaScript API Three.js を用いたCGの作成についての説明はこのページになります。


このページの先頭に行く
Three.jsのページに行く
トップ・ページに行く