機械学習基礎理論独習

誤りがあればご指摘いただけると幸いです。数式が整うまで少し時間かかります。リンクフリーです。

勉強ログです。リンクフリーです
目次へ戻る

WebGL2メモ

はじめに

本記事にはWebGL2に関する備忘録です。
WebGL2の習得に有用なサイトについても記載します。
私が後から参照できればよいので、わかりにくいかもしれません。

有用なサイト

・「床井研究室」でググってください。床井先生はCGの神様です。
wgld.org はい、これブックマークしてください。
webgl fundamental
コンピュータグラフィックス特論Ⅱ (大学院講義) 九州工業大学の授業のようです。私は影の箇所を参考にしました。
ゲームグラフィックス特論 A / B 床井先生の授業です。
ICS MEDIA - WebGLに関するハイレベルな記事があります。まだほとんど読んでません。がメモ!

シェーダの切り替え

頂点シェーダとフラグメントシェーダをまとめて切り替える方法です。
まず、gl.createProgram(); gl.attachShader(); gl.linkProgram(); シェーダに関する初期化を行います。

後は、そのシェーダを使うときに gl.useProgram(); を呼べばシェーダを切り替えることができます。

この方法はまとめて2つのシェーダを切り替える方法です。
頂点シェーダとフラグメントシェーダのどちらか一方を切り替える場合は、gl.dettachShader(); を呼ぶんだと思います。

デフォルトのフレームバッファのカラーバッファにクリアせずに随時書き込む方法

デフォルトのフレームバッファのカラーバッファは gl.clear() メソッドを呼ばずに gl.drawArrays() を呼ぶとカラーバッファがクリアされてしまいます。
(少なくとも見た目上はクリアされています。)
それを回避するために、canvas.getContext('webgl2', { preserveDrawingBuffer: true }); のように呼べばよいです。

バッファデータの動的切り替え

gl.bufferData() 後に配列の中身を変更した場合、再度gl.bufferData() を呼ぶのではなく、gl.bufferSubData() を呼ぶべき。
なお、gl.bufferData() の第三引数にはgl.DYNAMIC_DRAWを指定してやります。
wgld.orgのVBOを逐次更新しながら描画するの記事を参考にしました。

texImage2Dについて

mdnで調べると、以下の構文が書いてありました。

// WebGL1
texImage2D(target, level, internalformat, width, height, border, format, type, pixels)
texImage2D(target, level, internalformat, format, type, pixels)


// WebGL2
texImage2D(target, level, internalformat, width, height, border, format, type, offset)
texImage2D(target, level, internalformat, width, height, border, format, type, source)
texImage2D(target, level, internalformat, width, height, border, format, type, srcData, srcOffset)

下から2番目の呼び出し方をすればよいと思います。
width,heightには、画像であれば、そのまま画像のサイズを指定すればOKです。

gl.viewport() はよばなくても大丈夫

gl.viewport() はよばなくても動く。canvasの一部に書く時のみ必要となるのかな?

テクスチャの座標は左上隅を原点と考えてよい

よって、左回りは(0,0) -> (0,1) -> (1,1) -> (1,0) である。

canvasをテクスチャにしたときの更新方法

canvasの描画内容を更新した後、以下のようにバインドしなおす必要がある。

gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, canvas.width, canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
gl.bindTexture(gl.TEXTURE_2D, null);   

テクスチャのバインドの仕方

安全な方法を書きます。

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture0);
gl.uniform1i(uniLocation[1], 0);

gl.activeTexture()の後にgl.bindTexture()を呼ぶ必要があります。
gl.uniform1i(uniLocation[1], 0);の呼ぶ場所はどこでも構わないようです。
gl.activeTexture(gl.TEXTUREn);とgl.uniform1i(uniLocation[1], n); のnは数値であり、一致している必要があります。
nはいくつまで使えるのか気になりますが、私の場合は使っても0,1,2ぐらいなので調べてもいません。

