はじめに
p5は現在、キューブマップを実装していませんが、それを勝手に作って遊んじゃおうという無法な企画です。楽しいですね!
内容的にはこないだのTEXTURE_2D_ARRAYのコードを流用します。
キューブマップを作るには次の図を参照します。
なぜなら公式(WebGL)がそうしているからです。郷に入りては、です。
これに見合うようにテクスチャをそれぞれ用意して、然るべくアタッチすれば、いい具合に塩梅してくれるというわけです。
コード全文
// 🍓
function setup() {
console.clear();
createCanvas(400,400,WEBGL);
smooth();
smooth();
noStroke();
const gl = drawingContext;
const cube = buildGeometry(()=>{
box(200);
//sphere(200);
});
// shaderを作る
const sh = baseMaterialShader().modify({
vertexDeclarations:'out vec3 vPosition;',
'void afterVertex': `() { vPosition = aPosition; }`,
'fragmentDeclarations':`
uniform samplerCube uTex;
in vec3 vPosition;
`,
'vec4 getFinalColor':`(vec4 color, vec2 texCoord){
color = texture(uTex, vPosition);
return color;
}`
});
const tex = createMonoColorTextures(gl, 4, [
'red','orange','blue','green','purple','yellow'
]);
shader(sh); // これでprogramができる
const pg = sh._glProgram;
gl.useProgram(pg); // これでprogramが走る
const loc = gl.getUniformLocation(pg, "uTex");
// 4番に入れる。
gl.uniform1i(loc, 4);
draw=()=>{
shader(sh);
background(0);
orbitControl(1,1,1,{freeRotation:true});
fill(0);
model(cube);
}
}
function createMonoColorTextures(gl, index = 0, palette = []){
const targets = [
gl.TEXTURE_CUBE_MAP_POSITIVE_X,
gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
];
const offscreens = createOffscreens();
const tex = gl.createTexture();
gl.activeTexture(gl.TEXTURE0 + index);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
for(let i=0; i<6; i++){
gl.texImage2D(targets[i], 0, gl.RGBA, 400, 400, 0, gl.RGBA, gl.UNSIGNED_BYTE, offscreens[i].elt);
}
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
return tex;
}
function createOffscreens(){
const grs = [];
for(let i=0; i<6; i++){
const gr = createGraphics(400,400);
gr.background('pink');
gr.textSize(80);
gr.textAlign(CENTER,CENTER);
grs.push(gr);
}
const s = (i,x,y,r) => {
grs[i].push();
grs[i].translate(x,y);
grs[i].rotate(r);
grs[i].text('🍓',0,0);
grs[i].pop();
}
for(let i=0; i<6; i++){
for(let y=100; y<400; y+=100){
for(let x=100; x<400; x+=100){
s(i,x,y,random(TAU));
}
}
}
// ランダムシード
const rr = Array(36);
for(let i=0; i<36; i++){ rr[i] = random(TAU); }
// 共有辺は全部で12あり、12個のペアに分かれる。
// PXの右、NZの左
s(0,400,100,rr[0]); s(0,400,200,rr[1]); s(0,400,300,rr[2]);
s(5,0,100,rr[0]); s(5,0,200,rr[1]); s(5,0,300,rr[2]);
// PXの左、PZの右
s(0,0,100,rr[3]);s(0,0,200,rr[4]);s(0,0,300,rr[5]);
s(4,400,100,rr[3]);s(4,400,200,rr[4]);s(4,400,300,rr[5]);
// PZの左、NXの右
s(4,0,100,rr[6]);s(4,0,200,rr[7]);s(4,0,300,rr[8]);
s(1,400,100,rr[6]);s(1,400,200,rr[7]);s(1,400,300,rr[8]);
// NXの左、NZの右
s(1,0,100,rr[9]);s(1,0,200,rr[10]);s(1,0,300,rr[11]);
s(5,400,100,rr[9]);s(5,400,200,rr[10]);s(5,400,300,rr[11]);
// PZの上、PYの下
s(4,100,0,rr[12]); s(4,200,0,rr[13]); s(4,300,0,rr[14]);
s(2,100,400,rr[12]); s(2,200,400,rr[13]); s(2,300,400,rr[14]);
// PZの下、NYの上
s(4,100,400,rr[15]); s(4, 200, 400, rr[16]); s(4,300,400,rr[17]);
s(3,100,0,rr[15]); s(3,200,0,rr[16]); s(3,300,0,rr[17]);
// ここから先は回転が絡むため、難易度が跳ね上がる。
// PXの上、PYの右
s(0,100,0,rr[18]);s(0,200,0,rr[19]); s(0,300,0,rr[20]);
s(2,400,300,rr[18]-PI/2); s(2,400,200,rr[19]-PI/2); s(2,400,100,rr[20]-PI/2);
// NXの上、PYの左
s(1,100,0,rr[21]); s(1,200,0,rr[22]); s(1,300,0,rr[23]);
s(2,0,100,rr[21]+PI/2); s(2,0,200,rr[22]+PI/2); s(2,0,300,rr[23]+PI/2);
// NZの上、PYの上
s(5,100,0,rr[24]); s(5,200,0,rr[25]); s(5,300,0,rr[26]);
s(2,300,0,rr[24]+PI); s(2,200,0,rr[25]+PI); s(2,100,0,rr[26]+PI);
// PXの下、NYの右
s(0,100,400,rr[27]); s(0,200,400,rr[28]);s(0,300,400,rr[29]);
s(3,400,100,rr[27]+PI/2);s(3,400,200,rr[28]+PI/2);s(3,400,300,rr[29]+PI/2);
// NXの下、NYの左
s(1,100,400,rr[30]); s(1,200,400,rr[31]); s(1,300,400,rr[32]);
s(3,0,300,rr[30]-PI/2); s(3,0,200,rr[31]-PI/2);s(3,0,100,rr[32]-PI/2);
// NZの下、NYの下
s(5,100,400,rr[33]); s(5,200,400,rr[34]);s(5,300,400,rr[35]);
s(3,300,400,rr[33]+PI);s(3,200,400,rr[34]+PI);s(3,100,400,rr[35]+PI);
// ちなみに角だが、ちょっと考えればわかるが、どうしたって欠損が発生するので、無理です。
// お疲れ様でした!!!
grs[0].fill(0);
grs[0].noStroke();
grs[0].textSize(60);
//grs[0].text('strawberry',200,200);
return grs;
}
キューブマップを作ろう
キューブマップを作るには、グラフィックを用意し、texImage2Dでちまちまはめていくだけです。とても簡単です。以前もそうやったはずです。
この時と違ってめんどうなのでmipmapとかはしません。また数字を置くだけでは詰まんないので、ちょっと難しいことをします。ざっくり言うと、境界線上に絵文字で絵を置きます。境界線上に何かを置くのは難しいです。つながりを考慮しないと途切れてしまうからです。上の図を参考に、どことどこがどのようにつながっているかを考えながらちょっとずつやっていきます。
具体的には上のコードに書いてあるので、それを読んでください。
baseMaterialShaderの改変
例によってライティングはしないんで、普通にファイナルカラーをいじります。キューブマップは基本的に法線でやるんですが、立方体の法線は面に垂直なので、キューブマップに適していません。そこでローカル位置情報を使います。しかしそれはバリイングとして渡されていません。ビューポジションしかないんです。ライティングに使う情報です。そういうわけで、こっちで勝手に用意します。
// shaderを作る
const sh = baseMaterialShader().modify({
vertexDeclarations:'out vec3 vPosition;',
'void afterVertex': `() { vPosition = aPosition; }`,
'fragmentDeclarations':`
uniform samplerCube uTex;
in vec3 vPosition;
`,
'vec4 getFinalColor':`(vec4 color, vec2 texCoord){
color = texture(uTex, vPosition);
return color;
}`
});
キューブマップの場合プレシジョン宣言は不要です。おそらくWebGL1からある機能だからでしょう。それで、vPositionをぶちこみます。これできちんと表示されるはずです。やってみようか。
ちゃんとつながっていますね。なおsphereでもポジション法線が使えるんですが、どうなるかというと、
こうなります。しかしライティングが無いのでいまいち遠近感に欠けますね...これじゃ円だ。でもまあ綺麗です。
自分は詳しくないんですが、おそらくライティングと組み合わせる方法はあるはずなので探してみてください。
文字は逆になる
なお文字は逆になります。
普通にやってるはずなのになぜ逆になるかというと、p5の仕様が普通「じゃない」からです。y軸が逆なんですね。その影響です。おそらくキューブマップの導入をためらっている原因の一つになっている気がします。面倒なんです、その辺。
以前の記事ではマイナスを付けて対処していますが、
どうなんでしょうね...マイナスを付けないときちんと使えないキューブマップ...
まあいいや...
おわりに
いろんなフルーツで試してみても楽しいかと思います。食欲の秋です。たべたい!たべたい!🍓
ちなみにこれを書いてる管理人は今日西伊豆の海でめっちゃ泳いで潜ってきました。まだ夏だぜ!
ここまでお読みいただいてありがとうございました。
追記
スマホで見たらテクスチャがおかしなことになっていたので、ピクセル濃度をいじる形で書き換えました。実際のサイズで用意しないといけないからです。もちろん400で用意することもできるわけですが、その辺若干面倒ですね...




