プログラミングでかっこいい映像を作りたいととりくみ始めた『GLSL(シェーダー)』。
インターネットで『シェーダー』を使うには、
『WebGL(ウェブジーエル)』なる仕組みを使えばいいのですが、
という意見も多いようで。
実際の現場でも、
『生のWebGL』は記述量が多いので、ライブラリを使うことが多いようです。
WebGLは正式には「WebGL API」のことですが、素のAPIのままだと記述量が膨大になりどうしてもスピード感が出にくいのでライブラリを使うことがほとんどです。
WebGLコンテンツの開発フローと抑えどころ
- 『WebGL』を使うためのライブラリ・・Three.jsなど
『Three.js』参考記事
実際にはライブラリを併用しつつ作り込むことになるんだろうなと思いつつ、
難しいと言われる『生のWebGL』の仕組みも知っておいたほうがいいだろうということで、
ざっくりとまとめてみることにしました。
【WebGL】入門 描画までの流れ
『WebGL』を調べたことがあれば150%くらいの人がみていると思われるこちらのサイトを参考にまとめています。
『生のWebGL』で画面に描画するための流れは以下。
- HTML に canvas エレメントを明記
- 初期化処理
- シェーダーのコンパイル
- シェーダーとプログラムオブジェクトの生成
- 頂点バッファ( VBO )の生成と通知
- 座標変換行列の生成と通知
- 描画命令の発行
- canvas を更新してレンダリング
順番に見ていきます。
【WebGL】入門その1 HTMLにcanvasエレメントを明記
『WebGL』はホームページなどで表示されるために使われるので、
『HTML』でページを作りつつ、
『canvasタグ』を使って、『canvas』の中に描画していくことになります。
// index.html <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>wgld.org WebGL sample 002</title> <script src="script.js" type="text/javascript"></script> <script src="https://wgld.org/j/minMatrix.js" type="text/javascript"></script> </head> <body> <canvas id="canvas"></canvas> </body> </html>
【WebGL】入門その2. 初期化処理
続いて初期化処理。
『JavaScript』ファイルに書いていきます。
getContext で『WebGL』を取得して、色や深度(奥行き)を初期化しています。
// script.js // canvasエレメントを取得 var c = document.getElementById('canvas'); c.width = 300; c.height = 300; // webglコンテキストを取得 var gl = c.getContext('webgl') || c.getContext('experimental-webgl'); // canvasを初期化する色を設定する gl.clearColor(0.0, 0.0, 0.0, 1.0); // canvasを初期化する際の深度を設定する gl.clearDepth(1.0); // canvasを初期化 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
【WebGL】入門その3. シェーダーのコンパイル
3D画面を描写する役割の『シェーダー(Shader)』には大きく2つの種類があります。
- 頂点シェーダー(Vertex Shader) ・・頂点情報
- フラグメントシェーダー(Fragment Shader)・・色情報
書く順番は決まっていて、頂点シェーダー -> フラグメンントシェーダー になります。
『シェーダー(GLSL)』の内容は 『HTML』 側に書いていきます。
// index.html <html> <head> <title>WebGL TEST</title> <script src="script.js" type="text/javascript"></script> <script src="minMatrix.js" type="text/javascript"></script> <script id="vs" type="x-shader/x-vertex"> attribute vec3 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor; void main(void){ vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <script id="fs" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void){ gl_FragColor = vColor; } </script> </head> <body> <canvas id="canvas"></canvas> </body> </html>
【WebGL入門】 頂点シェーダー側
『頂点シェーダー』側に3種類の変数が設定されています。
- attribute変数 ・・ 頂点毎に異なる情報を設定
- uniform変数 ・・全ての頂点に対しまとめて処理
- varying変数 ・・頂点シェーダからフラグメントシェーダへの受け渡し
『シェーダー』はとにかく『頂点(Vertex)』が大事で、
『頂点』にいろんな『属性(attribute)』をくっつけることができます。
- 座標位置
- 色
- 法線(光の反射で使う)
- テクスチャ座標
などなど。
今回は 座標位置 と 色 をそれぞれ指定しています。
- 座標位置は(x, y, z)と3つの値があるので vec3 (ベクトル3)
- 色情報は( r, g, b, a)(赤緑青と透過度のアルファ)で4つなので vec4 (ベクトル4)です。
ベクトル関連記事
『unifrom』変数は頂点全体に関わる処理で、移動や回転をするための『行列(Matrix)』を指定しています。
『行列』は(x,y,z)の3つの値と思いがちですが、
視界よりも外にあるかどうかを判定するための『w』を含めて、
4×4の『行列』になるので、『mat4』とつけるようになっています。
- w ・・ 同次座標系。視界よりも外にあるかどうか
同次座標系の関連記事
深度値と座標系について理解する | wgld.org
『varying』変数を使うことで、
色情報など、頂点シェーダー -> フラグメントシェーダーに移すことができます。
最後に 『gl_Position』 を指定して『頂点シェーダ』側は完了です。
【WebGL入門】 フラグメントシェーダー側
precision mediump は、描画の精度を指定しています。
- lowp ・・精度低
- mediump ・・精度中
- highp ・・精度高
フラグメントシェーダー側にも『varying』変数を使うことで、
頂点シェーダー -> フラグメンントシェーダー の色情報を受け渡ししています。
最後に 『gl_FragColor』 で色情報を指定して完了です。
【WebGL】入門その4. シェーダーとプログラムオブジェクトの生成
『JavaScript』側でも『シェーダー』の記載が必要です。
// script.js // 頂点シェーダとフラグメントシェーダの生成 var v_shader = create_shader('vs'); var f_shader = create_shader('fs'); // プログラムオブジェクトの生成とリンク var prg = create_program(v_shader, f_shader); // attributeLocationを配列に取得 var attLocation = new Array(2); attLocation[0] = gl.getAttribLocation(prg, 'position'); attLocation[1] = gl.getAttribLocation(prg, 'color'); // attributeの要素数を配列に格納 var attStride = new Array(2); attStride[0] = 3; attStride[1] = 4;
- プログラムオブジェクト・・『頂点シェーダ』と『フラグメントシェーダ』の紐付け
頂点に紐づける属性(情報)は、順番を指定しています。
- attLocation ・・ 頂点に紐づける順番。何番目のデータかを指定
- attStride ・・データがいくつの要素からなるか。(座標は3で色は4)
【WebGL】シェーダー作成
『シェーダー』を生成する自作関数がこちら。
// シェーダを生成する関数 function create_shader(id){ // シェーダを格納する変数 var shader; // HTMLからscriptタグへの参照を取得 var scriptElement = document.getElementById(id); // scriptタグが存在しない場合は抜ける if(!scriptElement){return;} // scriptタグのtype属性をチェック switch(scriptElement.type){ // 頂点シェーダの場合 case 'x-shader/x-vertex': shader = gl.createShader(gl.VERTEX_SHADER); break; // フラグメントシェーダの場合 case 'x-shader/x-fragment': shader = gl.createShader(gl.FRAGMENT_SHADER); break; default : return; } // 生成されたシェーダにソースを割り当てる gl.shaderSource(shader, scriptElement.text); // シェーダをコンパイルする gl.compileShader(shader); // シェーダが正しくコンパイルされたかチェック if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){ // 成功していたらシェーダを返して終了 return shader; }else{ // 失敗していたらエラーログをアラートする alert(gl.getShaderInfoLog(shader)); } }
- gl.createShader でシェーダーをつくって、
- g.shaderSource でソース? を割り当てて、
- gl.compileShader でコンパイルして
- gl.getShaderParameter でチェックして
OKなら『シェーダー』返して、NGならログを出すと。
- gl.getShaderInfoLog ログを出す
関数名が英語そのままなので読んでいくとわかりやすいかもです。
【WebGL】プログラムオブジェクトの作成
- プログラムオブジェクト・・『頂点シェーダ』と『フラグメントシェーダ』の橋渡し
コードは以下。
// プログラムオブジェクトを生成しシェーダをリンクする関数 function create_program(vs, fs){ // プログラムオブジェクトの生成 var program = gl.createProgram(); // プログラムオブジェクトにシェーダを割り当てる gl.attachShader(program, vs); gl.attachShader(program, fs); // シェーダをリンク gl.linkProgram(program); // シェーダのリンクが正しく行なわれたかチェック if(gl.getProgramParameter(program, gl.LINK_STATUS)){ // 成功していたらプログラムオブジェクトを有効にする gl.useProgram(program); // プログラムオブジェクトを返して終了 return program; }else{ // 失敗していたらエラーログをアラートする alert(gl.getProgramInfoLog(program)); } }
- gl.createProgram でプログラムオブジェクトつくって
- gl.attachShader でシェーダーをくっつけて
- gl.linkProgram でシェーダーをリンクさせて
- gl.getProgramParameter でリンクのチェックして
- OKなら gl.useProgram で有効にして返す
- NGなら gl.getProgramInfoLog でエラーだす
という流れで。
【WebGL】入門その5. 頂点バッファ( VBO )の生成と通知
『WebGL』はとにかく『頂点』が大事で、
頂点にいろんな属性(情報)をくっつけて処理します。
- 座標位置
- 色
- 法線(光の反射で使う)
- テクスチャ座標
などなど。
で、それぞれの属性毎に、
『VBO(頂点バッファオブジェクト)』なるものをつくって情報を渡すそうです。
『VBO』生成の流れがイメージしづらなかったので図をつくってみました。
- バッファ(入れ物)をつくって
- WebGLにバインドして(くっつけて)
- 属性データ(配列)をセットして
- VBO として生成されるようです。
これを頂点の属性毎に繰り返すそうです。
コードは以下。
// VBOを生成する関数 function create_vbo(data){ // バッファオブジェクトの生成 var vbo = gl.createBuffer(); // バッファをバインドする gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // バッファにデータをセット gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); // バッファのバインドを無効化 gl.bindBuffer(gl.ARRAY_BUFFER, null); // 生成した VBO を返して終了 return vbo; }
- gl.createBuffer でバッファをつくって
- gl.bindBuffer でバッファをWebGLにバインドして
- gl.bufferData で配列データをセットして・・ここでVBO生成
- gl.bindBuffer でバインドを無効化して
- VBOを返して終わり
になります。
『VBO』を生成するユーザー定義関数を使って、
頂点の属性情報(配列)をつくって、『VBO』を生成しています。
コードは以下。
// 頂点の位置情報を格納する配列 var vertex_position = [ 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0 ]; // 頂点の色情報を格納する配列 var vertex_color = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0 ]; // VBOの生成 var position_vbo = create_vbo(vertex_position); var color_vbo = create_vbo(vertex_color); // VBOをバインドし登録する(位置情報) gl.bindBuffer(gl.ARRAY_BUFFER, position_vbo); gl.enableVertexAttribArray(attLocation[0]); gl.vertexAttribPointer(attLocation[0], attStride[0], gl.FLOAT, false, 0, 0); // VBOをバインドし登録する(色情報) gl.bindBuffer(gl.ARRAY_BUFFER, color_vbo); gl.enableVertexAttribArray(attLocation[1]); gl.vertexAttribPointer(attLocation[1], attStride[1], gl.FLOAT, false, 0, 0);
- gl.bindBuffer
- gl.enableVertexAttribArray
- gl.vertexAttribPointer
の3つはセットで使うと思ってればいいのかなと思います。
【WebGL】入門その6. 座標変換行列の生成と通知
3D空間で物体を回転させるには、『回転行列』を使う必要があります。
参考記事
『回転行列』は書く内容が決まっているので、
wgld.org サイトの方であらかじめ『行列』用のコードがあるので
まるっとお借りします。
// minMatrix.js を用いた行列関連処理 // matIVオブジェクトを生成 var m = new matIV(); // 各種行列の生成と初期化 var mMatrix = m.identity(m.create()); var vMatrix = m.identity(m.create()); var pMatrix = m.identity(m.create()); var mvpMatrix = m.identity(m.create()); // ビュー座標変換行列 m.lookAt([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0], vMatrix); // プロジェクション座標変換行列 m.perspective(90, c.width / c.height, 0.1, 100, pMatrix); // 各行列を掛け合わせ座標変換行列を完成させる m.multiply(pMatrix, vMatrix, mvpMatrix); m.multiply(mvpMatrix, mMatrix, mvpMatrix); // uniformLocationの取得 var uniLocation = gl.getUniformLocation(prg, 'mvpMatrix'); // uniformLocationへ座標変換行列を登録 gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
minMatrix.jsと座標変換行列 | wgld.org
【WebGL】入門その7. 描画命令の発行
最後はサクッと、下記2つで描画されます。
// モデルの描画
gl.drawArrays(gl.TRIANGLES, 0, 3);
// コンテキストの再描画
gl.flush();
描画命令を発行することを『ドローコール』と呼ぶそうで、
『ドローコール』が増えれば増えるほど処理も重くなるようで、
それを回避するために、『インスタンシング』や『GPGPUパーティクルシステム』などありますが、
それは別の記事で。
【WebGL】入門その8. canvas を更新してレンダリング
ここは恒常ループ処理かな、
JavaScript の setTImeout を使ってずっと描画させる方法かなと思います。
【WebGL】入門 全体のコード
全体のコードは以下。
// index.html <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>wgld.org WebGL sample 003</title> <script src="script.js" type="text/javascript"></script> <script src="https://wgld.org/j/minMatrix.js" type="text/javascript"></script> <script id="vs" type="x-shader/x-vertex"> attribute vec3 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor; void main(void){ vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <script id="fs" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void){ gl_FragColor = vColor; } </script> </head> <body> <canvas id="canvas"></canvas> </body> </html>
// script.js onload = function(){ // canvasエレメントを取得 var c = document.getElementById('canvas'); c.width = 300; c.height = 300; // webglコンテキストを取得 var gl = c.getContext('webgl') || c.getContext('experimental-webgl'); // canvasを初期化する色を設定する gl.clearColor(0.0, 0.0, 0.0, 1.0); // canvasを初期化する際の深度を設定する gl.clearDepth(1.0); // canvasを初期化 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 頂点シェーダとフラグメントシェーダの生成 var v_shader = create_shader('vs'); var f_shader = create_shader('fs'); // プログラムオブジェクトの生成とリンク var prg = create_program(v_shader, f_shader); // attributeLocationを配列に取得 var attLocation = new Array(2); attLocation[0] = gl.getAttribLocation(prg, 'position'); attLocation[1] = gl.getAttribLocation(prg, 'color'); // attributeの要素数を配列に格納 var attStride = new Array(2); attStride[0] = 3; attStride[1] = 4; // 頂点の位置情報を格納する配列 var vertex_position = [ 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0 ]; // 頂点の色情報を格納する配列 var vertex_color = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0 ]; // VBOの生成 var position_vbo = create_vbo(vertex_position); var color_vbo = create_vbo(vertex_color); // VBOをバインドし登録する(位置情報) gl.bindBuffer(gl.ARRAY_BUFFER, position_vbo); gl.enableVertexAttribArray(attLocation[0]); gl.vertexAttribPointer(attLocation[0], attStride[0], gl.FLOAT, false, 0, 0); // VBOをバインドし登録する(色情報) gl.bindBuffer(gl.ARRAY_BUFFER, color_vbo); gl.enableVertexAttribArray(attLocation[1]); gl.vertexAttribPointer(attLocation[1], attStride[1], gl.FLOAT, false, 0, 0); // minMatrix.js を用いた行列関連処理 // matIVオブジェクトを生成 var m = new matIV(); // 各種行列の生成と初期化 var mMatrix = m.identity(m.create()); var vMatrix = m.identity(m.create()); var pMatrix = m.identity(m.create()); var mvpMatrix = m.identity(m.create()); // ビュー座標変換行列 m.lookAt([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0], vMatrix); // プロジェクション座標変換行列 m.perspective(90, c.width / c.height, 0.1, 100, pMatrix); // 各行列を掛け合わせ座標変換行列を完成させる m.multiply(pMatrix, vMatrix, mvpMatrix); m.multiply(mvpMatrix, mMatrix, mvpMatrix); // uniformLocationの取得 var uniLocation = gl.getUniformLocation(prg, 'mvpMatrix'); // uniformLocationへ座標変換行列を登録 gl.uniformMatrix4fv(uniLocation, false, mvpMatrix); // モデルの描画 gl.drawArrays(gl.TRIANGLES, 0, 3); // コンテキストの再描画 gl.flush(); // シェーダを生成する関数 function create_shader(id){ // シェーダを格納する変数 var shader; // HTMLからscriptタグへの参照を取得 var scriptElement = document.getElementById(id); // scriptタグが存在しない場合は抜ける if(!scriptElement){return;} // scriptタグのtype属性をチェック switch(scriptElement.type){ // 頂点シェーダの場合 case 'x-shader/x-vertex': shader = gl.createShader(gl.VERTEX_SHADER); break; // フラグメントシェーダの場合 case 'x-shader/x-fragment': shader = gl.createShader(gl.FRAGMENT_SHADER); break; default : return; } // 生成されたシェーダにソースを割り当てる gl.shaderSource(shader, scriptElement.text); // シェーダをコンパイルする gl.compileShader(shader); // シェーダが正しくコンパイルされたかチェック if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){ // 成功していたらシェーダを返して終了 return shader; }else{ // 失敗していたらエラーログをアラートする alert(gl.getShaderInfoLog(shader)); } } // プログラムオブジェクトを生成しシェーダをリンクする関数 function create_program(vs, fs){ // プログラムオブジェクトの生成 var program = gl.createProgram(); // プログラムオブジェクトにシェーダを割り当てる gl.attachShader(program, vs); gl.attachShader(program, fs); // シェーダをリンク gl.linkProgram(program); // シェーダのリンクが正しく行なわれたかチェック if(gl.getProgramParameter(program, gl.LINK_STATUS)){ // 成功していたらプログラムオブジェクトを有効にする gl.useProgram(program); // プログラムオブジェクトを返して終了 return program; }else{ // 失敗していたらエラーログをアラートする alert(gl.getProgramInfoLog(program)); } } // VBOを生成する関数 function create_vbo(data){ // バッファオブジェクトの生成 var vbo = gl.createBuffer(); // バッファをバインドする gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // バッファにデータをセット gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); // バッファのバインドを無効化 gl.bindBuffer(gl.ARRAY_BUFFER, null); // 生成した VBO を返して終了 return vbo; } };
こちらの記事にまるっと掲載されています。
【WebGL】入門 まとめ
wgld.org のページは本当にすごい情報量で、
詳しい分なかなか難しかったりするので、
なんども読みつつ、ノートをとりつつ、
ようやく図にできた感があります。
生の『WebGL』はHTMLとJavaScriptを足してざっくり220行。
対して『Three.js』は約50行。
参考記事
便利ライブラリの助けを借りつつ、
ここぞという場面では『生WebGL』も使いこなせるようになるといいんでしょうね。
『Three.js』関係ではこんな記事も読まれています。
1. 3Dプログラムの基本を【Three.js】でまとめてみた【初心者向け】2. 【Three.js】で『GLSL(シェーダー)』を使う環境構築の仕方 【初心者向け】
3. 【WebGL】入門 わかりやすく【図解】してみた
4. 【Three.js】用語や関数のまとめ【3Dプログラム】※随時更新
『GLSL(シェーディング)』関係ではこんな記事も読まれています。
1. 【GLSL】プログラムでかっこいい映像をつくりたい! 〜『TouchDesigner』を見据えて2. 【WebGL】入門 わかりやすく【図解】してみた
3. 【OpenGL】と【DirectX】のバージョンをまとめてみた【シェーダーメイン】【初心者向け】
4. 【GLSL(シェーディング)】でよく使う関数とユーザー関数のまとめ※随時更新
5. 【GLSL】プログラムでかっこいい映像をつくるには『レイマーチング』なるものを覚えればいいらしい
6. 【GLSL】『レイマーチング』入門その1 距離関数とレイとカメラの設定
7. 【GLSL】『レイマーチング』入門(2) 立体的に見せる方法〜光の反射は『内積』で〜
8. 【GLSL】『レイマーチング』入門(3) 距離関数を使ってみる・回転・合成・量産
9. 【TouchDesigner】で『GLSL』を使う方法まとめ【画像あり】
10. 【TouchDesigner】『GLSL MAT』の使い方 3次元でぐりぐり動かしてみる
11. 【GLSL】波のつくり方簡易まとめ。波もプログラムでつくれます【コピペスタイル】
アオキのツイッターアカウント。
この記事へのコメントはありません。