📹

Three.jsでカメラの向きによってMeshの色を変化させる実装方法

2023/11/28に公開

Three.jsでカメラの向きによってMeshの色を変化させる実装方法

開発環境

package.json

{
  "name": "name-sample",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "vite": "^4.3.9"
  },
  "dependencies": {
    "orbit-controls": "^1.2.4",
    "three": "^0.153.0"
  },
  "volta": {
    "node": "16.13.0",
    "npm": "9.3.1"
  }
}

主なバージョンや環境の確認

  1. HTMLファイル1つ
  2. JavaScriptファイル1つ
  3. node: 16.13.0
  4. npm: 9.3.1
  5. vite: 4.3.9
  6. three: 0.153.0
  7. orbit-controls: 1.2.4
     

インストールをするもの、アカウントを作るもの

  1. Viteの環境の作り方。

 

 

  1. Three.jsとOrbitControls
  • Three.js

 

  • OrbitControls

 

  • npmのダウンロード
npm
  npm install three
  npm install orbit-controls

まずThree.jsとWebGLを解説

  • WebGLとは

WebGL (Web Graphics Library) は、互換性があるウェブブラウザーにおいて、プラグインを使用せずにインタラクティブな 3D や 2D のグラフィックをレンダリングするための JavaScript API です。HTMLの <canvas> 要素へ OpenGL ES 2.0 に密接に従った API を導入することにより、WebGL を実現します。これにより、ユーザーの端末が提供するハードウェアのグラフィックアクセラレーションを API で利用することが可能になります。

引用元:

 

  • Three.jsとは

上記のWebGLを、もっと手軽に使えるライブラリで手軽に3D表現ができます!

 

実装したコード

下記のように実装いたしました。

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>title</title>
    <style>
      * {margin: 0; padding: 0;}
      html, body {
          overflow: hidden;
      }
  </style>
  </head>
  <body>
    <div id="webgl"></div>
    <script type="module" src="/script.js"></script>
  </body>
</html>


 
 

今回、JavaScriptの記述が多いので一個、一個説明いたします。。。

 
 

Three.jsとOrbitControlsをimportする。

script.js

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

 
 

汎用変数

汎用変数は、letで再代入する可能性のあるものを冒頭に記載

script.js

  // 汎用変数
  let run = true; // レンダリングループフラグ

  // three.js に関連するオブジェクト用の変数
  let scene;            // シーン
  let camera;           // カメラ
  let renderer;         // レンダラ
  let material;         // マテリアル
  let torus;            // トーラスメッシュ
  let controls;         // カメラコントロール
  let axesHelper;       // 軸ヘルパーメッシュ

 
 

関数に関する設定

  1. 初期設定の関数
script.js
  function init() {
  }
  1. 描画する関数
script.js
  function render() {
  }

 
 

Three.jsのカメラに関する設定

