attributeでIBO描画

attributeでIBOを使おう!

 ここまではdrawArraysしか使ってきませんでした。せっかくindexBufferを学んだのにもったいないので、この節ではアトリビュートをエレメント描画で使ってみましょう。とはいえ、整数列を連番ではなくインデックスバッファで用意するだけで、あとは全部同じです。これが3Dなどの描画におけるスタンダードです。複数の頂点に対してポリゴンを用意する際に、三角形一つ一つでは非効率ですから、重複があってもいいようにエレメント描画をするわけです。まあ3Dは無理ですから、普通に平面でやりましょう。描くのは3-1でやった「F」です。

 それと、動かしてみましょう。久々の恒常ループです。

コード全文

作品28のリンク

コードの解説

 まず点データとインデックスデータを用います。3-1で使ったのと全く同じものです。今回はuniformではないのできちんと型付配列にしています。インデックスは8bitでいいですね。

  // 点データuniform
  const points = [];
  for(let k=0; k<6; k++){
    for(let i=0; i<4; i++){
      const x = -3/8 + (1/4)*i;
      const y = 5/8 - (1/4)*k;
      points.push(x, y);
    }
  }
  const pData = new Float32Array(points);
  // indexのデータ
  const indices = [
    0,4,5, 0,5,1, 1,5,6, 1,6,2, 2,6,7, 2,7,3,
    4,8,9, 4,9,5,
    8,12,13, 8,13,9, 9,13,14, 9,14,10, 10,14,15, 10,15,11,
    12,16,17, 12,17,13,
    16,20,21, 16,21,17
  ];
  const iData = new Uint8Array(indices);

 頂点データはvec2が24個、3-1でuniformで用意したものと同じものを用意しています。今回は0番しか使いません。あの時はエレメントバッファが指定したindexをgl_VertexID(これもアトリビュートですが...)経由で配列にセットして位置情報を割り出しましたが、今回はそのインデックスで位置データのバッファからフェッチで出すわけです。アトポンの指定に従って出しているので、見かけ上、この配列から出しているように見えますが、アトリビュートの仕組みに詳しい今ならそこまで単純ではないことがわかるでしょう。まあ、結果は同じですが...

 次にindexデータを見てみましょう。これは3-1で用意したのと同じものです。indexは、ELEMENT_ARRAY_BUFFERで登録します。これも懐かしいですね。忘れている人は3-1で復習しましょう。

  // バッファを作る
  const pBuf = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, pBuf);
  gl.bufferData(gl.ARRAY_BUFFER, pData, gl.STATIC_DRAW);
  gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  const iBuf = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuf);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, iData, gl.STATIC_DRAW);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

 バインドする場所が違うのでその都度unbindしてます。なおここにも書きましたが、描画中にバインドが必須なのはIBOくらいなので常にあれもこれもバインドしておく必要はありません。その都度きちんと解除しましょう。バグの原因になるので。

 (追記、ここでは説明しませんがあくまでglobal stateのbindingBuffersの話です。テクスチャとなるとまた事情が異なります。扱いは似ていますが。)

 余談ですが、24年の12月くらいまでの自分の理解ではバインドに対する理解が本当にあやふやで、描画中はARRAY_BUFFERも含めてそれっぽいバッファを全部バインドしといて(テクスチャも...!)、描画が終わるたびに無節操に全部解除とか言うめちゃくちゃなコードを書いてました。今では考えられないんですが、そうですね、理解するって大事ですね...。