IBO(インデックスバッファオブジェクト)の型を指定する方法

drawElementsで描画するときにIBOを使いますが、この時、配列の大きさによって型を変えた方が良いでしょう。
配列の大きさが65536(2の16乗)以下の場合、以下のような2バイトに対応するプログラムを書くべきです。

// Bind Data
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexes), gl.STATIC_DRAW);

// Draw elements
gl.drawElements(gl.TRIANGLES, indexes.length, gl.UNSIGNED_SHORT, 0);

配列の大きさが65536(2の16乗)より大きいの場合、以下のような4バイトに対応するプログラムを書くべきです。

// Bind data
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indexes), gl.STATIC_DRAW);

// Draw elements
gl.drawElements(gl.TRIANGLES, indexes.length, gl.UNSIGNED_INT, 0);

ANGLEについて

ANGLEとは、ANGLE(Almost Native Graphics Layer Engine / アングル)というオープンソースのライブラリです。
WindowsChromeWebGLが動作するとき、
[ユーザーが記述したGLSL] -> [ANGLEが変換したGLSL] -> [ANGLEが変換したHLSL] -> [グラフィックスAPIDirect3D)]
といった具合に変換されます。
このことを「ANGLEのレイヤー上でWebGLが動作している。」というそうです。(ここでいうレイヤーというのは、ライブラリ上でぐらいの意味かな)
WebGL - WEBGL_debug_shadersで変換済みのシェーダーコードを確認するを参考にしました。

フレームバッファのコピー

WebGL 2.0 で追加されたblitFramebuffer()メソッドはフレームバッファー同士のコピーを行うAPIです。
詳細はこちらをご覧ください

BlenderからSTLファイルを出力するときの設定

何もしなくてよい。Blender起動時のままの設定でよい。一応設定内容を貼り付けておく。

平面でクリップする場合

OpenGLではglClipPlaneという関数があるらしいがWebGL2にはない。
モデリング座標系の平面でクリップする場合、頂点シェーダーからフラグメントシェーダにに頂点座標をそのまま渡して、
フラグメントシェーダでその座標 (x,y,z) を平面の式 ax+by+cz+d に代入して、\geq 0 とでもすればよい。

モデルの断面を表示する方法

実装はしていないが、多分こうやったらうまいこと行くんちゃう的なメモ。
1. ax+by+cz+d\geq 0 はdicardし、それ以外は普通にステンシルバッファに1を書き込む
2. デプスバッファとカラーバッファをクリアし、backをcullして、ステンシルバッファに0を書き込む
3. ステンシルバッファが1のところに平面を書き込む
モデルがソリッドであればこれで断面を表示できるはず。
少し応用すれば、回転体の断面も表示できると思う。(例:x軸回りの回転角度150度から270度を表示する)

バンプマッピングメモ

箇条書き
・球の接線ベクトルはy軸と法線ベクトルから計算している例が多くみられるが、それだと北極と南極で失敗する。
北極と南極の法線ベクトルはy軸と平行だから。
・通常のバンプマッピングでの法線の決め方は、法線マップを使うか、高さマップから法線を計算するかの2択である。
法線が固定なら、もちろん法線マップを使った方が良い。法線を求める計算をしなくてよいので。ただし、高さマップから求めた法線と同じ品質である。
・砂浜や砂利などが動くような場合、土台マップの上に高さマップがあるような感じにするとよい。かな。よくわからなくなってきた。

VAO(Vertex Array Object)はなるべく使った方が良い?

VAOとは、一言でいうとVBO(Vertex Buffer Object)をまとめる機能の事です。
こちらの記事によると、
OpenGL 3.2 の Core Profile 以降では, OpenGL の図形描画は頂点配列オブジェクト (Vertex Array Object, VAO) を介して行わないといけません」とあるので
将来的にOpenGLで書く可能性もある人は、VAOを使って描画するようにプログラムを書いたほうが良いと思います。
VAOはWebGL2では、標準機能なのでそのまま使えます。