script.js

  // カメラに関するパラメータ PerspectiveCamera
  // const CAMERA_PARAM = {
  //     fovy: 40.0,
  //     aspect: window.innerWidth / window.innerHeight,
  //     near: 0.1,
  //     far: 60.0,
  //     x: 5.0,
  //     y: 0.0,
  //     z: 5.0,
  //     lookAt: new THREE.Vector3(0.0, 0.0, 0.0),
  // };

  // カメラに関するパラメータ Orthographic
  const aspect = window.innerWidth / window.innerHeight; // アスペクト比
  const scale = 2.0;                                     // 切り取る空間の広さ(スケール)
  const horizontal = scale * aspect;                     // 横方向のスケール
  const vertiacal = scale;                               // 縦方向のスケール
  const CAMERA_PARAM = {
    left: -horizontal,  // 切り取る空間の左端
    right: horizontal,  // 切り取る空間の右端
    top: vertiacal,     // 切り取る空間の上端
    bottom: -vertiacal, // 切り取る空間の下端
    near: 0.1,
    far: 60.0,
    x: 5.0,
    y: 0.0,
    z: 5.0,
    lookAt: new THREE.Vector3(0.0, 0.0, 0.0),
  };

  function init() {


    // *** カメラの定義 *** //

    // カメラ PerspectiveCamera
    // camera = new THREE.PerspectiveCamera(
    //     CAMERA_PARAM.fovy,
    //     CAMERA_PARAM.aspect,
    //     CAMERA_PARAM.near,
    //     CAMERA_PARAM.far
    // );
    // camera.position.set(CAMERA_PARAM.x, CAMERA_PARAM.y, CAMERA_PARAM.z);
    // camera.lookAt(CAMERA_PARAM.lookAt);

    // カメラ OrthographicCamera
    camera = new THREE.OrthographicCamera(
      CAMERA_PARAM.left,
      CAMERA_PARAM.right,
      CAMERA_PARAM.top,
      CAMERA_PARAM.bottom,
      CAMERA_PARAM.near,
      CAMERA_PARAM.far
    );
    camera.position.set(CAMERA_PARAM.x, CAMERA_PARAM.y, CAMERA_PARAM.z);
    camera.lookAt(CAMERA_PARAM.lookAt);

    // *** カメラの定義 *** //

  }

 
 
今回の実装は、PerspectiveCameraとOrthographicCameraもどちらで見ても問題ないと思い、コメントアウトでPerspectiveCameraの設定も残してあります。
 
ちなみにカメラの違いですが、
PerspectiveCameraは、遠近感がある投影方法。(奥行きがある)
OrthographicCameraは、平行感のある投影方法。(奥行きがない)

今回は違いに分かりにくい為、下記のURLをご参考にしていただけると幸いです。

 
 

レンダラーに関する設定

script.js

  // レンダラに関するパラメータ
  const RENDERER_PARAM = {
    clearColor: 0xffffff,
    width: window.innerWidth,
    height: window.innerHeight,
  };


  function init() {

    // *** レンダラの定義 *** //

    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(RENDERER_PARAM.clearColor));
    renderer.setSize(RENDERER_PARAM.width, RENDERER_PARAM.height);
    const wrapper = document.querySelector('#webgl');
    wrapper.appendChild(renderer.domElement);

    // *** レンダラの定義 *** //

  }

 
 

上記のはWebGLの描画を、どの要素に描画して、背景色とレンダリングした時の幅と高さを設定することが目的です。

 
 

シーンに関する設定

script.js

  function init() {
    // *** シーンの定義 *** //
    scene = new THREE.Scene();
    // *** シーンの定義 *** //
  }

 

シーンは、ライトや物体(メッシュ)を置くためのもの。

 
 

ジオメトリに関する設定

script.js

  function init() {
    
    const torusGeometry = new THREE.TorusGeometry(0.85, 0.25, 20, 80, Math.PI * 2);
    
  }

 
 

今回は、TorusGeometryを使用します。Three.jsではgeometries一覧に色んなGeometryがあるので、他のGeometryに試すのもいいかもしれません。

 
 

マテリアルに関する設定

script.js

  function init() {

     material = new THREE.ShaderMaterial({
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      uniforms: {

        // カメラの方向ベクトル(例: カメラがZ軸負方向を向いている場合)
        cameraDirection: { value: new THREE.Vector3(0, 0, 0) }, 

        // ライトの方向ベクトル(例: ライトがX軸正方向とZ軸正方向を結ぶ斜め方向を照らしている場合)
        lightDirection: { value: new THREE.Vector3(-1, 0.5, 0.5).normalize() }, 
        
        // ウィンドウの解像度を渡す
        u_resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, 

        // 色相値を時間に応じて変化するuniform変数 初期値として0.0を設定
        u_time: { value: 0.0 }

      },
    });

  }

 
 

今回はカメラの向きによってMeshの色を変化させる為、マテリアルをShaderMaterialでカスタムして、シェーダーコードを書く必要があります。
uniformsでカメラの方向ベクトル、ライトの方向ベクトル、ウィンドウの解像度、色相の時間に応じて変化する値を初期設定をシェーダー側に送ります。

 
 

