【Three.js】写真の中にラインを侵入させる

2021年7月18日JavaScript,Three.js

「分かるようになる」という事は「ビックリしなくなる」という事。少し寂しいわ…。

はじめに

だまし絵とかトリックアート的なものを作ってみたいと思っていたら、こんなものができました。

できたもの

お分かりになりますでしょうか。

リボンが赤ちゃんの手の向こう側に回り込んでいます。そう、リボンが写真の中に入ってしまっているのです。

タネ明かし

さっそくタネ明かし。

はい。画像を重ねて配置して、その間をリボンが通っているだけでした。

画像を用意する

1.奥の画像

イラストじゃなくて写真が良いと思います。その方が「あれっ!?写真なのに!?」ってなりますよね。

2.手前の画像

がんばって赤ちゃんを切り抜きます。髪の毛のところが雑ですけど、奥の画像と重ねてしまえば違和感は無くなります。背景は透過させておきましょう。

2つの画像を配置する

//*****************************************
// 写真パネルを作る
//*****************************************
function createPanel() {
  
  // 画像を読み込む
  const textureLoader = new THREE.TextureLoader();
  const backTexture = textureLoader.load("./picture/ribon/back.jpg");
  const frontTexture = textureLoader.load("./picture/ribon/front.png");

  // 奥のパネル
  const backGeo = new THREE.PlaneGeometry(10, 10);
  const backMat = new THREE.MeshBasicMaterial({map: backTexture});
  const back = new THREE.Mesh(backGeo, backMat);
  back.position.set(0, 10, 0);
  scene.add(back);

  // 手前のパネル
  const frontGeo = new THREE.PlaneGeometry(10,10);
  const frontMat = new THREE.MeshBasicMaterial({map: frontTexture, transparent: true});
  const front = new THREE.Mesh(frontGeo, frontMat);
  front.position.set(0.05, 10, 0.5);
  scene.add(front);
}

まずはTextureLoaderクラスで画像を読み込みます。で、手前と奥それぞれのマテリアルのmapプロパティに読み込んだ画像を設定してあげます。

背景を透過させたい手前のマテリアルは、transparentプロパティにtrueを設定します。これやらないと手前のパネルの背景が真っ黒です。

こんな感じで2つの画像を配置すれば、ほぼ完成したようなものです。あとはラインを描くだけ。

ラインを描く

//*****************************************
// ラインを作る
//*****************************************
let lines = []; 
function createLine() {
  for (let i = 0; i < 10; i++) {
    const points = [];

    points.push(new THREE.Vector3(20, 8 + i * 0.01, 20));
    points.push(new THREE.Vector3(15, 7 + i * 0.01, 15));
    points.push(new THREE.Vector3(10, 6.5 + i * 0.01, 10));
    points.push(new THREE.Vector3(5, 8 + i * 0.01, 4));
    points.push(new THREE.Vector3(3, 9.3 + i * 0.01, 0.1));
    points.push(new THREE.Vector3(1.3, 9.2 + i * 0.01, 0.5));
    points.push(new THREE.Vector3(1.2, 8.9 + i * 0.01, 1));
    points.push(new THREE.Vector3(2, 8.7 + i * 0.01, 1.4));
    points.push(new THREE.Vector3(3.0, 9.1 + i * 0.01, 1.2));
    points.push(new THREE.Vector3(3.2, 9.8 + i * 0.01, 0.5));
    points.push(new THREE.Vector3(2.5, 10.3 + i * 0.01, 0.02));
    points.push(new THREE.Vector3(0, 10.6 + i * 0.01, 1));
    points.push(new THREE.Vector3(-5, 10.5 + i * 0.01, 1.5));
    points.push(new THREE.Vector3(-10, 11.5 + i * 0.01, 1.5));
    points.push(new THREE.Vector3(-15, 13.5 + i * 0.01, 1.5));
    points.push(new THREE.Vector3(-20, 14.8 + i * 0.01, 1.5));

    const curve = new THREE.CatmullRomCurve3(points);
    const curvePoints = curve.getPoints(200);

    const geo = new THREE.BufferGeometry().setFromPoints(curvePoints);
    const mat = new THREE.LineBasicMaterial({color: 0xf03ef0});
    const line = new THREE.Line(geo, mat);
    lines.push(line);
    scene.add(line);
  }
}

CatmullRomCurve3クラスに座標を渡してあげると、滑らかな曲線を作ってくれます。その曲線を200個に分割して、その分割点となる座標をもとにラインを作っています。この辺は公式ドキュメントに載ってますので、詳しくはそちらをどうぞ。

ラインは1本だけだと細くてよく見えないので10本の束にしてあります。

ラインを動かす

//*****************************************
// ラインを動かす
//*****************************************
const start = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
const count = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
function moveLine() {
  for (let i = 0; i < 10; i++) {
    lines[i].geometry.setDrawRange(start[i], count[i]);
    count[i] += 1;

    if (count[i] > 100) {
      start[i] += 1;
    }
    if (start[i] > 201) {
      count[i] = 0;
      start[i] = 0;
    }
  }
}

//*****************************************
// 描画
//*****************************************
function renderScene() {
  requestAnimationFrame(renderScene);
  webGLRenderer.render(scene, camera);
  moveLine();
}

ラインを動かすための関数を作ります。ジオメトリのsetDrawRangeメソッドでラインの描画開始位置と描画する長さを変化させます。詳しくは以前の記事をどうぞ。

この関数を描画ループ内で呼んであげれば完成です。

あとがき

アイデア次第でおもしろいものがつくれそうですが、アイデアが浮かびません…。あと、webサイトで使うとなるとレスポンシブ対応が大変そうだなぁと思いました。今回は以上です。ありがとうございました。

おしゃれ度

★★☆☆☆

Posted by ナカタ