WebGLでPortal Shieldとかを描く

唐突にWebGLを勉強してみようと思って、練習として作ってみた。

(※WebGL対応ブラウザでご覧ください)


WebGLについて全然知識が無かったので、このへんを読んで勉強した。

特に wgld.org は基礎から始まり詳しく解説しつつ徐々に色んな要素を盛り込んでいて、分かりやすいし楽しい。ひたすら写経しつつ読み進めることで、なんとなく3Dの描画のことが分かってきた(ような気になった)。
素晴らしいテキスト、本当にありがたいです。


で、3Dで何か描いてみるのに何か良い題材ないかなーと思ってたところに Ingress にちょうどハマっていたので。
もしかしてiTunesと同期したipaファイルを解凍したら何か3Dモデルのデータが転がってるんじゃないか、と覗いてみたところ Ingress.app/scannerってディレクトリにそれっぽいファイル名のものたちを発見。
*.objっていうファイル名だけどWevefrontのASCII形式の…ではなかった。バイナリ。どうやらJavaシリアライズされたものらしい。
ので適当に デシリアライズするコード を書いて中身を取り出してみた。
最初にfloatの配列が出てきて、その後にshortの配列が2つ出てくる。最初のfloatのが頂点座標のデータに違いない、と思ったのだけど要素数が3の倍数じゃなくて5の倍数になっていたりする。どうやら頂点の位置座標とテクスチャの座標が混在しているっぽい?ということで色々ためしてみたら


頂点1のx座標, 頂点1のy座標, 頂点1のz座標, 頂点1のテクスチャx座標, 頂点1のテクスチャy座標,
頂点2のx座標, 頂点2のy座標, 頂点2のz座標, 頂点2のテクスチャx座標, 頂点2のテクスチャy座標,
頂点3のx座標, 頂点3のy座標, 頂点3のz座標, 頂点3のテクスチャx座標, 頂点3のテクスチャy座標,
...
という感じに並んでいるようだった。のでindexの0, 1, 2, 5, 6, 7, 10, 11, ...番目の物を頂点位置座標に、3, 4, 8, 9, 12, 13, ...番目のものをテクスチャ座標として取り出して、2番目に出てくるshort配列を頂点インデックスとして使ってみたところ上手くいった。3番目に出てくるshort配列は何に使うのか分からない…。

Portal Keyは取り出したデータを元にそれっぽいファイル名のテクスチャ画像を使ってはめてみたら意外に簡単にできた。
シェーダプログラムはこんなかんじ。 glMatrix を使って適当に回転させてみてる。

attribute vec3 position;
attribute vec2 texCoord;
uniform   mat4 pMatrix;
uniform   mat4 mvMatrix;
varying   vec2 vTexCoord;

void main() {
    vTexCoord = texCoord;
    gl_Position = pMatrix * mvMatrix * vec4(position, 1.0);
}
precision mediump float;

uniform sampler2D texture;
varying vec2      vTexCoord;

void main() {
    gl_FragColor = vec4(vec3(0.0), 1.0) + texture2D(texture, vTexCoord);
}


次にPortal Shieldにチャレンジしてみたのだけど、これは部品が2個に分かれていて、外側のものはPortal Keyと同じ要領で描けたのだけど 内側の正二十面体のものはなんか表面の模様がウヨウヨと動いている。これはどうやっているんだろう…と思って再び解凍したipaの中身を探ってみるとIngress.app/shadersというディレクトリにシェーダプログラムが色々あって、そこに書いてあった。

attribute vec3  a_position;
attribute vec2  a_texCoord;
uniform   mat4  u_pMatrix;
uniform   mat4  u_mvMatrix;
uniform   float u_elapsedTime;
varying   vec2  v_texCoord;
varying   vec4  v_texCoord0And1;

void main() {
    v_texCoord = a_texCoord;
    v_texCoord0And1 =  vec4(a_texCoord, a_texCoord * 1.35);
    v_texCoord0And1 += vec4(0, u_elapsedTime * 0.6, u_elapsedTime * 0.6, u_elapsedTime * 0.45);
    gl_Position = u_pMatrix * u_mvMatrix * vec4(a_position, 1.0);
}
precision mediump float;

uniform sampler2D u_texture;
varying vec2      v_texCoord;
varying vec4      v_texCoord0And1;

void main() {
    vec4  base       = texture2D(u_texture, v_texCoord0And1.xy);
    vec4  scrolled   = texture2D(u_texture, v_texCoord0And1.zw * 1.35);
    float blend_mask = ((base.g * scrolled.g) + (base.r * scrolled.r)) * 2.0;
    vec4  colorTint  = mix(vec4(vec3(0.5, 0.0, 0.5), 1.0), vec4(vec3(0.5), 1.0), blend_mask - 0.25);
    gl_FragColor   = colorTint + vec4(blend_mask) - vec4(1.0);
    gl_FragColor.a = 0.5;
}

という感じで、elapsed timeをuniform変数で渡して、それをもとにテクスチャ座標をずらしたものを生成して、それらを使って合成してなんかイイカンジにそれっぽい模様の動きを表現している、らしい(よく分かってない)。テクスチャ画像はobjectXMTexture.tgaっていう謎の模様を表しているファイルがあったのでpngに変換して使ってみてる。


複数のシェーダを使って別々のオブジェクトを描画するのに良い方法がよく分からなくて、毎フレームごとにgl.useProgram()で切り替えつつVertex Buffer ObjectやIndex Buffer Objectをbindさせて、とかやってしまっている…。こういうの無駄っぽいから省けると思ったのだけど どうすればいいのかなぁ


まぁとりあえずはPortal Shield的なものも描画できるようになった、ということで他のModやXMPやResonatorとかも同じような感じで描いて遊んでみようっと。