シェーダーに関する設定

script.js

  function init() {


    // *** カスタムシェーダーの定義 *** //

    // vertex
    const vertexShader = `

          // 頂点法線を表すvarying変数
          varying vec3 vNormal;   
  
          void main() {

            // 法線行列を使用して、法線をワールド座標系に変換し、vNormalに格納
            vNormal = normalMatrix * normal; 

            // 最終的な位置をgl_Positionに格納
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 

          }

    `;


    // fragment
    const fragmentShader = `

          // HSBからRGBへの変換関数
          vec3 hsb2rgb(in vec3 c) {
              vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
              rgb = rgb * rgb * (3.0 - 2.0 * rgb);
              return c.z * mix(vec3(1.0), rgb, c.y);
          }

          // 頂点法線を表す
          varying vec3 vNormal;

          // u_timeは変化の速さを調整するパラメータのuniform変数
          uniform float u_time;

          // カメラの方向ベクトルを追加 
          uniform vec3 cameraDirection;

          // ライトの方向ベクトルを追加
          uniform vec3 lightDirection; 

          // ウィンドウの解像度を追加
          uniform vec2 u_resolution;
          
          void main() {

            // カメラの方向のベクトルで内積を計算し、頂点の法線とカメラ方向の角度を計算
            float cameraAngle = dot(normalize(vNormal), normalize(cameraDirection));

            // ライトの方向のベクトルで内積を計算し、頂点の法線とライトの角度を計算
            float lightAngle = dot(normalize(vNormal), normalize(lightDirection));

            // カメラの方向に対する角度が大きいほど小さな明るさになるように計算
            float cameraBrightness = 5.0 - abs(cameraAngle);

            // ライトの方向に対する角度に基づいて明るさを計算
            float lightBrightness = abs(lightAngle);

            // 頂点の最終的な明るさが計算
            float brightness = (cameraBrightness + lightBrightness) * 0.25;

            // 明るさを調整して0〜1の範囲にする
            float adjustedBrightness = brightness + 0.25 * 0.25; 
  
            // 色相を格納する変数です。色相は色の種類を表す値で、0.0から1.0の範囲で表されます。
            float hue;

            // cameraDirectionのyとxの成分から角度(ラジアン)を計算
            // カメラの方向ベクトルがx軸となす角度が求められる
            float angle = atan(cameraDirection.y, cameraDirection.x);

            // 計算された角度が負の場合、360度を足して正の範囲に調整しています。
            if (angle < 0.0) {
              angle += 2.0 * 3.14159;
            }

            // 調整された角度を0.0から1.0の範囲に正規化しています。これにより、色相が0.0から1.0の範囲で表されるようになります。
            float normalizedAngle = angle / (2.0 * 3.14159);

            // x軸方向に動かす場合の色の可変性を増やす
            float xMovement = abs(cameraDirection.x) * 20.0;

            // xMovement が大きいほど色相に対する変更が加わり、cameraDirection.yが中心からずれると変更が強くなる設定をしております。
            // 最後の引数 0.5 は、補間の割合を示しており、0.5 に設定されているため元の値と変更後の値との中間を取っています。
            normalizedAngle = mix(normalizedAngle, normalizedAngle * (1.0 - xMovement) + (0.5 - cameraDirection.y) * xMovement, 0.5);

            // 最終的な色相 hue に計算された normalizedAngle を代入しています。
            // これにより、元の色相に対する変更が最終的な色相に反映されます。
            hue = normalizedAngle;

            // 色相値を時間に応じて変化させる
            hue += u_time * 100.0; // 100.0は変化の速さを調整するパラメータです

            // HSB値を設定
            vec3 hsb = vec3(hue, 2.0 * 1.5, 0.65); 

            // HSBをRGBに変換
            vec3 rgbColor = hsb2rgb(hsb); 

            // 色相と明るさを組み合わせる
            vec3 finalColor = rgbColor * adjustedBrightness;

            // 色相と明るさを組み合わせた値をFragColorに出力
            gl_FragColor = vec4(finalColor, 1.0);

          }
    `;

    // *** カスタムシェーダーの定義 *** //

  }

 
 