行列uniformふたたび

 加えて今回はちょっとだけ新しいことをしています。まあ区切りとなるコンテンツでは真新しい何かを追加するのはよくあることです。ただの復習では面白くないですからね。前回の記事で行列による平行移動と回転をやったので、今回はさらに拡縮を加えましょう。mat2x3のuniformです。前回やったのとほぼ同じですが、加えて0.5倍に縮小しています。

 描画のメイン部分を見てみましょう。

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuf);
    const loc = pg.uniforms.uModify.location; // ちゃんと入ってる
    // 複数描画
    const t = frameCount*Math.PI*2/240;
    const yScale = 0.5*Math.cos(t*2);
    // 最後なのでuniformMatrix2x3fvでも使うか
    // やり方は一緒です
    gl.uniformMatrix2x3fv(loc, false, [
      0.5*Math.cos(t), -0.5*Math.sin(t), 0.5,
      yScale*Math.sin(t), yScale*Math.cos(t), 0.5
    ]);
    gl.drawElements(gl.TRIANGLES, iData.length, gl.UNSIGNED_BYTE, 0);
    gl.uniformMatrix2x3fv(loc, false, [
      0.5*Math.cos(-t), -0.5*Math.sin(-t), -0.5,
      yScale*Math.sin(-t), yScale*Math.cos(-t), 0.5
    ]);
    gl.drawElements(gl.TRIANGLES, iData.length, gl.UNSIGNED_BYTE, 0);
    gl.uniformMatrix2x3fv(loc, false, [
      0.5*Math.cos(t), -0.5*Math.sin(t), 0.5,
      yScale*Math.sin(t), yScale*Math.cos(t), -0.5
    ]);
    gl.drawElements(gl.TRIANGLES, iData.length, gl.UNSIGNED_BYTE, 0);
    gl.uniformMatrix2x3fv(loc, false, [
      0.5*Math.cos(-t), -0.5*Math.sin(-t), -0.5,
      yScale*Math.sin(-t), yScale*Math.cos(-t), -0.5
    ]);
    gl.drawElements(gl.TRIANGLES, iData.length, gl.UNSIGNED_BYTE, 0);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

 uniformMatrix2x3fvです。他の不一致についても一緒です。uniformXは現在、これをサポートしていません。用意してもいいんですが、使用頻度が低いので入れてないんです。fisceにも入ってないです。まあ需要があったら考えますが...というわけでロケーションからやってます。ロケーションの取得は済んでるのでオブジェクト経由でサクッと入手できます。2番目がfalseなのもあれと一緒です。シェーダーでもちゃんと右から掛けています:

#version 300 es
layout (location = 0) in vec2 aPosition;
uniform mat2x3 uModify;
out vec2 vUv;
void main(){
  vec2 p = aPosition;
  vUv = p * 0.5 + 0.5; // 左下が0,0で右上が1,1
  p = vec3(p, 1.0) * uModify;
  gl_Position = vec4(p, 0.0, 1.0);
}

 uModifyを右から掛けています。使い方は前回の記事と一緒です。なおvUvを用意していますが今回これを使って彩色してます。いわゆるUV彩色です。shadertoyのデフォルトで見かけるやつです。
 行列の定義ですが、正方と一緒ですね。行ごとに順に並べるだけです。分かりやすくていいですね。内部でもこの順に指定することでmat2x3を作れます。外と中で同じ順で数を並べて行列が作れるのは精神衛生的にとてもいいですね。掛ける方向以外は全部一緒ですから。
 恒常ループについてちょっとだけ触れると、loopFunctionを最後に1回実行しています。内部でも最後にrequestAnimationFrameで実行しています。これで恒常ループになります。
 最後にdrawElementsでUNSIGNED_BYTEを指定して、個数はiDataのlengthで、offsetは0です。uniformごとに違う描画をしています。その都度違う行列を用意してるんですが、こういうのを、たとえば各々のFごとにそれぞれ予め用意できたら便利だと思うんですよね...

 はい!それがインスタンシングです。

 いい感じに導入になったところで、この記事はこれで終わりです。基礎編が終了しました。次からは応用編です。こうご期待。まだまだアトリビュートは続きます。WebGLで最も重要な話題ですから、話すことが尽きないですね。

今回登場した関数

uniformMatrix[234]x[234]fv