arusu0629のブログ

UnityエンジニアからiOSエンジニアへ

WebGLについて⑪

【前回】 arusu0629.hatenablog.com

ポリゴンのレンダリング

■初期化処理

    // canvasを初期化する際の深度を設定する
    gl.clearDepth(1.0);

    // canvasを初期化
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  • clearDepth
    →3次元空間を扱う場合に奥行きに関する情報をクリアするために使われるメソッド。clearメソッドに震度に関してクリアするために組み込み定数の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 = gl.getAttribLocation(prg, 'position');

    // attributeの要素数(この場合はxyzの3要素)
    var attStride = 3;
  • attLocationattStride
    →頂点シェーダにデータを渡す際に必要となる情報を保持するのに使用する

attLocation: そのデータが何番目のデータなのかを保持するために使う

attStride: データがいくつの要素からなるのかを保持するために使う


■頂点バッファの生成と通知

    // モデル(頂点)データ
    var vertex_position = [
        0.0,  1.0, 0.0,
        1.0,  0.0, 0.0,
       -1.0,  0.0, 0.0
    ];

    // VBOの生成
    var vbo = create_vbo(vertex_position);

    // VBOをバインド
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);

    // attribute属性を有効にする
    gl.enableVertexAttribArray(attLocation);

    // attribute属性を登録
    gl.vertexAttribPointer(attLocation, attStride, gl.FLOAT, false, 0, 0);
  • gl.enableVertexAttribArray
    →目的の属性を有効にすることが出来る
  • gl.vertexAttribPointer
    →シェーダにデータを登録する
    第1引数 :何番目のであるかのインデックス
    第2引数 :データの要素数
    第3引数 :データの型
    第4~6引数:基本的にはこの形で、メモリ上の参照を駆使して特殊なデータの受け渡し方をしたい場合に使用することがある

    ※このメソッドを実行する際に対象のVBOを必ずバインドする必要がある


    ■座標変換行列の生成と通知
    // 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.getUniformLocation
    →attribute変数同様にuniform変数のインデックスを取得するために使用される

  • gl.uniformMatrix4fv
    →uniform変数のインデックスにデータを登録して頂点シェーダにデータが正しく受け渡せるようにする。第2引数のブール値は行列を転置するかどうかの値(※trueにするとクラッシュする場合がある)



■モデルの描画とコンテキストの再描画

    // モデルの描画
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    // コンテキストの再描画
    gl.flush();
  • gl.drawArrays
    →モデルをバッファ上に描画する
    ※このメソッド実行時にはまだ画面上にはポリゴンは描画されておらず下記で説明するflushが実行されることでレンダリング結果が画面上に描画される

    第1引数:頂点をどのように利用して描画するかの組み込み定数
    第2引数:何番目の頂点から利用するかのオフセット値
    第3引数:いくつの頂点を描画するかの値

  • flush
    →コンテキストの再描画



■全ソースコード
■実行結果f:id:arusu0629:20180401192012p:plain

WebGLについて⑩

【前回】 arusu0629.hatenablog.com

座標変換行列

モデル変換において、移動・回転・拡大縮小を行う順序には十分に注意しないといけない
具体的には、拡大縮小 -> 回転 -> 移動 という順序で変換を行えば、回転・拡大縮小した状態のモデルを任意の位置に移動させることが出来る

ただし、OpenGLには行列が列オーダーになっているため、掛け合わせる順序が真逆になる

つまり、拡大縮小 -> 回転 -> 移動 という順序で掛け合わせると結果は意図したものと全く違うものになるため、正しくは 移動 -> 回転 -> 拡大縮小 という順序で掛け合せれば良いということ

■モデル・ビュー・プロジェクション行列の掛け合わせる順序
mvpMatrixと各変換行列を掛け合わせたものを表記することが多いが、掛け合わせる順序はmvpではなく、 p > v > m の順序で掛け合わせる

【続き】 arusu0629.hatenablog.com

WebGLについて⑨

【前回】 arusu0629.hatenablog.com

モデルデータと頂点属性
  • 頂点属性
    →頂点が持つ様々な要素のこと
    例:位置情報、頂点色、法線、テクスチャ座標


■VBOの生成

  • まずVBOの元になるバッファオブジェクトをcreateBufferメソッドを用いて生成する
  • バッファを操作するにはWebGLにバインドする必要があるためbindBufferメソッドを使ってバインドする。第1引数に組み込み定数のgl.ARRAY_BUFFERを指定することでVBOが生成できる
  • 続いて生成したVBOに頂点情報として渡されたdataをセットするためにbinderDataを実行する。その際にFloat32Arrayで配列を作り直しているのは浮動小数点を扱うためである
  • gl.STATIC_DRAWという組み込み定数はそのバッファがどのような頻度で内容を変更されてるのかを定義する。VBOの場合は、基本的にモデルデータはそのままで何度も利用することになるためこの定数を指定する
  • WebGLバインド出来るバッファは1度に1つだけなので、他のバッファに操作を加える場合は適宜バッファをバインドし直すため、最後にバッファのバインドを無効にして予期せぬエラーを発生させないようにする



