アトリビュートの基礎
アトリビュートだ!

アトリビュートの説明を始めます。今まではドローコールにおいて、特定の整数からの連番、もしくはインデックスバッファにより、自由な整数列を生成して描画していました。その整数からデータを作るのも、整数のみに基づいてバーテックスシェーダで位置を決めていました。gl_VertexIDにおんぶにだっこで、位置や色を配列やuniformを駆使して決めていました。実はこのgl_VertexIDこそ、アトリビュート(整数型)の一例でした。言わなかったけど。
アトリビュートとは整数からデータを作る仕組みです。データはインデックスバッファと同様、バイト列に入っています。これも一種のWebGLBufferです。なので型付配列やDataViewでデータをバッファして、これまたインデックスバッファと同様、フェッチして数にして使います。それで前回そこら辺を詳しくやりました。ただ今回は前回のように整数3パターンではなく、小数なども含まれます。いっぱいあります。それでも基本は同じです。焦らず落ち着いて、じっくり読み進めましょう。
コード全文
global state
ちょっとglobal stateを見てみます。今回はbindingBuffersのarrayBuffer枠と、vertexAttributeArrayが関連します。これはデータの格納庫です。スロットにはそれぞれWebGLBufferを割り当てます。elementArrayBufferで作ったようなWebGLBufferがそれぞれのスロットに割り当てられます。
globalState ={
currentProgram:null,
bindingFramebuffer:null,
bindingBuffers:{
arrayBuffer:null, // 今回用があるのはここと...
elementArrayBuffer:null,
uniformBuffer:null,
transformFeerbackBuffer:null,
pixelPackBuffer:null,
pixelUnpackBuffer:null, ...
},
bindingVertexArrayObject:null,
vertexAttributeArray:[ // ここですね。
{
enable:false, arrayBuffer:null, layout:{
size:4, type:gl.FLOAT, normalize:false, stride:0, offset:0
}, divisor:0,
current:DEFAULT_VERTEX_ATTRIB
},{},{},...
],
...,
}
vertexAttributeArrayの長さですが、機種に依存します。gl.MAX_VERTEX_ATTRIBSで取得できます。一応スマホでも見れるようにしました(左下)。基本的に16ですが、機種に依存するようです。自分のデスクトップとスマホでは16でした。
さて、整数からどのようにデータを取り出すかですが、順を追って説明します。まずシェーダー側の準備からです。
シェーダーの準備
アトリビュートはin修飾子で宣言します。そのあとで型を決めます。今はとりあえず、float, vec2, vec3. vec4しかやりませんが、いずれいろいろやります。先に言っておくと配列や構造体は出てきません(安心)。
#version 300 es
in vec2 aPosition;
in vec3 aColor;
out vec3 vColor;
void main(){
vColor = aColor;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
inで宣言されたaPositionやaColorがどのような値かというのが、この節で説明したいことです。それを知るにはまずgl_VertexIDを考えてみます。drawArraysにせよdrawElementsにせよ、最終的には何らかのインデックス(非負整数)が与えられるわけです。それをダイレクトに使うのがgl_VertexIDで、これも一種のアトリビュートです。そこからドローコールに従って三角形や線ができて描画が実行されるわけですが、その「整数」から何らかの手続きにより「データ」が得られてaPositionやaColorに放り込まれる、そういう仕組みです。その手続きを説明しようというわけですね。ただの整数では配列を使うくらいしか選択肢がありませんが、アトリビュートなら複数のプログラムでデータを使いまわすなど、いろいろできます。早速学んでいきましょう。
(まあそういうことはUBOでもできるわけですが、その話はおいておきます...とにかくアトリビュートはWebGL描画の基本なので、学ばない選択肢はないです。)
attribute location
アトリビュートにはプログラムごとにロケーションが設定されます。uniformと似ていますが、内実は全く異なるものです。uniformのロケーションはプログラム内での位置で、そこに外部からデータを供給し、頂点に依らず同じ値を使います。ロケーションも整数ではなく、謎のオブジェクトです。しかしアトリビュートのロケーションは整数です。何の値かというと、vertexAttributeArrayの格納庫の番号です(最大16個...通常は)。

各々の格納庫(vertex attribute)にはWebGLBuffer, この場合はarrayBufferというべきですね。それと、その用途というか、値をフェッチする方法の詳細が記述されています。それに基づいて、与えられた整数ごとにデータを供給します。なお、16個しかないため、しばしば複数のプログラムで競合が発生します。完全に別々というわけにはいかず、場合によって差し替えたりします。その辺はいずれ詳しくやりますが...とりあえず仕組みだけ解説しました。そうです、vertex attributeで使うWebGLBufferのことをarrayBufferといいます。
fetch value
さて、値のフェッチですが、こんな感じです。とりあえずsize,type,stride,offsetを先に出してしまいます。あとからおいおい解説します。

例としてvec2をあげます。今回のシェーダーでもvec2使ってますからね。基本ですね。この場合まず、sizeは2で、typeはgl.FLOATです。2つのFLOATというわけですね(後で解説します)。4バイトの小数です。つまりsizeは2で、typeのバイト長は4です。strideとoffsetはtypeのバイト長の整数倍、かつ非負整数であるという条件が付いています。
indexBufferのときにやったようにしてWebGLBufferが設定されるんですが(設定の仕方は後でやります)、そこからこれまたdrawElementsでやったようなバイトフェッチを実行するわけです。そのスタート位置を決めるのがstrideとoffsetです。結論を言うと、整数nに対して、n x stride + offsetです。このoffsetですが、非負整数でtypeのバイト長の整数倍ならどんなに大きくてもOKです...まあ通常ははみ出すような使い方はしないですが、複数のデータをまとめて扱う場合にそのような指定をするかもしれません。それで、そこからtypeのバイト長にsizeを掛け算した数だけバイトフェッチするわけですね。それを、typeごとに順に区切って、typeの値になるようにリトルエンディアンで解釈します(また出た)。WebGLはどこまでもリトルエンディアンです。それで、そのあとnormalizeっていう、まあそれは今はパスで、そのままvec2に順繰りに放り込まれて、これで整数からデータまでのリレーが完了します。お疲れ様でした。
なおstrideが0の場合、この場合の実際のstrideは0にならず、typeのバイト長のsize倍になります。つまり、イメージすると分かるんですが、供給されるべきデータが隙間なくぎっしり詰まった状態になるわけです。この状態でもoffsetは機能します。まあ通常は両方0で、そのアトリビュートのみのためのバッファという形になるでしょうね。
なんとなく雰囲気がつかめたでしょうか...では、実装に入ります。
ロケーションを取得する
プログラムのアトリビュートにロケーションが割り当てられる様子についてとりあえず解説します。実は、何もしなくても勝手に機種依存の仕組みに従って割り当てが実行されます。それを取得するためのコードを書きました。実はしれっとcommon.jsが更新されています:
// uniformとほぼ同じ。欲しいのはlocation.
// ただuniformと違って整数だし普通に他のpgとかぶる。取扱注意(VAO使え!)
// たとえばpg.attributes.aPosition.locationでaPositionのlocationが出る
// layoutで決めるなら要らないけれど...一応ね。
function getActiveAttributes(gl, pg){
const attributes = {};
// active attributeの個数を取得。
const numActiveAttributes = gl.getProgramParameter(pg, gl.ACTIVE_ATTRIBUTES);
console.log(`active attributeの個数は${numActiveAttributes}個です`);
for(let i=0; i<numActiveAttributes; i++){
const attribute = gl.getActiveAttrib(pg, i);
const location = gl.getAttribLocation(pg, attribute.name);
attribute.location = location;
attributes[attribute.name] = attribute;
}
return attributes;
}
取得の仕方はuniformとほぼ一緒です。アクティブなアトリビュートの個数を...これも内部で使われないと弾かれるんですが、今回それは用意しませんでした。各自実験してみてください。アクティブなものに連番が付くので、それに基づいてgetActiveAttribで取得します。そこに名前が入ってるので、getAttribLocationという関数で取得します。これをattributeに紐付けて、最終的にattributesという形でprogramに登録するところまでuniformと一緒です。この処理はuniformと同じく、リンク後に実行されます:
// uniform情報を作成時に登録してしまおう
program.uniforms = getActiveUniforms(gl, program);
// まあattribute情報も追加するか
program.attributes = getActiveAttributes(gl, program);
return program;
なので、プログラムから取得するのが基本です。これに基づいて、アトリビュートごとに然るべきロケーションにデータを置くわけです...が、これの難点は、これだとロケーションが可視化されないので、自前での管理がしづらいんですね。それにuniformと違ってプログラムごとに際限なくデータ置き場があるわけではないので(共用倉庫なので)、その辺りも気になる場合には気になるわけです。そこで手動で決める方法が一応、用意されています。バインドと、レイアウトです。
バインドで設定する
バインド、これはWebGL1でも使えるんですが、プログラムをアタッチしてからリンクするまでの間に、とある処理をはさみます。それにより、好きな番号の倉庫を使えるようになります。
gl.attachShader(program, vsShader);
gl.attachShader(program, fsShader);
// layout指定は修飾子を使わない場合、ここでやる。
// アタッチしてからリンクするまでにやらないと機能しない。
// なおこの機能はwebgl1でも使うことができる。
setAttributeLayout(gl, program, layout);
gl.linkProgram(program);
このタイミングですね。bindAttribLocationといいます。
// レイアウトの指定。各attributeを配列のどれで使うか決める。
// 指定しない場合はデフォルト値が使われる。基本的には通しで0,1,2,...と付く。
function setAttributeLayout(gl, pg, layout = {}){
const names = Object.keys(layout);
if(names.length === 0) return;
for(const name of names){
const index = layout[name];
gl.bindAttribLocation(pg, index, name);
}
}
なお、新たに引数にlayoutというオブジェクトを用意しました。デフォルトでは空です。
// layoutを追加
const {vs, fs, layout = {}} = params;
アタッチからリンクまでの間でこのbindAttribLocationを実行することで、指定したindexにnameが割り当てられます。一つ目のプログラムではこれを実行しています。
const pg = createShaderProgram(gl, {vs:vs, fs:fs, layout:{aPosition:0, aColor:1}});
これによりaPositionは0番、aColorは1番を使うことになるわけですね。
もう一つの方法は、シェーダーサイドで決めるものです。こちらの方が確実です。WebGL2で新たに登場しました。
レイアウトで設定する
ロケーションはプログラムごとに一意です。ですからシェーダーで決めてしまえばいい。それがlayout修飾子です。
#version 300 es
layout (location = 0) in vec2 aPosition;
layout (location = 2) in vec3 aColor; // layout指定のテスト
out vec3 vColor;
void main(){
vColor = aColor;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
このようにすると、aPositionは0番、aColorは2番を使うことになります。なおこの場合にはlayoutの方は空っぽにしてあります。当然ですね。
const pg_layout = createShaderProgram(gl, {vs:vs_layout, fs:fs});
もっともこれをやると、このバーテックスシェーダから作られるプログラムのロケーションはすべて同じになります。しかし異なる場合のメリットもこれといって思いつかないので、自由に決められるメリットの方が大きいでしょう。たとえば複数のプログラムで同じ役割のアトリビュートのロケーションを揃えたい場合、この手法は必須です。
ところで、バインドとレイアウトを両方実行したらどうなるとか、そういうのは次回詳しくやるのでここでは割愛します。
ロケーションは分かりました。それでは、データの供給に入りましょう。vertexAttribPointerという関数を使って、vertexAttributeArrayにデータを送り込みます。
vertexAttribPointer
データの供給作業です。プログラムとは独立した処理です。対象はvertexAttributeArrayなのでプログラムは出てきません。indexBufferでもそうだったと思いますが...データを使うための番号が関連しているだけで、完全に独立した処理です。
まず、今回はvec2で三角形の頂点位置と、vec3で色をそれぞれ3つ決めています。
// 頂点は3つ。位置を3つ、色を3つ。
const angle = Math.PI*2/3;
const posData = new Float32Array([
Math.cos(0), Math.sin(0),
Math.cos(angle), Math.sin(angle),
Math.cos(angle*2), Math.sin(angle*2)
]);
const colData = new Float32Array([
1, 0, 0, 0, 1, 0, 0, 0, 1
]);
// 別の色も用意するか~
const otherColData = new Float32Array([
1, 1, 0, 1, 0, 1, 0, 1, 1
])
色は今後のことを考えて2パターン用意しました。ところでいずれも型付配列です。例によって通常の配列では各々のデータのバイト長が分かんないですから、それをbufferDataされても困ってしまいます(というか何も起きない仕様です)。きちんと指定しましょう。何でもいいんですが、今回はすべてFloat32Arrayとしています。4バイトです。0や1が4バイトなのはもったいない?そうですね。まあいずれ...
これをindexBufferでやったようにWebGLBufferを作ったうえで割り当てるんですが、それに加えて例の格納庫への割り当てもしないといけません。そのための関数がvertexAttribPointerです。とりあえず、式:
// まず位置データを用意します
const posBuf = gl.createBuffer();
// ARRAY_BUFFER枠に入れることで各種処理が実行可能になります
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
// データの供給。これはもうやりましたね。
gl.bufferData(gl.ARRAY_BUFFER, posData, gl.STATIC_DRAW);
// vertexAttributeArrayにデータを割り当てる。
// location: スロット番号。バインドされているデータはここにはめられる。完全上書き。
// size: いくつの数を作るか
// type: バイト列をどういう数にするかの指定。
// 型によりフェッチするバイト数は異なる。
// たとえばFLOATなら4バイトずつ取ってリトルエンディアンで小数にする。
// ...心配しなくてもWebGLなので格納時に自動的にリトルエンディアンにされる。
// normalize: まだ触れないけど数の正規化
// stride: フェッチの開始位置はこれの整数倍となる。バイト数。
// typeのバイト長の整数倍でないとエラー。
// 0の場合は自動的にtypeのバイト長 x sizeとなる。負の数もエラー。
// offset: フェッチの開始位置をこのバイト数だけずらす。
// typeのバイト長の整数倍でないとエラー。負の数もエラー。
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
// bindされたバッファに対する処理はたったこれだけです。
gl.bindBuffer(gl.ARRAY_BUFFER, null);
まずcreateBufferでWebGLBufferを作ります。これをARRAY_BUFFERというターゲットにバインドすると、global stateのbindingBufferのarrayBuffer枠にこれがハマります。そしてbufferDataをターゲットをARRAY_BUFFERとして実行するとここにあるバッファにposDataが入ります。いじる予定はないのでSTATIC_DRAWでいいですね。ちなみにusageを関数紹介でやってしまったので、これについては今後も割愛します。ごめんなさい。気になる人は3-1で復習してください。
それでvertexAttribPointerを実行します。この処理は、そもそもARRAY_BUFFER枠が対象なのでバッファどころかターゲットすらありません。当然ですが、そこがnullだと何も起きません(当たり前すぎる...)。最初に来るのはロケーションです。データ倉庫の番号です。そこにデータぶち込むぜってわけです。完全上書きです。今回どちらのプログラムでもaPositionは0番を使うので、0としています。
次に来るのはサイズです。サイズはさっきの絵でも見ましたがtypeの数字を作る個数です。vec2なので2としています。vec2であるという情報は取得済みのはずなのになぜ指定するのか、それは、言うまでもなくだってこの処理プログラム関係ないからです。つまりvec2なんて情報は持ってません。次に指定するtype, これが作る数のタイプで(drawElementsのtypeに相当するやつ)、これのsize倍だけバイトを取るわけです。これはあくまでそういう処理なので、プログラム関係ないんですね。まあ合わせましょう。
(じゃあ不一致でもいいの?不一致だとどうなるの?...まあいずれ、いずれね...)
falseのところはnormalize引数と言います。今はどうでもいいのでfalseにしてください。
最後の0,0がstrideとoffsetです。いずれ色んな実験をします。内容は先ほど紹介した絵の通りです。共に0ですから、データがぎっしり、を仮定しています。実際posData含めてすべてのデータはそうなっていますから、問題ないですね。じゃあ複数のデータを一つの列に入れたりできるんじゃないの?まあいずれ、いずれね...
登録が終わったらバインドを解除しておきます。elementArrayBufferは使用時にバインドするなどの使い道があるんですが、arrayBufferは基本データの供給時、書き換え時にしかバインドしません。用が済んだら解除します。解除するにはnullを指定します。以上です。
どうでしょう。vertexAttribPointerの引数、覚えましたか?私は今この記事を書いて、ようやく覚えました。親しみが持てれば機械的な暗記は必要なくなるんですよ。不思議ですね。
色も同じように登録します。こちらもtypeはFLOATです。FLOATで用意したので当然FLOATです。それで、sizeは共に3です。ぎっしりなのでstrideもoffsetも0です。
// 同じように色データを用意します
const colBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colBuf);
gl.bufferData(gl.ARRAY_BUFFER, colData, gl.STATIC_DRAW);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// この色データは2番に供給します。layoutも2番にしてあります。
const otherColBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, otherColBuf);
gl.bufferData(gl.ARRAY_BUFFER, otherColData, gl.STATIC_DRAW);
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
データの準備ができました。しかし、まだやることがあります。スロットの有効化です。
enableVertexAttribArray
これでデータが使えるようになったと思うでしょう。そうは問屋が卸さないというか、下ろされているのはシャッターです。実はデフォルトではすべての倉庫はシャッターが降りています。データが使えません。使うにはどうするかというと、その番号を有効化します。enableVertexAttribArrayといいます。
// 0番~2番を有効化する。この処理はvertexAttributeArrayに対するもので、
// プログラムやバッファは無関係です。
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
gl.enableVertexAttribArray(2);
これで先ほどの図にあるようなフェッチが実行されるようになります。ではシャッターが降りてる場合はどうなるのか?いずれやりますが、たとえば1だけ有効化しないと、pgの描画結果はこうなります。
gl.enableVertexAttribArray(0);
//gl.enableVertexAttribArray(1);
gl.enableVertexAttribArray(2);

