【Three.js】発展編

はじめに

どうも、NNCの石上です。
今回はThree.jsの発展編として、当サイトのMV部分をどのように実装しているかを見ていきましょう。

完成見本とサンプルコード

今回の完成見本はこちらになります。動画では描画されていませんが、マウスの動きに連動して3Dのオブジェクトも動くようになっています。
HTML / Javascriptのファイルにそれぞれ以下のコードを貼り付けていただくと、サンプルのような描画ができると思います。(言うまでもなく、事前にライブラリの読み込みを済ませておきましょう)

<canvas id="background"></canvas>

App({ el: 'background' });
function App(conf) {
  conf = {
    fov: 80, //カメラの位置、初期値:75
    cameraZ: 70, //初期値:75
    xyCoef: 1, //初期値:50、小さければ小さいほど鋭くなる
    zCoef: 6, //初期値:10、大きければ大きいほど高くなる
    lightIntensity: 0.8,
    ambientColor: 0x000000,
    light1Color: 0x3E6BFF, //青
    light2Color: 0x3EE2FF, //水色
    light3Color: 0x3EE2FF, //オレンジ
    light4Color: 0xFFEF37, //黄色
    ...conf
  };

  let renderer, scene, camera, cameraCtrl;
  let width, height, cx, cy, wWidth, wHeight;
  const TMath = THREE.Math;

  let plane;
  const simplex = new SimplexNoise();

  const mouse = new THREE.Vector2();
  const mousePlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
  const mousePosition = new THREE.Vector3();
  const raycaster = new THREE.Raycaster();

  const noiseInput = document.getElementById('noiseInput');
  const heightInput = document.getElementById('heightInput');

  init();

  function init() {
    //レンダラー・カメラの設定
    renderer = new THREE.WebGLRenderer({
      canvas: document.getElementById(conf.el),
      antialias: true,
      alpha: true });
    camera = new THREE.PerspectiveCamera(conf.fov);
    camera.position.z = conf.cameraZ;
    //リサイズ
    updateSize();
    window.addEventListener('resize', updateSize, false);
    // マウスと連動してアニメーション
    document.addEventListener('mousemove', e => {
      const v = new THREE.Vector3();
      camera.getWorldDirection(v);
      v.normalize();
      mousePlane.normal = v;
      mouse.x = (e.clientX / width) * 2 - 1;
      mouse.y = - (e.clientY / height) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      raycaster.ray.intersectPlane(mousePlane, mousePosition);
    });

    initScene();
    animate();
  }

  function initScene() {
    scene = new THREE.Scene();
    initLights();
    let mat = new THREE.MeshLambertMaterial({ color: 0xffffff, side: THREE.DoubleSide });
    // let mat = new THREE.MeshPhongMaterial({ color: 0xffffff });
    // let mat = new THREE.MeshStandardMaterial({ color: 0x808080, roughness: 0.5, metalness: 0.8 });
    let geo = new THREE.PlaneBufferGeometry(wWidth, wHeight, wWidth / 2, wHeight / 2);
    plane = new THREE.Mesh(geo, mat);
    scene.add(plane);
    plane.rotation.x = -Math.PI / 2 - 0.2;
    plane.position.y = -25;
    camera.position.z = 60;
  }

  function initLights() {
    const r = 30;
    const y = 10;
    const lightDistance = 500;
    // light = new THREE.AmbientLight(conf.ambientColor);
    // scene.add(light);
    light1 = new THREE.PointLight(conf.light1Color, conf.lightIntensity, lightDistance);
    light1.position.set(0, y, r);
    scene.add(light1);
    light2 = new THREE.PointLight(conf.light2Color, conf.lightIntensity, lightDistance);
    light2.position.set(0, -y, -r);
    scene.add(light2);
    light3 = new THREE.PointLight(conf.light3Color, conf.lightIntensity, lightDistance);
    light3.position.set(r, y, 0);
    scene.add(light3);
    light4 = new THREE.PointLight(conf.light4Color, conf.lightIntensity, lightDistance);
    light4.position.set(-r, y, 0);
    scene.add(light4);
  }

  function animate() {
    requestAnimationFrame(animate);
    animatePlane();
    animateLights();
    renderer.render(scene, camera);
  };

  function animatePlane() {
    gArray = plane.geometry.attributes.position.array;
    const time = Date.now() * 0.0002;
    for (let i = 0; i < gArray.length; i += 3) {
      gArray[i + 2] = simplex.noise4D(gArray[i] / conf.xyCoef, gArray[i + 1] / conf.xyCoef, time, mouse.x + mouse.y) * conf.zCoef;
    }
    plane.geometry.attributes.position.needsUpdate = true;
    // plane.geometry.computeBoundingSphere();
  }

  function animateLights() {
    const time = Date.now() * 0.001;
    const d = 50;
    light1.position.x = Math.sin(time * 0.1) * d;
    light1.position.z = Math.cos(time * 0.2) * d;
    light2.position.x = Math.cos(time * 0.3) * d;
    light2.position.z = Math.sin(time * 0.4) * d;
    light3.position.x = Math.sin(time * 0.5) * d;
    light3.position.z = Math.sin(time * 0.6) * d;
    light4.position.x = Math.sin(time * 0.7) * d;
    light4.position.z = Math.cos(time * 0.8) * d;
  }

  function updateSize() {
    width = window.innerWidth; cx = width / 2;
    height = window.innerHeight; cy = height / 2;
    if (renderer && camera) {
      renderer.setSize(width, height);
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      const wsize = getRendererSize();
      wWidth = wsize[0];
      wHeight = wsize[1];
    }
  }

  function getRendererSize() {
    const cam = new THREE.PerspectiveCamera(camera.fov, camera.aspect);
    const vFOV = cam.fov * Math.PI / 180;
    const height = 2 * Math.tan(vFOV / 2) * Math.abs(conf.cameraZ);
    const width = height * cam.aspect;
    return [width, height];
  }
}

参考サイト

こちらの表現方法については、すでに公開されているコードを見本にしています。

Codepenで公開されているこちらのコードを基に、配色や山の傾斜などをサイトの世界観に合うようにカスタマイズしていきます。とは言っても、手を入れる箇所は下の部分だけです。

conf = {
    fov: 80, //カメラの位置
    cameraZ: 70, 
    xyCoef: 1, // 小さければ小さいほど山が鋭くなる
    zCoef: 6, // 大きければ大きいほど山が高くなる
    lightIntensity: 0.8,
    ambientColor: 0x000000,
    light1Color: 0x3E6BFF, //青
    light2Color: 0x3EE2FF, //水色
    light3Color: 0x3EE2FF, //オレンジ
    light4Color: 0xFFEF37, //黄色
    ...conf
  };

「xyCoef」「zCoef」で山の高さや鋭さ、「ambientColor」及び「light1Color ~ light4Color」で配色を設定します。この辺りの数値は、サイトの世界観にマッチするよう微調整していきましょう。

元々のコードでは画面下部にオブジェクトが表示されていると思いますが、当サイトのMV部分では描画範囲として定義されているcanvas要素に対して、「transform: rotate(180deg);」を適用しています。これにより、画面上部に3Dの背景オブジェクトを配置することができます。

以上で完成です!

RECENT ENTRIES

  • 【ウェブ解析】Core Web Vitalsを指標としたサイト改善方法

    VIEW MORE
  • 【jQuery】「jquery.cookie.js」で言語切り替え機能を実装する

    VIEW MORE
  • 【ウェブ解析】インプレッションにおけるSEOとMEOの重要性とは?

    VIEW MORE

CONTACT