feature image

2022年4月5日 | ブログ記事

WebGLで上手く表示されないバグに嵌った

21Bのseasonです。最近WebGLを触り始めました。WebGLで球を表示しようとしてバグって非常に嵌ったのでメモ。

バグの内容

やり方としては、JavaScriptの方で球の表面の頂点座標、色、インデックスバッファを生成して頂点シェーダーに渡す。

const [positions, colors, index] = genVertex(400, 200);

const positionAttributeLoation = gl.getAttribLocation(shaderProgram, 'aPosition');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

const colorAttributeLocation = gl.getAttribLocation(shaderProgram, 'aColor');
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(index), gl.STATIC_DRAW);

genVertex(a, b)というのは自分が定義した関数で詳細は省く。a, bは球の二方向の分割数。

gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(shaderProgram);

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLoation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLoation);

gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(colorAttributeLocation);

const rotateMatrix = mat4.create();
mat4.rotate(rotateMatrix, rotateMatrix, -Math.PI/2, [1, 0, 0]);

gl.uniformMatrix4fv(rotateMatrixLocation, false, rotateMatrix);

gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);

すると以下のように表示された。(ちなみに球面上で地形生成をやろうとしてるので、パーリンノイズで作った地形ができてる。)

原因を探す

上手く表示されてない部分は無数にあったのだけど、表示するインデックバッファの長さを変えてみると、gl.drawElements(gl.TRIANGLES, 391416, gl.UNSIGNED_SHORT, 0);にしたとき初めて表示が球からずれることが分かった(391416は3の倍数)。つまりindex[391413], index[391414], index[391415]番目の頂点がなす三角形がおかしいということになる。ここでindex[391413] = 65335, index[391414] = 65336, index[391415] = 65536だった(伏線1)。それぞれの座標(positions[3 * 65335], positions[3 * 65335 + 1], positions[3 * 65335 + 2]), (positions[3 * 65336], positions[3 * 65336 + 1], positions[3 * 65336 + 2]), (positions[3 * 65536], positions[3 * 65536 + 1], positions[3 * 65536 + 2])console.logで表示してみると特に変な値にはなっていない。ちなみに謎現象があって、genVertexに入れる分割数を小さくすると正しく表示される(伏線2)。

分からんのでシェーダに渡されている座標を見ることにする。色デバッグなるものを試してみた。

【WebGL / GLSL】GLSLはconsole.log()なんてできないから、「色デバッグ」をするらしい

gl.readPixels()でcanvasの色を見てみたんだけど何故か指定した色と違ってた。エイリアシングとかの影響なのかもしれない。

結局if文で分岐させるのが楽だった。目視で外れてる頂点の座標が(0, 0, -1)だったのでそこだけ色を変えるようにしたら当たってた。何らかの理由で三つの頂点の内の一つが(0, 0, -1)に変わってるらしい。で、多分おかしいのは三つ目の頂点だと当たりが付いてた。genVertex()では、球面座標でz軸の小さい方から生成して配列に加えてるから(0, 0, -1)は丁度0番目の頂点ということになる。65536が0になってるということは...あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ

結論

インデックスバッファをInt16Arrayにしてたのでmod 65536されて0になってました。分割数が小さいと頂点数も少ないのでmod以下になってたんですね~

gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(index), gl.STATIC_DRAW);

ついでにgl.drawElements()でもgl.UNSIGNED_SHORTを渡してました。

gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);

解決

まずInt32Arrayに変更します。

gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int32Array(index), gl.STATIC_DRAW);

それからgl.drawElements()です。

WebGLRenderingContext.drawElements() - Web APIs | MDN

3番目の引数typeに渡せるのが

とあります。BYTEの方がSHORTより小さいので拡張機能を導入してINTを使います。

gl.getExtension('OES_element_index_uint');
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_INT, 0);

できました。

あーやっと解決した。

season1618 icon
この記事を書いた人
season1618

数学と物理と競プロをやります。

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2023年4月17日
ポケモンを飼いたい夢を叶える
tqk icon tqk
2023年3月20日
traPグラフィック班の活動紹介(Ver.2023)
NABE icon NABE
2022年4月7日
traPグラフィック班の活動紹介
annin icon annin
2021年3月19日
traPグラフィック班の活動紹介
NABE icon NABE
2022年7月31日
2022春ハッカソン 18班 Music Video "I C Universe"
dan_dan icon dan_dan
2021年7月4日
2021年度春ハッカソン参加報告記事 10班「ホエール瀧」
Z icon Z
記事一覧 タグ一覧 Google アナリティクスについて