【まとめ】
WebGLでは頂点について自由に属性を付加することが出来る。付加した属性の数だけVBOが必要になる
頂点属性のデータは1次元配列として用意する。その配列データをもとにVBOを生成する
まずはバッファを作り、WebGLにバインドすることで操作を可能にした後で、配列データをバッファにセットしてVBOを生成する。バインドは1度に1つしか出来ないため、操作が終わった後にはバインドを解除して予期せぬエラーを防ぐ

【続き】 arusu0629.hatenablog.com

WebGLについて⑧

【前回】 arusu0629.hatenablog.com

シェーダのコンパイルとリンク

■ポリゴンを描画するまで〜

  1. HTMlからcanvas要素を取得
  2. canvasからWebGLコンテキストの取得
  3. シェーダのコンパイル
  4. モデルデータを用意
  5. 頂点バッファ(VBO)の生成と通知
  6. 座標変換行列の生成と通知
  7. 描画命令の発行
  8. canvasを更新してレンダリング

1,2については以下の記事でやってきたので割愛します。 arusu0629.hatenablog.com

今回は3.についての記事です!

※上記プログラムはWebGLコンテキストを既に取得しているとみなして書いています

  • シェーダを生成するにはWebGLのメソッドであるcreateShaderを使う
  • 頂点シェーダの場合はgl.VERTEX_SHADERを引数に指定
  • フラグメントシェーダではgl.FRAGMENT_SHADERを引数に指定
  • 生成したシェーダをソースに割り当てるためにshaderSourceメソッドを実行する
  • シェーダをコンパイルするにはcompileShaderメソッドに引数として生成したシェーダを渡してあげればOK


プログラムオブジェクト

WebGLプログラムと各シェーダとのデータのやり取りを管理してくれるオブジェクト

  • まずはプログラムオブジェクトを生成するためにcreateProgramメソッドを行う
  • 生成したプログラムオブジェクトに各シェーダを割り当てる
  • その後はlinkProgramメソッドを使ってシェーダをリンクする
  • リンクが上手く出来たかを組み込み定数のLINK_STATUSを見て判断する
  • リンクが問題なければuseProgramメソッドを実行。これをしないとWebGLに正しく認識されない

【まとめ】
ここまでの内容として

  1. HTMLソースを用意
  2. javascriptファイルの参照とシェーダのソースを記述
  3. javascriptにはシェーダの生成・コンパイルを行うメソッドとリンクを管理するプログラムオブジェクトのメソッドを用意

【続き】 arusu0629.hatenablog.com

WebGLについて⑦

【前回】 arusu0629.hatenablog.com

頂点バッファの基礎
  • ローカル座標
    →頂点がどのように配置されているのかを表した座標
    例:(1.0, 0.0, 0.0) ⇔ 原点からX方向に1.0の距離に存在する
    ※原点を(0.0, 0.0, 0.0)とした場合

  • 頂点バッファ(VBO:vertex buffer object)
    WebGLのプログラム内で、頂点のローカル座標を格納するための入れ物
    他にもフレームバッファやインデックスバッファなどがある

    頂点バッファの役割には位置情報を保持する以外にも、頂点が持つ法線の情報や、に関する情報、他にもテクスチャ座標や頂点の重さなど、ありとあらゆる頂点に関する情報を格納することが出来る

■頂点バッファの処理の流れ

  1. 頂点の各情報を配列に格納
  2. WebGLのメソッドを使ってVBOを作成
  3. WebGLのメソッドを使ってVBOに配列のデータを転送
  4. 頂点シェーダ内のattribute変数とVBOを紐付ける

1.について
頂点の位置情報には(x, y, z)の3つの情報が必要なため頂点数×3の要素数の配列が必要
ここで配列は多次元配列にせず1次元配列にすること。VBOの生成には1次元配列を使う

【まとめ】
WebGLでは実装したい頂点情報の数だけ、頂点バッファ(VBO)が必要になる
頂点情報というのは位置情報だけでなく、色や法線の情報などありとあらゆる頂点に関する情報をVBOに格納することが出来る
頂点シェーダ内で定義されたattribute修飾子付きの変数にVBOを紐付けることが必要

【続き】 arusu0629.hatenablog.com

WebGLについて⑥

【前回】
arusu0629.hatenablog.com

※この章は少し長い内容となっております...mm

シェーダの記述と基礎

WebGLではプログラマブルシェーダの一種であるシェーダ言語が実装されている。それがGLSL(OpenGL Shading Language)である

GLSLを使って2種類のシェーダ、頂点シェーダとフラグメントシェーダを記述することが出来る。原則的に先に頂点シェーダが呼ばれます