コメントアウトに詳細な処理を書いたので細かな説明は、割愛します。
ポイントだけ触れますと、cameraDirectionにy軸上、x軸上の角度(ラジアン)を求めることです。

  • ラジアンとは?

上記の参考URLでご紹介いたしましたが、今回の実装においては

  1. 中心に物体。
  2. 弧の周りにカメラが移動する。
  3. 物体と弧の周りの角度(ラジアン)または中心角を求める。またatan(アークタンジェント、逆正接)で求めるとラジアン単位で返してくれます。
  4. 求めたラジアンの数値に対して、HSB値を入れてあげる。最後はHSBをRGBに変換する。

最後は、Web上の概念ではHSBが描画されないので、RGBに変換してFragColorに出力する。

 
 

MeshとOrbitControlsに関する設定

script.js
  function init() {

    torus = new THREE.Mesh(torusGeometry, material);
    torus.rotation.set(0.0, 0.5, 0.0)

    // シーンにtorusを追加
    scene.add(torus);

    // *** 作業補助 *** //

    // 軸ヘルパー
    axesHelper = new THREE.AxesHelper(5.0);
    scene.add(axesHelper);

    // コントロール
    controls = new OrbitControls(camera, renderer.domElement);

    // *** 作業補助 *** //

  }

 
 

torusGeometryとmaterialをMeshの引数に加えて、scene.addに追加。
axesHelperは任意。マウスドラッグした際にカメラの手動制御を入れたいので、OrbitControlsの引数に対してcameraとrenderer.domElementを入れます。

 
 

renderの全体のコード

script.js

  function render() {
    // 再帰呼び出し
    if (run === true) { requestAnimationFrame(render); }

    // カスタムシェーダーのuniform変数を更新
    material.uniforms.u_time.value += 0.05;

    // cameraをuniform変数に設定する
    const cameraDirection = new THREE.Vector3();
    camera.getWorldDirection(cameraDirection);

    // カメラの方向ベクトルを設定する(例: カメラの向きに合わせてシェーダー側に送る)
    material.uniforms.cameraDirection.value = cameraDirection; 

    // コントロールの更新
    controls.update();

    // 描画
    renderer.render(scene, camera);
  }

 
 

render関数には

  1. アニメーションが有効であれば、requestAnimationFrameを使用して再帰的にrender関数を呼び出す処理を行います。
  2. 先ほど作ったmaterialのuniform変数のu_timeを0.05更新する。
  3. 描画した際のカメラの向き(方向ベクトル)を取得する為、Three.jsのObject3DでもあるgetWorldDirectionを追加。
  4. 先ほど作ったmaterialのuniform変数のcameraDirectionに、カメラの向き(方向ベクトル)の値を入れる。
  5. OrbitControlsを更新して、sceneとcameraを描画する。
  • getWorldDirectionとは??

 
 

DOMContentLoadedの全体のコード

script.js

window.addEventListener('DOMContentLoaded', () => {

    // 初期化処理
    init();

    // リサイズイベント
    window.addEventListener('resize', () => {

      renderer.setSize(window.innerWidth, window.innerHeight);

      // PerspectiveCamera
      // camera.aspect = window.innerWidth / window.innerHeight;

      // Orthographic
      const aspect = window.innerWidth / window.innerHeight;
      const scale = 2.0;
      const horizontal = scale * aspect;
      const vertiacal = scale;
      camera.left = -horizontal;
      camera.right = horizontal;
      camera.top = vertiacal;
      camera.bottom = -vertiacal;

      // updateProjectionMatrix
      camera.updateProjectionMatrix();

    }, false);

    // 描画処理
    run = true;

    render();

  }, false);

 
 

