normalize引数
整えちゃおうね

たとえば色っていうのは0~255で指定するわけです。間をどんなに細かく取っても4バイトで1色です。ならデータとしては4バイトで用意したいところですが、普通に0~255が4つでUNSIGNED_BYTEで用意するとそのものの値で入ってきてしまい、255でいちいち割らないといけません。出力としての色は0~1に正規化されている必要があるからです。clearColorの指定も0~1ですよね。0~255ではないわけです。
面倒なので、それを何とかしましょうというお話です。
コード全文
normalizeの仕組み
vertexAttribPointerの関数解説でやってしまったんですが、きちんと取り上げるのがこの節の目的です。まず、
gl.vertexAttribPointer(index, size, type, normalized, stride, offset);
におけるnormalizedというのは、値の正規化、つまりベクトルとかでもあるわけですが、1以内に抑える処理です。現在アトリビュートは、float, vec2, vec3, vec4しか扱っていません。なのでフェッチしてから型ごとに数値にした後は、そのまま32bitのfloatにキャスティングされます。UNSIGNED_BYTEで255とかなった場合、本当に255でぶち込まれます。その、キャスティング後の段階で、このnormalizedが生きてきます。これがtrueの場合、型に応じた数値化の後で、型に応じて-1~1の値に制限します。それぞれの場合の挙動:
- gl.BYTE(-128~127): -1~1
- gl.SHORT(-32768~32767): -1~1
- gl.INT(-2^31~2^31-1): -1~1
- gl.UNSIGNED_BYTE(0~255): 0~1
- gl.UNSIGNED_SHORT(0~65535): 0~1
- gl.UNSIGNED_INT(0~2^32-1): 0~1
というわけですね。FLOATやHALF_FLOATの場合は効果なし、です。この正規化の後で、そのまま32bitのfloatとして扱われます。たとえばUNSIGNED_SHORTなら65535で割るわけですね。なお、たとえばBYTEの場合の-128ですが、これも-1のようです。-127と被ってますが、なんかいつだったか調べたらそうなってました。多分127で割ってclampしてるんじゃないですか?(適当)
まあ、細かいことは気にしないことにしましょう。楽しく人生を生きるコツです。
そんな感じで正規化されるので、0~255の色情報をそのまま使えるわけです。嬉しいですね。
normalizedを使う
じゃあ使いましょう。
const pBuf = gl.createBuffer();
const cBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, pBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Int8Array([-64, -64, 64, -64, 0, 64]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, cBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array([255, 64, 99, 99, 255, 64, 64, 99, 255]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
BYTEで位置、UNSIGNED_BYTEで色を作りました。堂々と-128~127あるいは0~255を想定しています。まあ遊びなので。普通こんな風に位置決めないですからね。で、正規化して正規化デバイス座標に落とそうってわけです。
const vap = (normalized) => {
gl.bindBuffer(gl.ARRAY_BUFFER, pBuf);
gl.vertexAttribPointer(0, 2, gl.BYTE, normalized, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, cBuf);
gl.vertexAttribPointer(1, 3, gl.UNSIGNED_BYTE, normalized, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
normalizedはデフォルトではtrueです。それでサムネイルの画像になります。じゃあfalseにしたら?まあやってみればわかるんですが、

冬になってしまいました。あの数字が「そのまま」使われるわけです。まあエライことになるのが分かると思います。
なお当然ですが、次のようにすれば同じ見た目になります...この場合にnormalizedをtrueにするとつぶれて消えて真っ黒になります。
#version 300 es
layout (location = 0) in vec2 aPosition;
layout (location = 1) in vec3 aColor;
out vec3 vColor;
void main(){
vColor = aColor/255.0;
gl_Position = vec4(aPosition/127.0, 0.0, 1.0);
}
normalizedの実例
たとえば色指定にしても、別に3バイトで整数で用意しようが12バイトで小数で用意しようが、どのみち12バイトのvec3で扱われるわけですから、気にする必要はそれほどないかもしれませんね。実際p5はそうしています(全部gl.FLOAT, 全部false,0,0)。しかしこれを知っていないと不便な場合というのは実際、あります。たとえばねこりいねこさんのこのサイトなんかでは実際にnormalizedがフル活用されています。一部抜粋すると...
if ('a_Position' in program) {
if(isRectBuffer) console.log("usePosition");
gl.enableVertexAttribArray(program.a_Position);
gl.vertexAttribPointer(program.a_Position, 3, gl.SHORT, true, 2 * 8, 2 * 0);
}
if ('a_Curvature' in program) {
gl.enableVertexAttribArray(program.a_Curvature);
gl.vertexAttribPointer(program.a_Curvature, 1, gl.SHORT, true, 2 * 8, 2 * 3);
}
このコードではジオメトリに関するすべてのデータがSHORT(2バイト符号付き整数)で用意され、normalizedで-1~1に正規化されて扱われています。ライブラリのくびきに縛られないコードではよくあることです。そういうのを読み解く際に助けになるかと思います。
また、gltfのデータを扱う際に、色データが2バイトの非負整数(0~65535)で取得されるんですが、これをそのまま使おうって場合にもnormalizedを知らないといちいち65535で割ってgl.FLOATでやらないといけないわけです。非効率です。normalizedは、使われるところでは使われています。色々知るのは大事ですね。
今回はここまでです。次はちょっと変わった色指定をしてみましょう。ではまた次回~