0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

p5.jsのメビウスの帯でつなぎ目のないテクスチャを実現する

0
Last updated at Posted at 2026-09-28

はじめに

 メビウスの帯を以前描画しました。

 それで、今回はテクスチャなので法線は出てきません。何がしたいかというと、メビウスの帯にテクスチャを貼ります。ただ裏表がないので、普通に貼ると途切れてしまいます。それを解消する方法です。
 なおライティングと組み合わせる方法については分かりませんでした。

コード全文

/*
  https://qiita.com/inaba_darkfox/items/563e94fd4f1d5da0df95
  ax(), ay(), az()はクォータニオンが表す正規直交基底の3本のベクトルを
  個別に取得する関数
  まとめて取得する場合はgetAxes()がある(x,y,z成分がベクトル)
  あとinitで1,0,0,0にできるように追加

  2026/08/06
  長崎原爆の日です。
  それで、p5.js/2.3.2では_glが使用禁止なので、modelを使ってください。

  2026/09/28
  ライティングを組み合わせる方法が分からん。調べてください。
*/

const {Vecta, Quarternion} = fisce.fox3Dtools;

let loopFunction = () => {};

function setup() {
  createCanvas(400, 400, WEBGL);
	smooth();

  const geom = new p5.Geometry();

  // クォータニオン使った方が楽です(イメージを作るのが)
  const v = new Vecta(0, 0, 200);
  const q = new Quarternion();
  // なおイメージはzが手前でyが下でxが右で向きとしてはz-->xの方向に回す感じですね

  for(let i=0; i<=200; i++){
    const angleY = Math.PI*2*i/200;
    const angleX = Math.PI*i/200;

    // qをyのまわりにローカル回転、次いでxのまわりにローカル回転
    q.init();

    // 先にy回転して軸の中心を決める
    q.localRotate(0,1,0,angleY);
    const centerPosition = q.az().mult(200);

    // そのあとでx回転してねじる
    q.localRotate(1,0,0,angleX);
    const diffVector = q.ay().mult(40);

    // centerからdiffでマイナス、プラス
    const pUp = centerPosition.sub(diffVector, true);
    const pDown = centerPosition.add(diffVector, true);

    geom.vertices.push(
      createVector(...pUp.array()), createVector(...pDown.array())
    );
	  geom.uvs.push(i/200,0,i/200,1);
  }
  for(let i=0; i<200; i++){
    const lu = 2*i;
    const ru = 2*(i+1);
    const ld = 2*i+1;
    const rd = 2*(i+1)+1;
    geom.faces.push([lu, ru, ld], [ld, ru, rd]);
  }

  const sh = baseMaterialShader().modify({
	  'vec4 getFinalColor':`(vec4 color, vec2 texCoord){
   vec2 uv = texCoord;
   if(gl_FrontFacing){
         color = texture(uSampler, vec2(uv.x*0.5, 1.0-uv.y));
		 }else{
   color = texture(uSampler, vec2(uv.x*0.5+0.5, uv.y));
		 }
		 return color;
	  }`
  });

  const lightVector = new Vecta(0,0,-1);
  const lightVectorAxis = new Vecta(0,1,0);

	const tex = createGraphics(2000,40);const ctx=tex.drawingContext;
	const gd = ctx.createLinearGradient(0,0,2000,0);
	gd.addColorStop(0,'red');gd.addColorStop(1,'red');gd.addColorStop(0.5,'orange');
	ctx.fillStyle=gd;
	ctx.fillRect(0,0,2000,40);
	
	tex.fill('white');
	tex.textSize(18);
	tex.textStyle(ITALIC);
	tex.textAlign(CENTER,CENTER);
	for(let x=0;x<=2000;x+=100){ tex.text('TEXTURE',x,20); }

	texture(tex);

  loopFunction = () => {

    shader(sh);
    lightVector.rotate(lightVectorAxis, TAU/240);

    background(0);
    orbitControl();

    specularMaterial(32);
	  model(geom);
  }
}

function draw() {
  loopFunction();
}

普通に貼る場合

 次のようなテクスチャを用意します。1000x40でやりたいと思います。適当です。細長ければいいだろうというわけです。

const tex = createGraphics(1000,40)
	tex.background('red');
	tex.fill('white');
	tex.textSize(18);
	tex.textStyle(ITALIC);
	tex.textAlign(CENTER,CENTER);
	for(let x=0;x<=1000;x+=100){ tex.text('TEXTURE',x,20); }

