同じデータの供給

同じデータでも大丈夫

 アトリビュートは、ロケーションは競合できないんですが、同じデータの供給であれば可能です。つまり同じ用途で同じデータを違うスロットで、みたいなことができます。まあ普通はしないですが...というのも、同じデータを供給する場合、普通は別々の用途で指定するからです。たとえば、一つのバッファにいろんなデータが入ってて、それを異なるアトリビュートが違うやり方でフェッチするわけです。いわゆるインターリーブドというやつで、いずれ詳しくやりますが、今回は同じデータをそのまま他のアトリビュートに供給する実験だけやりましょう。
 32bit小数の位置データを使って色を決める、ちょっと変わった色指定で遊びましょう。

コード全文

作品22のリンク

バッファは1つだけ

 今回はバッファは1つしか作りません。すなわち三角形の頂点の位置データです。

  const angle = Math.PI*2/21;
  const pData = new Float32Array([
    Math.cos(angle*4), Math.sin(angle*4),
    Math.cos(angle*11), Math.sin(angle*11),
    Math.cos(angle*18), Math.sin(angle*18)
  ]);

 0や1ができないように若干ずらしました。これを使って色も決めようというわけですが、色はnormalizedを使って0~255で決めます。どういうことかというと、結局位置データはリトルエンディアンでバイト列になるんで、そこからUNSIGNED_BYTEのフェッチで色データを取ってUNSIGNED_BYTEの値として数を作り、それを正規化することで色としようというわけです。つまり、こう:

  const buf = gl.createBuffer(); // 位置と色で使う
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.bufferData(gl.ARRAY_BUFFER, pData, gl.STATIC_DRAW);
  gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
  // stride:7, offset:5
  gl.vertexAttribPointer(1, 3, gl.UNSIGNED_BYTE, true, 7, 5);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

 色の方は1番です。サイズは3で、UNSIGNED_BYTEなのでバイト長は1です。つまりstrideもoffsetも何でもありです。そういうわけで7と5にしました。つまり、整数0,1,2に対してそれぞれ5,6,7, 12,13,14, 19,20,21で色が作られます。分かりますか?分かんない場合は復習しましょう...復習はなんぼあってもいいですからね。
 しかしそのバイトにどんな数があるのか分かんないですよね。そこでDataViewの出番です。これを使って位置データと全く同じバイト構成を作り、同じ箇所をフェッチしてどんなバイトがとられるのか見ようというわけです。一応述べておくと、バイト列にはIEEE32bit浮動小数点数の流儀に従って6つの小数が順番に入っており、リトルエンディアンで各々のバイトが小さい方から順番に...まだるっこしいわっていうね。まあ無理ですね。理屈を理解すればできるんでしょうが、やってられないです。なのでDataViewでやりましょう。

DataViewを使う

 6つの小数をリトルエンディアンでバイト列として放り込むには、setFloat32をtrue指定で然るべきオフセットで入れるだけです。とても簡単です:

  // バイト列にしよう。
  const dw = new DataView(new ArrayBuffer(24));
  for(let i=0; i<6; i++){
    dw.setFloat32(i*4, pData[i], true);
  }

 それであとは該当する位置のバイトをgetUint8で出すだけです。これは1バイトなのでエンディアン指定はありません。

  // stride:7, offset:5
  const indices = [5,6,7, 12,13,14, 19,20,21];
  let bytes = "";
  for(let i=0; i<9; i++){
    const b = dw.getUint8(indices[i]);
    bytes += `${b}.0, `;
  }
  console.log(bytes); // 77.0, 110.0, 63.0, 137.0, 158.0, 24.0, 63.0, 28.0, 38.0,

 無事算出できました。これで合ってるんでしょうか...

fetch data

 まあシェーダーで確かめればいいんですよね。

シェーダーの解説

 シェーダープログラムでやるのとは別に、ここで算出した色データと比べるために、露骨にこのデータを定数としてシェーダーにぶち込みました。ブールのユニフォームで分岐させて比較しようってわけです。

#version 300 es
layout (location = 0) in vec2 aPosition;
layout (location = 1) in vec3 aColor;
// フェッチの結果がきちんとなっているのを確認しましょう。
const vec3[3] colors = vec3[](
  vec3(77.0, 110.0, 63.0), vec3(137.0, 158.0, 24.0), vec3(63.0, 28.0, 38.0)
);
uniform bool uUseConstant;
out vec3 vColor;
void main(){
  vec2 p = aPosition;
  vColor = aColor;
  if(uUseConstant){
    // uUseConstantが機能していることを確かめるためにちょっとだけ回転させる。
    // 行列のところでみっちりやったので、
    // これで反時計回りにちょこっと傾くのが分かると思う...分かってほしい...
    p *= mat2(cos(0.2), -sin(0.2), sin(0.2), cos(0.2));
    // めんどうなので255.0で割りましょう。
    vColor = colors[gl_VertexID]/255.0;
  }
  gl_Position = vec4(p, 0.0, 1.0);
}

 uUseConstantで分岐させています。デフォルトはfalseです。すなわちアトリビュートによる色指定です。サムネイルの通りです。で、使う場合ですが、この場合は定数で上書きします。しかし全く同じ見た目になるので逆にわかりにくいです。そこでフラグが機能していることを確認するために、行列で傾けています。この行列は、cos(0.2), -sin(0.2), sin(0.2), cos(0.2)と並んでおり、これが機能すると、反時計回りにちょこっと傾きます。向きは重要です。行列のところでみっちりやったと思いますが、このように成分を行ベースで並べた時に2次元の列ベクトルに作用させた結果が正の向きの回転ですから、これでいいんです。乗算代入です。その方が分かりやすいですからね。大丈夫でしょうか。いいですね。
 同じ色になることが確かめられたと思います。

 今回はどっちかというとstrideとoffsetに親しむのが目的でした。同じデータを利用することの実例はインターリーブでみっちりやります。その際もDataViewは大活躍します。こうご期待。ではまた、次回。