HTMLが完全に読み込まれ後、init関数を実行。
リサイズがあればrendererのsetSizeとcameraをupdateProjectionMatrixを更新。
あとは描画処理してrender関数を実行。

 
 

JavaScriptの全体のコード

script.js

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

// グローバル空間を汚染しないように、無名関数で全体を囲んでいます。
(() => {

  // 汎用変数
  let run = true; // レンダリングループフラグ

  // three.js に関連するオブジェクト用の変数
  let scene;            // シーン
  let camera;           // カメラ
  let renderer;         // レンダラ
  let material;         // マテリアル
  let torus;            // トーラスメッシュ
  let controls;         // カメラコントロール
  let axesHelper;       // 軸ヘルパーメッシュ

  // カメラに関するパラメータ PerspectiveCamera
  // const CAMERA_PARAM = {
  //     fovy: 40.0,
  //     aspect: window.innerWidth / window.innerHeight,
  //     near: 0.1,
  //     far: 60.0,
  //     x: 5.0,
  //     y: 0.0,
  //     z: 5.0,
  //     lookAt: new THREE.Vector3(0.0, 0.0, 0.0),
  // };

  // カメラに関するパラメータ Orthographic
  const aspect = window.innerWidth / window.innerHeight; // アスペクト比
  const scale = 2.0;                                     // 切り取る空間の広さ(スケール)
  const horizontal = scale * aspect;                     // 横方向のスケール
  const vertiacal = scale;                               // 縦方向のスケール
  const CAMERA_PARAM = {
    left: -horizontal,  // 切り取る空間の左端
    right: horizontal,  // 切り取る空間の右端
    top: vertiacal,     // 切り取る空間の上端
    bottom: -vertiacal, // 切り取る空間の下端
    near: 0.1,
    far: 60.0,
    x: 5.0,
    y: 0.0,
    z: 5.0,
    lookAt: new THREE.Vector3(0.0, 0.0, 0.0),
  };


  // レンダラに関するパラメータ
  const RENDERER_PARAM = {
    clearColor: 0xffffff,
    width: window.innerWidth,
    height: window.innerHeight,
  };



  function init() {

    // *** シーンの定義 *** //

    scene = new THREE.Scene();

    // *** シーンの定義 *** //


    // *** レンダラの定義 *** //

    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(RENDERER_PARAM.clearColor));
    renderer.setSize(RENDERER_PARAM.width, RENDERER_PARAM.height);
    const wrapper = document.querySelector('#webgl');
    wrapper.appendChild(renderer.domElement);

    // *** レンダラの定義 *** //

    // *** カメラの定義 *** //

    // カメラ PerspectiveCamera
    // camera = new THREE.PerspectiveCamera(
    //     CAMERA_PARAM.fovy,
    //     CAMERA_PARAM.aspect,
    //     CAMERA_PARAM.near,
    //     CAMERA_PARAM.far
    // );
    // camera.position.set(CAMERA_PARAM.x, CAMERA_PARAM.y, CAMERA_PARAM.z);
    // camera.lookAt(CAMERA_PARAM.lookAt);

    // カメラ OrthographicCamera
    camera = new THREE.OrthographicCamera(
      CAMERA_PARAM.left,
      CAMERA_PARAM.right,
      CAMERA_PARAM.top,
      CAMERA_PARAM.bottom,
      CAMERA_PARAM.near,
      CAMERA_PARAM.far
    );
    camera.position.set(CAMERA_PARAM.x, CAMERA_PARAM.y, CAMERA_PARAM.z);
    camera.lookAt(CAMERA_PARAM.lookAt);

    // *** カメラの定義 *** //



    // *** カスタムシェーダーの定義 *** //

    // vertex
    const vertexShader = `

          // 頂点法線を表すvarying変数
          varying vec3 vNormal;   
  
          void main() {

            // 法線行列を使用して、法線をワールド座標系に変換し、vNormalに格納
            vNormal = normalMatrix * normal; 

            // 最終的な位置をgl_Positionに格納
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 

          }

    `;


    // fragment
    const fragmentShader = `

          // HSBからRGBへの変換関数
          vec3 hsb2rgb(in vec3 c) {
              vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
              rgb = rgb * rgb * (3.0 - 2.0 * rgb);
              return c.z * mix(vec3(1.0), rgb, c.y);
          }

          // 頂点法線を表す
          varying vec3 vNormal;

          // u_timeは変化の速さを調整するパラメータのuniform変数
          uniform float u_time;

          // カメラの方向ベクトルを追加 
          uniform vec3 cameraDirection;

          // ライトの方向ベクトルを追加
          uniform vec3 lightDirection; 

          // ウィンドウの解像度を追加
          uniform vec2 u_resolution;
          
          void main() {

            // カメラの方向のベクトルで内積を計算し、頂点の法線とカメラ方向の角度を計算
            float cameraAngle = dot(normalize(vNormal), normalize(cameraDirection));

            // ライトの方向のベクトルで内積を計算し、頂点の法線とライトの角度を計算
            float lightAngle = dot(normalize(vNormal), normalize(lightDirection));

            // カメラの方向に対する角度が大きいほど小さな明るさになるように計算
            float cameraBrightness = 5.0 - abs(cameraAngle);

            // ライトの方向に対する角度に基づいて明るさを計算
            float lightBrightness = abs(lightAngle);

            // 頂点の最終的な明るさが計算
            float brightness = (cameraBrightness + lightBrightness) * 0.25;

            // 明るさを調整して0〜1の範囲にする
            float adjustedBrightness = brightness + 0.25 * 0.25; 
  
            float hue;

            float angle = atan(cameraDirection.y, cameraDirection.x);
            if (angle < 0.0) {
              angle += 2.0 * 3.14159;
            }
            float normalizedAngle = angle / (2.0 * 3.14159);

            // x軸方向に動かす場合の色の可変性を増やす
            float xMovement = abs(cameraDirection.x) * 20.0;
            normalizedAngle = mix(normalizedAngle, normalizedAngle * (1.0 - xMovement) + (0.5 - cameraDirection.y) * xMovement, 0.5);

            hue = normalizedAngle;

            // 色相値を時間に応じて変化させる
            hue += u_time * 100.0; // 100.0は変化の速さを調整するパラメータです

            // HSB値を設定
            vec3 hsb = vec3(hue, 2.0 * 1.5, 0.65); 

            // HSBをRGBに変換
            vec3 rgbColor = hsb2rgb(hsb); 
  
            vec3 finalColor = rgbColor * adjustedBrightness;
            gl_FragColor = vec4(finalColor, 1.0);

          }
    `;

    // *** カスタムシェーダーの定義 *** //




    // *** torusのMeshを作成 *** //

    const torusGeometry = new THREE.TorusGeometry(0.85, 0.25, 20, 80, Math.PI * 2);

    material = new THREE.ShaderMaterial({
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      uniforms: {
        // カメラの方向ベクトル(例: カメラがZ軸負方向を向いている場合)
        cameraDirection: { value: new THREE.Vector3(0, 0, 0) }, 

        // ライトの方向ベクトル(例: ライトがX軸正方向とZ軸正方向を結ぶ斜め方向を照らしている場合)
        lightDirection: { value: new THREE.Vector3(-1, 0.5, 0.5).normalize() }, 
        
        // ウィンドウの解像度を渡す
        u_resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, 

        // 色相値を時間に応じて変化するuniform変数 初期値として0.0を設定
        u_time: { value: 0.0 }
      },
    });

    torus = new THREE.Mesh(torusGeometry, material);
    torus.rotation.set(0.0, 0.5, 0.0)
    // シーンにtorusを追加
    scene.add(torus);

    // *** torusのMeshを作成 *** //


    // *** 作業補助 *** //

    // 軸ヘルパー
    axesHelper = new THREE.AxesHelper(5.0);
    scene.add(axesHelper);

    // コントロール
    controls = new OrbitControls(camera, renderer.domElement);

    // *** 作業補助 *** //

  }

  function render() {
    // 再帰呼び出し
    if (run === true) { requestAnimationFrame(render); }

    // カスタムシェーダーのuniform変数を更新
    material.uniforms.u_time.value += 0.05;

    // cameraをuniform変数に設定する
    const cameraDirection = new THREE.Vector3();
    camera.getWorldDirection(cameraDirection);

    // カメラの方向ベクトルを設定する(例: カメラの向きに合わせてシェーダー側に送る)
    material.uniforms.cameraDirection.value = cameraDirection; 

    // コントロールの更新
    controls.update();

    // 描画
    renderer.render(scene, camera);
  }

  window.addEventListener('DOMContentLoaded', () => {
    // 初期化処理
    init();

    // リサイズイベント
    window.addEventListener('resize', () => {

      renderer.setSize(window.innerWidth, window.innerHeight);

      // PerspectiveCamera
      // camera.aspect = window.innerWidth / window.innerHeight;

      // Orthographic
      const aspect = window.innerWidth / window.innerHeight;
      const scale = 2.0;
      const horizontal = scale * aspect;
      const vertiacal = scale;
      camera.left = -horizontal;
      camera.right = horizontal;
      camera.top = vertiacal;
      camera.bottom = -vertiacal;

      // updateProjectionMatrix
      camera.updateProjectionMatrix();

    }, false);

    // 描画処理
    run = true;

    render();

  }, false);

})();


 