これをはりつけようとすると、こうなります。

image.png

このように、境目で逆になってしまいます。なおあらかじめp5.Geometryにuvsをいじってuvを付けてあります。境目に行くまでに上下が半分だけ回転するためこのようになります。一回転すればいいんですが...
これを解消する方法を考えます。

長さを2倍にする

 これを解消するため、テクスチャの長さを2倍にします。また、グラデーションを付けて、真ん中で別の色、最後に元の色になるようにします。

const tex = createGraphics(2000,40);const ctx=tex.drawingContext;
	const gd = ctx.createLinearGradient(0,0,2000,0);
	gd.addColorStop(0,'red');gd.addColorStop(1,'red');gd.addColorStop(0.5,'orange');
	ctx.fillStyle=gd;
	ctx.fillRect(0,0,2000,40);
	
	tex.fill('white');
	tex.textSize(18);
	tex.textStyle(ITALIC);
	tex.textAlign(CENTER,CENTER);
	for(let x=0;x<=2000;x+=100){ tex.text('TEXTURE',x,20); }

紅葉の季節が近いことを意識して、赤とオレンジにしました。もうすぐ秋ですね。

それはいいとして、この状態でshaderの方もいじります。どういじるかというと、getFinalColorを使います。p5のshaderいじるやつはどこをどういじるのかよくわかんないんですが、このfinalColorというのはuvを第二引数に取り、これにより出力をいじれるようなので、それでやってみます。

  const sh = baseMaterialShader().modify({
	  'vec4 getFinalColor':`(vec4 color, vec2 texCoord){
   vec2 uv = texCoord;
   if(gl_FrontFacing){
         color = texture(uSampler, vec2(uv.x*0.5, 1.0-uv.y));
		 }else{
   color = texture(uSampler, vec2(uv.x*0.5+0.5, uv.y));
		 }
		 return color;
	  }`
  });

shaderで何をしてるかというと、まず表面を描画する際はuvのxを半分にします。こうすることで左半分が描画されます。次に、裏面を描画する際にはuvのxを0.5倍してさらに0.5を加えています。こうすることで右半分の描画になります。そしてuv.yについては1から引くことで逆にしています。これにより境目がピッタリつながり、このようにきれいになります。

image.png

綺麗なものは見てて気持ちいいですね。

なおライティングも組み合わせられるはずですが、よくわからなかったです。

おわりに

 ここまでお読みいただいてありがとうございました。

image.png

追記

 あのあとスマホで見たらジオメトリが消えていました。真っ黒ですね。なんも見えませんでした。Androidです。
おそらくcreateGraphicsで2000x40で作ったのがまずかったんでしょう。数をいじったら動きましたが、...

 上記のコードでは、OffscreenCanvasというのを用いています。

 1600x64とかいうかなりでかいサイズで用いています。普通にスマホでも動きます。OffscreenCanvasは通常のキャンバスと何が違うかというと、DOM要素ではありません。つまり、キャンバス要素のDOM機能をはぎ取った感じですね。グラフィックだけが欲しい場合の機能です。それで充分なんです、普通は。

 p5のcreateGraphicsで作られるキャンバスは、オフスクリーンではありません。大昔からある機能ですが、作った人がそうしたんだと思いますが、通常のキャンバス要素を用意して、隠してるだけです。なので属性をいじると普通に可視化されます。オフスクリーンキャンバスは可視化できないので、描画しないと見えません。

 それで本題ですが、どうもスマホのサイズ限界はオフスクリーンキャンバスの方が大きいようです。なんででしょうね?知りませんが、まあ自分がスケッチを描く上では役立っています。ありがたいです。なおOpenProcessingの自分のサイトを見るとサムネが壊れています。これもキャンバス要素からフェッチしているからで、オフスクリーンで作れば、そういう問題は起きません(ただしp5はオフスクリーンキャンバスを扱えないので、p5の機能は一切援用できません、ご了承ください。テクスチャ関数に入れても何も起きません)。

 昔だったらこの手の機種依存の問題に真剣に取り組んでいたんですが、最近はもう疲れてしまったし、それにスケッチの本質に問題があるならともかく、プラットフォームが問題なのであれば、自サイトでやればいいわけですから、もう真剣に取り組む気が起きないです。自由にのびのびコードを書きます。補足終わり。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?