鳳 技術部

個人製作とか趣味とか備忘録なあれこれ

webGLで行列計算をしようとした

webGLでの行列

これまでの経験と今回したこと

  1. 過去にライブラリを用いてwindows(DirectX環境)向けにコードを書いたことがあり、そのときに行優先、列優先の話を聞いたことがあった。
  2. ただ、計算内容の詳しいことはやってなかったので、今回調べた。

今回知ったこと

1. そもそも行優先、列優先が2種類あった

・ データの格納方法としての行優先、列優先
前提 1. すべてのデータはメモリ上に格納される
前提 2. メモリは論理的には1列に並んでいる

この二つの前提から1つの結論が導かれる。

行列には行と列とが存在するが、どちらを優先してメモリ上に並べるかを決める必要がある、ということである。

つまり 4x4行列 が次のように存在した場合

m11  m12  m13  m14  
m21  m22  m23  m24  
m31  m32  m33  m34  
m41  m42  m43  m44  

行優先の場合

var mat = new Array(16);

mat[0] // m11
mat[1] // m12
mat[2] // m13
...
mat[15] // m44

列優先の場合

var mat = new Array(16);

mat[0] // m11
mat[1] // m21
mat[2] // m31
...
mat[15] // m44

という風になっている。

GLでは列優先でデータが入る。
・ 計算方法としての行優先、列優先

注意する点はいくつかある。

まず、行優先であれば後ろにかけていくことで計算できるが、列優先では前にかけていくことが必要になる。 また、それぞれの場合で、標準で存在するベクトル型は行優先なら横ベクトル、列優先なら縦ベクトルとして計算しているものと思われる。

例として ローカル座標、ワールド行列、ビュー行列があるものとして、最終的な位置pを求める式は

行優先の場合

 p = Local * World * View 

列優先の場合

p =  World * Local;
p =  View * p 

それぞれ、pから座標を取る。

もっともwebGLjavascriptから触るので適切な関数を作成する必要があるが

GLでは列優先でデータが入る。

2. 行列計算においてOpenGLとDirextXの互換性とWebGL

さて、1.に書いたとが、GLでは常に列優先である。 もっとも、データを転送する際に格納方式が異なるのは困ると思うが、開発するシステム内で適切に管理できるならば、別に計算を行優先ですべて自前で書いてしまっても問題はなさそうである。

すべて自前で書くというのは問題ではないかという突っ込みはおいておく

そのため、ネイティブアプリ開発においてシェーダーに転送する時点で適切に変換できるなら(オーバーヘッドに目を瞑るとして)GLとDirectXに対して共通の処理から得られたデータを転送するのは不可能ではなさそうである。

また、webGLはGLではあるものの、計算等は内部に存在しないため、外部のライブラリを活用するか、自前で作成することが必要になるため、これまでwindowsDirectXしか触っていなかった人がweb開発をする、となった際に、それまでとおなじ感覚で作業したければ、行優先で計算して、列優先のデータ配置で転送するというのは大いにあるかもしれない。

ただし、データを転送する先であるシェーダー(GLSL)は列優先でデータを持ち、また列優先で計算するので、シェーダーとの記述方法をそろえる意味でも列優先で処理するべきかも知れない。

まとめ

OpenGL DirectX WebGL HLSL(DirextXのシェーダ) GLSL(GLのシェーダ)
データ 列優先 行優先 列優先 列優先 列優先
計算 列優先 行優先 数学部分は別途用意 行優先 列優先

基本形状の表示について

できてること

  • webglで単純な何かを2Dで出す
  • 何かを出すのにはインデックスバッファを用いる

やりたいこと

  • 効率よくprimitiveな形状を出す
  • primitiveな形状には2D用のものと3D用のものがある
    • 2d Rectangle Circle 等
    • 3d Box Sphere Cylinder 等
  • 2dのものはどう考えても値がいじられまくる
  • 3dのものは明らかに頂点情報を用意してやる以外に方法がない

考えていること

  1. 基本的な形状用の固定バッファを作る?
  2. それとも基本形状のデータはそのままシェーダに渡せる形式にする?

サンプル

共通事項
  • rect は left top rigth bottom にアクセスできる
  • gl には glのコンテキストが入っている
  • 必要に応じてプリミティブ表示のためにrenderRectが呼ばれる
  • gl に渡す position は vec3 であるため z軸に相当する部分は適当に埋めている
  • glソースコンパイルやuseProgramはここより前に完了しているものとして変数programにそのプログラムオブジェクトが入っているとする

    1 の場合