// WebGL2 program
 var someVAO = gl.createVertexArray();

WebGL(1)では、以下のようにgetExtensionメソッドを呼ぶ必要があります。

// WebGL1 program
var ext = gl.getExtension("OES_vertex_array_object");
if (!ext) {
    // tell user they don't have the required extension or work around it
} else {
    var someVAO = ext.createVertexArrayOES();
}

WebGL1からWebGL2に乗り換える

こちらにうまくまとまっているので、私がまとめる必要が無いと感じました。なので、リンクを参考にしてください。

#Vertex Shader と Fragment Shader で同じ uniform の変数を使うときの注意
以下のようなメッセージが出た。
Precisions of uniform 'uLight' member 'uLight.type' differ between VERTEX and FRAGMENT shaders.

要は、Vertex Shader と Fragment Shaderで 'uLight.type' の型が違うよってことらしい。
両シェーダにprecision mediump int;を書いたら解決した。ということで、GLSLで int を使うときは注意してください。


WebGLOpenGL ES と GLSL ES のバージョンの対応

WebGL OpenGL ES GLSL ES
1.0 2.0 1.0
2.0 3.0 3.0

浮動小数点テクスチャ

texImage2D の第八引数にgl.FLOATを指定することで実現できます。
画像をテクスチャとして用いる場合は、第八引数にgl.UNSIGNED_BYTEです。
下のプログラムは、フレームバッファのテクスチャの例なので、第九引数が null となっております。

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);

浮動小数点テクスチャはWebGL2では標準で使える。

頂点ブレンディングの頂点シェーダのサンプルプログラム

WebGL Skinning より引用。

attribute vec4 a_position;
attribute vec4 a_weight;
attribute vec4 a_boneNdx;
 
uniform mat4 projection;
uniform mat4 view;
uniform sampler2D boneMatrixTexture;
uniform float numBones;
 
// these offsets assume the texture is 4 pixels across
#define ROW0_U ((0.5 + 0.0) / 4.)
#define ROW1_U ((0.5 + 1.0) / 4.)
#define ROW2_U ((0.5 + 2.0) / 4.)
#define ROW3_U ((0.5 + 3.0) / 4.)
 
mat4 getBoneMatrix(float boneNdx) {
  float v = (boneNdx + 0.5) / numBones;
  return mat4(
    texture2D(boneMatrixTexture, vec2(ROW0_U, v)),
    texture2D(boneMatrixTexture, vec2(ROW1_U, v)),
    texture2D(boneMatrixTexture, vec2(ROW2_U, v)),
    texture2D(boneMatrixTexture, vec2(ROW3_U, v)));
}
 
void main() {
 
  gl_Position = projection * view *
                (getBoneMatrix(a_boneNdx[0]) * a_position * a_weight[0] +
                 getBoneMatrix(a_boneNdx[1]) * a_position * a_weight[1] +
                 getBoneMatrix(a_boneNdx[2]) * a_position * a_weight[2] +
                 getBoneMatrix(a_boneNdx[3]) * a_position * a_weight[3]);
 

シェーダでプリプロセッサ文を使う

プリプロセッサ文とはC/C++で使われるコンパイルの前に行われる処理の事で主にソースの置き換えために用いられる命令です。
#define, #ifdef, #endifのみ実際に試しました

GLSL Preprocessor Directivesを貼り付けておきます。

WebGL2での浮動小数点テクスチャのtexImage2Dの呼び方

浮動小数点テクスチャのサンプルがWebGL1のものが多く、少しはまったのでメモします。
3番目の引数 internal format の実引数を gl.RGBA32F にすれば OK でした。

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, 4, this.joints.length, 0,
                    gl.RGBA, gl.FLOAT, this.jointData);

WebGL2でテクスチャが表示されない

私の場合はミップマップを作成し忘れていました。

gl.generateMipmap(gl.TEXTURE_2D);
目次へ戻る