次のように書いても同じです。disableVertexAttribArrayといって、無効化、シャッターを降ろすための関数です。
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
gl.enableVertexAttribArray(2);
gl.disableVertexAttribArray(1);
ともあれ有効化できたので、さっさと描画して、この節を終わらせましょう。
ドローコール
バインドでロケーションを決めたものと、レイアウトで決めたものと両方用意して、切り替えができるようにしました。
const executeDraw = () => {
gl.clearColor(0.5,0.5,0.5,1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(config.pg);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.flush();
}
フェッチにより位置データと色データが復元され、三角形ができます。何のひねりも無い2枚の三角形です。切り替えて遊んでください。お疲れ様でした。アトリビュートの基本の基本はこれで解説できたと思います。お茶を飲んで一休みしましょう。自分も書いてて疲れました...

もう一つの三角形:

次回以降の予告
この節の目的はアトリビュートを用意して使うところまでです。基本を説明しました。例外事項はほぼスルーしました。なので次回以降、そこら辺を詳しくやっていきます。実はp5やThreeでやるようなポリゴン描画がしたいだけならこの程度の理解で充分で、このあとの長々と続く補足事項は要らないんですが、いつどこで役に立つかわかんないので、頑張って解説していこうと思います。おたのしみに。がんばるぞ...!
今回登場した関数
getActiveAttrib
- リンク:getActiveAttrib
- 概要:アクティブなアトリビュートを通し番号から取得する。総数はgl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES)によって取得できる。
- 構文:
gl.getActiveAttrib(program, index)
- 引数:
- program: 対象のWebGLProgram
- index: 該当する番号
- 返り値:WebGLActiveInfo. name, size, typeの情報が含まれる。
- 補足:gl_VertexIDも取得される場合があります。そのため、独自に設定したアトリビュートの一覧が得られるわけではないことに注意してください。gl_VertexIDのロケーションは-1なので、ロケーションでバリデーションを掛けてもいいでしょう。
getAttribLocation
- リンク:getAttribLocation
- 概要:プログラム内で使われるアトリビュートの、vertexAttributeArray内で使用するスロットの通し番号を取得する。uniformと同じく「location」という番号が付いているが、全く異なる性質のものである。
- 構文:
const location = gl.getAttribLocation(program, name);
- 引数:
- program: 対象のWebGLProgram
- name; 該当するアトリビュートの名前
- 返り値:該当するアトリビュートが使用するスロット番号。アクティブでないアトリビュートの場合は-1が返るが、gl_VertexIDの場合も-1が返る。
- 補足:今後説明するかもしれませんが。行列アトリビュート、mat[m]ないしはmat[mxn]の場合、mの個数分だけ連番で予約されます。その場合、最小のものだけが取得されます。
bindAttribLocation
- リンク:bindAttribLocation
- 概要:対象となるアトリビュートに対し、それが使用するスロット番号を強制する。
- 構文:
gl.bindAttribLocation(program, index, name);
- 引数:
- program: 対象のWebGLProgram
- index: 使用するスロットの番号
- name: 対象のアトリビュート名
- 返り値:なし
- 補足:layout修飾子で先に指定されている場合は無効です。また、既に予約されている番号と競合した場合、エラーになります。
vertexAttribPointer
- リンク:vertexAttribPointer
- 概要:現在global stateにおいてARRAY_BUFFER枠にバインドされているarrayBufferを、vertexAttributeArrayの該当する番号に紐付ける。その際、データの取得方法(レイアウト)を設定する。
- 構文:
gl.vertexAttribPointer(index, size, type, normalized, stride, offset);
- 引数:
- index: 対象となるvertexAttributeArrayのスロット番号
- size: 連続で取得する数の個数。1,2,3,4のいずれか。
- type: 生成する数の型。次の種類がある。REVについては省略(詳細はリンク先を参照)
- gl.BYTE: 1バイト整数(-128~127)
- gl.SHORT: 2バイト整数(-32768~32767)
- gl.UNSIGNED_BYTE: 1バイト非負整数(0~255)
- gl.UNSIGNED_SHORT: 2バイト非負整数(0~65535)
- gl.FLOAT: IEEE32bit浮動小数点数
- gl.HALF_FLOAT: IEEE16bit浮動小数点数
- gl.INT: 4バイト整数(-2^31~2^31-1)
- gl.UNSIGNED_INT: 4バイト非負整数(0~2^32-1)
- normalized: floatにキャスティングする際に、範囲に応じて-1~1もしくは0~1に正規化する。整数の場合は-1~1, 非負整数の場合は0~1. 小数の場合は効果なし。
- stride: データフェッチのストライド。フェッチは整数nに対してn x stride + offsetから開始されるが、そのstride. 0以上255以下でなければならない。また、typeのバイト長の整数倍でなければならない。0の場合はtypeのバイト長にsizeを掛けたものが自然に設定される。
- offset: データフェッチのオフセット。フェッチは整数nに対してn x stride + offsetから開始されるが、そのoffset. 非負整数である必要がある。typeのバイト長の整数倍でなければならない。
- 返り値:なし
- 補足:引数は省略できません。つまり、データの紐付けだけしてレイアウトは据え置き、のようなことはできません。ARRAY_BUFFER枠のバッファの紐付けとレイアウトの構成は同時に実行されます。もしバッファの中身だけ書き換えたい場合は、WebGLBufferの書き換え用の関数を使ってください。また、レイアウトだけを変更することもできません。ARRAY_BUFFER枠のバッファが存在しない(null)場合にこれを実行するとできそうですが、その場合関数の実行自体失敗するので、レイアウトはそのままです。
enable/disable VertexAttribArray
- リンク:enableVertexAttribArray
- リンク:disableVertexAttribArray
- 概要:該当する番号のvertexAttributeを有効化/無効化する。
- 構文:
gl.enableVertexAttribPointer(index);
gl.disableVertexAttribPointer(index);
- 引数:
- index: 対象となるvertexAttributeArrayのスロット番号
- 返り値:なし
- 補足:無効化されている場合は既定値が使われます。詳しくは今後説明します。
よくある誤解
vertexAttribPointerの解説の際に、この処理はプログラムが関与しないということを散々強調しました。この処理はuniformでいうところのuniform関数に相当し、その際に使うlocationオブジェクトはプログラムに紐付けられているので、誤解が生じやすいんですが、attributeのlocationはプログラム関係ないですから、よく誤解の原因になります。というかmdnのサイトでもこれに相当する変数はindexが使われることが多いです。locationという名称は自分の知る限り一度も出てきません。特殊なオブジェクトではないからです。
wgldのサイトではここで初登場するんですが、つまりattLocationのところですが、何番目のアトリビュート、という言い方をされています。この言い方ではプログラムに関連するindexであると誤解してしまうのは当然で、自分もp5のデバッグをしていて初めて知ったので、それまでは完全に誤解していました。なおp5のWebGLもそこら辺を誤解してる人が構築したようです(ひどいな)。さすがにThreeはそんなことしていませんが...あと関係ないですが本来sizeと称される変数がattStrideと名前がついててわかりにくいですね...それで正式にstrideを使うインターリーブのところで案の定ふたつのstrideが現れて混乱を招いています。いや、このサイトでWebGL学ぶ人は大変ですね...ご愁傷様です。過去の自分だよ。そういうわけで、せっせとこんな記事を書いているというわけなんですね。