頂点シェーダは、頂点に関する情報のすべてを渡すことが出来る
例えば、頂点の位置情報や、頂点が持つ法線、テクスチャ座標、頂点の色など、頂点に関する全てをシェーダに渡すことが可能

次にフラグメントシェーダは画面にどんな色を出力すればよいのかを決めることが出来る
画面上のピクセルそれぞれに対して、最終的に出力される色を操作してくれる

【ミニまとめ】
頂点シェーダは頂点に関する情報を、フラグメントシェーダは画面上の色の情報をそれぞれ処理するもの

■ものすごく単純な頂点シェーダの記述例
attribute vec3 position;

void main(void) {
    // 組み込み変数gl_Positionに頂点データを渡す
    gl_Position = position
}

attributeという修飾子を付けて宣言された変数(上記の場合はposition)が頂点の情報をシェーダ側で受け取る変数になる。つまりWebGLのプログラム側でpositionという名前をつけ、あらかじめデータを仕込んでおきシェーダに渡す


頂点シェーダは頂点に関する処理をするため、座標変換も頂点シェーダの仕事の1つです
どこまでを頂点シェーダに任せるかは自由ですが、WebGLのプログラム側でモデル・ビュー・プロジェクションのそれぞれの行列を生成して掛け合わせておき、最終的に出来上がった座標変換行列を頂点シェーダに渡すのが一般的

■座標変換行列をシェーダに渡す記述例
attribute vec3 position;
uniform mat4 mvpMatrix;

void main(void) {
    gl_Position = mvpMatrix * position;
}

uniform修飾子全ての頂点に対して一律に処理される情報を渡すことが可能になる
ここで出てきているmvpMatrixはモデル・ビュー・プロジェクションそれぞれの変換行列をかけ合わせた座標変換行列。その変換行列をWebGL側からGLSLに渡してあげる

attribute, uniformの2つの修飾子に加えて、varying修飾子があります
このvarying修飾子の使いみちは頂点シェーダとフラグメントシェーダの橋渡しです

例えば頂点シェーダが持つ頂点の色情報をフラグメントシェーダで使用したい場合などに使われます。(以下例)

頂点シェーダ
attribute vec4 position;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec4 vColor

void main(void) {
    vColor = color;
    gl_Position = mvpMatrix * position;
}


続いてvarying修飾子付きの変数vColorを受け取るフラグメントシェーダ

フラグメントシェーダ
varying vec4 vColor

void main(void) {
    // フラグメントシェーダの組み込み変数gl_FragColorに色情報を入力する
    gl_FragColor = vColor;
}


このように頂点シェーダが持つ頂点の色情報をフラグメントシェーダに渡す際にvarying修飾子付きの変数を用いる。頂点シェーダで必須だったgl_Positionへのデータの代入と似たような形で、フラグメントシェーダにおいてはgl_FragColorにデータを入力する

【まとめ】

  • 頂点シェーダとフラグメントシェーダはGLSLを使って記述でき、基本的に双方がセットの形で使用する
  • シェーダ内部では必ずmain関数を用意してシェーダの行う処理を記述する
  • WebGL側から頂点の位置情報や色情報、座標変換行列などをシェーダに渡す際にattribute, uniformなどの特別な修飾子付きの変数を用意する必要がある
  • そして頂点シェーダからフラグメントシェーダにデータを渡す際にはvarying修飾子付きの変数を用意することで値を渡すことが出来る
  • 頂点シェーダは組み込み変数であるgl_Positionへのデータ代入が必須であり、またフラグメントシェーダはgl_FragColorに出力する色のデータを基本的に代入する必要がある


【続き】 arusu0629.hatenablog.com

WebGLについて⑤

【前回】 arusu0629.hatenablog.com

コンテキストの初期化

コンテキストの取得から初期化まで

  1. htmlソース要素をgetElementByIdを使って取得

  2. 取得したcanvas要素を使ってWebGLコンテキストを取得する。これをするにはgetContextメソッドを利用する

  3. 画面をクリアするにはclearメソッドを使い、引数には画面上の色をクリアするので組み込み定数のCOLOR_BUFFER_BITのみを指定する


上記例のJavaScriptコード

script.js

onload = function(){
    // canvasエレメントを取得
    var c = document.getElementById('canvas')
    // 画面サイズを適当に決める
    c.width = 500
    c.height = 300

    // webglコンテキストを取得
    var gl = c.getContext('webgl') || c.getContext('experimental-webgl');

    // canvasを黒でクリア(初期化)する
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
};

このJavaScriptコードをhtml側で読み込めばブラウザで500×300の黒い領域が表示される(添付図参照)

sample.html

<html>
  <head>
    <title>WebGL TEST</title>
    <script src="script.js" type="text/javascript"></script>
  </head>
  <body>
    <canvas id="canvas"></canvas>
  </body>
</html>


【添付図】 f:id:arusu0629:20180331182731p:plain

【続き】 arusu0629.hatenablog.com