【Three.js】背景色を徐々に変化させる

2021年4月25日JavaScript,Three.js

できた時すごく嬉しかった

はじめに

クリックしたら背景色が変わるヤツを作ります。最初は白だけど、じわじわと黄色になるようにしたいと思います。

背景色の設定方法

webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xfff8d4));

WebGLRendererクラスのsetClearColorメソッドで背景色の設定ができます。↑この例だと16進数で色の指定をしていますが、書き方は他にも色々あります。ちなみにRGBで指定する時はこうです↓。

webGLRenderer.setClearColor("rgb(255, 248, 212)");

setClearColorメソッドの引数を少しずつ変化させていけば、背景色もじわじわと変わってくれるという事です。

Tween.jsでアニメーションさせる

出ました。みんな大好きTween。これを使ってsetClearColorメソッドに渡す値を変化させていこうと思います。

function clickWindow() {
  let color = {c: 0xfff8d4};
  new TWEEN.Tween(color)
      .to({c: 0xf9ff52}, 2000)
      .easing(TWEEN.Easing.Linear.None)
      .onUpdate(function() {
        webGLRenderer.setClearColor(new THREE.Color(color.c));
      })
      .start();
}

↑こう書いたらこうなりました↓。(チカチカします!)

setClearColorメソッドに渡す値を、2秒かけて0xfff8d4から0xf9ff52へ減らしているんですけど、16進数のままだとダメみたいですね。単純に数を減らしているだけなので、いろいろな色を通ってしまうようです。そのせいでチカチカします。

RGBで指定するように変更

function clickWindow() {
  let color = {r: 255, g: 248, b: 212};
  new TWEEN.Tween(color)
      .to({r: 249, g: 255, b: 82}, 2000)
      .easing(TWEEN.Easing.Linear.None)
      .onUpdate(function() {
        webGLRenderer.setClearColor(new THREE.Color("rgb(" + Math.floor(color.r) + "," + 
                                                              Math.floor(color.g) + "," + 
                                                              Math.floor(color.b) + ")"));
      })
      .start();
}

16進数を2桁ずつ分けて10進数にします。それをrとgとbに割り当てました。そうしたらこうなりました↓。

とってもイイ感じ。自分でもうまくできたなぁと感心しました。

ムニュムニュ動いてるヤツについては何も触れませんでしたが、これはパーリンノイズを使って動かしてます。全コードを載せておくので良かったら参考にしてください。

<!DOCTYPE html>

<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>ムニュムニュ</title>
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  <script type="text/javascript" src="../libs/utils/perlin.js"></script>
  <style>
      * {
        margin: 0;
        padding: 0;
      }
      html,body {
          overflow: hidden;
          height: 100%;
      }
  </style>
</head>

<body>
  
  <div id="screen"></div>
  
  <script type="module">
    
    import * as THREE from "https://unpkg.com/three@0.127.0/build/three.module.js";
    import { TrackballControls } from "https://unpkg.com/three@0.127.0/examples/jsm/controls/TrackballControls.js";
    import { TWEEN } from "https://unpkg.com/three@0.127.0/examples/jsm/libs/tween.module.min.js";
    
    window.onload = function() {
      
      let scene, camera, webGLRenderer;
      let plane, trackballControls;
      let circleMesh, circleMesh2;
      let aspRatio, fov;
      
      init();
      main();
      renderScene();
      window.addEventListener("resize", resizeWindow);
      window.addEventListener("click", clickWindow);

      // 初期化
      function init() {
        scene = new THREE.Scene();  
    
        aspRatio = window.innerWidth / window.innerHeight;
        fov = getFov(aspRatio);
        camera = new THREE.PerspectiveCamera(fov, aspRatio, 1, 10000);
        camera.position.set(0, 10, 50);
        scene.add(camera);
  
        webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.setClearColor(new THREE.Color(0xfff8d4));
        document.getElementById("screen").appendChild(webGLRenderer.domElement);

        const plane = new THREE.GridHelper(200, 16);
        scene.add(plane);
  
        trackballControls = new TrackballControls(camera, webGLRenderer.domElement);
        trackballControls.target = new THREE.Vector3(0, 0, 0);
        trackballControls.rotateSpeed = 3;
      }

      // メイン
      function main() {
        const geo = new THREE.CircleGeometry(10, 64);
        const mat = new THREE.MeshBasicMaterial({color: 0xffff00});
        mat.side = THREE.DoubleSide;
        circleMesh = new THREE.Mesh(geo, mat);
        scene.add(circleMesh);
      } 

      // 円の頂点を動かす
      function moveCircleVertex() {
        const positions = circleMesh.geometry.attributes.position.array;
        const time = Date.now() / 1000;
        const r = 10;
        const k = 1;

        for (let i = 0; i < positions.length; i+=3) {
          const p = new THREE.Vector2(
            positions[i],
            positions[i + 1]
          );

          p.normalize().multiplyScalar(r + 1.0 * noise.perlin2(p.x * k + time, p.y * k));

          positions[i] = p.x
          positions[i + 1] = p.y
        }
        
        // 頂点情報の更新
        circleMesh.geometry.attributes.position.needsUpdate = true;
        
        // 法線情報の更新
        circleMesh.geometry.computeVertexNormals();
      }

      // 度→ラジアン
      function degToRad(deg) {
        return deg * Math.PI / 180;
      }

      // ラジアン→度
      function radToDeg(rad) {
        return rad * 180 / Math.PI;
      }

      // ウィンドウリサイズ
      function resizeWindow() {
        aspRatio = window.innerWidth / window.innerHeight;
        camera.aspect = aspRatio
        camera.fov = getFov(aspRatio);
        camera.updateProjectionMatrix();
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        renderScene();
      }
      
      // ウィンドウクリック
      function clickWindow() {
        let color = {r: 255, g: 248, b: 212};
        new TWEEN.Tween(color)
            .to({r: 249, g: 255, b: 82}, 2000)
            .easing(TWEEN.Easing.Linear.None)
            .onUpdate(function() {
              webGLRenderer.setClearColor(new THREE.Color("rgb(" + Math.floor(color.r) + "," + 
                                                                    Math.floor(color.g) + "," + 
                                                                    Math.floor(color.b) + ")"));
            })
            .start();
      }

      // 視野角取得
      function getFov(aspRatio) {
        let fov;
        if (aspRatio > 1) {
          fov = 25;
        } else if (aspRatio > 0.8) {
          fov = 30;
        } else if (aspRatio > 0.6) {
          fov = 40;
        } else if (aspRatio > 0.5) {
          fov = 50;
        } else {
          fov = 60;
        }
        return fov;
      }
      
      // 描画
      function renderScene() {
        const clock = new THREE.Clock();
        const delta = clock.getDelta();
        trackballControls.update(delta);
        moveCircleVertex();
        TWEEN.update();
        requestAnimationFrame(renderScene);
        webGLRenderer.render(scene, camera);
      }
    }
  </script>
</body>
</html>

あとがき

できましたねぇ。どうやったらできるのか相当頭ひねったので、意図した通りに動いたときの感動たるや。プログラミングの醍醐味ですわねぇ。

今回できたものをもう少し作りこんで、おしゃれなモノを完成させようと思っています。ありがとうございました。

おしゃれ度

★★☆☆☆

Posted by ナカタ