var buffer = { };
var att = {};

buffer.vp = gl.createBuffer();
buffer.vpd = new Float32Array(12);
buffer.index = gl.createBuffer();
buffer.indexd = new Int16Array( [0 , 1 , 2 , 1 , 2 , 3] );

att.position = gl.getAttribLocation( program, "position");  

renderRect = function(rect)
{
    buffer.vpd[0] = rect.left;
    buffer.vpd[1] = rect.top;
    buffer.vpd[2] = 0;
    buffer.vpd[3] = rect.left;
    buffer.vpd[4] = rect.bottom;
    buffer.vpd[5] = 0;
    buffer.vpd[6] = rect.right;
    buffer.vpd[7] = rect.top;
    buffer.vpd[8] = 0;
    buffer.vpd[9] = rect.right;
    buffer.vpd[10] = rect.bottom;
    buffer.vpd[11] = 0;

    gl.bindBuffer( gl.ARRAY_BUFFER , buffer.vp);
    gl.bindData( gl.ARRAY_BUFFER , buffer.vpd, gl.STATIC_DRAW);
    gl.vertexAttribPointer(att.position,3,gl.FLOAT,0,0);

    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER , buffer.index);
    gl.bindData( gl.ELEMENT_ARRAY_BUFFER , buffer.indexd, gl.STATIC_DRAW);
 
    gl.drawElements(gl.TRIANGLES , rectBuf.index.length, gl.UNSIGNED_SHORT, 0);
}
  • 結局毎回書き換えなければならない
  • 但し各形状が持つデータは最小限でよさそう

2 の場合

var buffer = { };
var att = {};

buffer.index = gl.createBuffer();
buffer.indexd = new Int16Array( [0 , 1 , 2 , 1 , 2 , 3] );

att.position = gl.getAttribLocation( program, "position");  

class Rectangle
{
    constructor(left,top,right,bottom)
    {
         this.vpd  =    new Float32Array(12);
         this.vpd[0] = left;
         this.vpd[1] = top;
         this.vpd[2] = 0;
         this.vpd[3] = left;
         this.vpd[4] = bottom;
         this.vpd[5] = 0;
         this.vpd[6] = right;
         this.vpd[7] = top;
         this.vpd[8] = 0;
         this.vpd[9] = right;
         this.vpd[10] = bottom;
         this.vpd[11] = 0;
    }
}
renderRect = function(rect)
{
    gl.bindBuffer( gl.ARRAY_BUFFER , rect.vp);
    gl.bindData( gl.ARRAY_BUFFER , rect.vpd, gl.STATIC_DRAW);
    gl.vertexAttribPointer(att.position,3,gl.FLOAT,0,0);

    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER , buffer.index);
    gl.bindData( gl.ELEMENT_ARRAY_BUFFER , buffer.indexd, gl.STATIC_DRAW);
 
    gl.drawElements(gl.TRIANGLES , rectBuf.index.length, gl.UNSIGNED_SHORT, 0);
}
  • 明らかにデータ量が増える
  • 明らかにパラメータ変更にかかるコストが増える

まとめ

  1. Rectangleのような2Dの単純な形状では1のほうがよさそうだと感じた
    • RectangleやCircleは2Dでの単純な衝突判定などに用いられるため、単純に更新しやすいほうがよいし、データ量が少ないほうがよいと思われる
  2. それ以外,例えば4頂点すべて を持つ四角形や3Dでの基本形状であれば結局すべての頂点を保持するだろうから、2の形式のほうがよさそうだと思われる
  3. 今回は基本形状で考えたが、3Dモデル等は必然的にモデルが頂点すべてを保持し、管理する2の形式になると思われる  

コードのハイライトとか

技術的なこと書くんだからコード書くけど、忘れそうなのでメモしておく

ハイライトの入れ方

``` 言語名
コード
```

入力例

``` javascript

sample = function()
{
console.log("hello")
}

```

出力例

sample = function()
{
    console.log("hello")
}

備考

` はバッククォートであるから、間違ってシングルクォートで囲んだりしないように

コードのとこだけ黒くするとかそういうことがしたければ cssのほうをいじればいいらしい。変えたくなったらまた調べる