実装した結果

 
 

ポイントは

  1. カメラの向き、物体の回転で何かしらの処理をしたい場合は、三角関数必須

数学が苦手な人(私も苦手ですが。。。)は、三角関数というと苦手意識があるんじゃないかなと思います。
その原因は最初から数式を理解しようとするからだと思います。(数式だと。。。かなりとっつきにくい)

数式ではなくまずはざっくり概念を理解して、下記のURLのように最初は直感的な部分に、触れてから実装することをオススメします。

 
 

  1. getWorldDirectionを入れること。
script.js

    // cameraをuniform変数に設定する
    const cameraDirection = new THREE.Vector3();
    camera.getWorldDirection(cameraDirection);

    // カメラの方向ベクトルを設定する(例: カメラの向きに合わせてシェーダー側に送る)
    material.uniforms.cameraDirection.value = cameraDirection; 

今回は、レンダリングされた際にカメラの向きもしくは視点に対して出た値を
material.uniforms.cameraDirection.valueに送る必要があります。

camera.getWorldDirection(cameraDirection);は、シーン全体に対する共通の座標のことで、
すべてのオブジェクトや要素が配置される基準となる座標を指します。

 
 

この実装を使った実績

 
 

まとめ

今回は、Three.jsでカメラの向きによってMeshの色を変化させる実装方法を紹介しました。

数学的要素やWebGLの概念が組み合わさったものはかなり難し目ではあります。
とくにラジアン、sin、cosなど三角関数などは、日常生活でなかなか使うものではないのでとっつきにくいと思います。
最初のうちは、カメラの向き、物体の移動は、三角関数がいるなぁ。。。ぐらいでも
あくまで数学的要素は、1つの道具で武器と考えればいいんじゃないでしょうか(個人の感想)。

ただ正直、私自身もThree.js、WebGLに関してはまだまだ理解できていないところはありますが、今後実装を積み重ねて表現の幅を増やしていきたいと考えております。

 

参考記事URL

  • Viteの環境の作り方

 

 

  • Three.jsとWebGL

 

 

 

  • PerspectiveCameraとOrthographicCameraの違い

 

  • Three.jsのTorusGeometry

 

  • ラジアンとは?

 

  • getWorldDirectionとは??

 

  • 分かりにくい三角関数(sin,cos)をシミュレーション/図解で理解!

Discussion