category icon
2022-04-12
JavaScript - Three.js

Three.js の発光エフェクトを使い SF 感をマシマシにする方法

Three.js
0.139.2
profile
hikaru
Software Developer / DIY'er

今回は Three.js に付属しているモデルを読み込み、エフェクトを使って発光させます。発光させることで印象が変わり、いい感じに見えるようになります。

左が発光エフェクト『なし』、右が発光エフェクト『あり』です。発光エフェクトを使うと、SF 感がマシマシです。

メインプログラムコード

今回のメインコードファイルを載せます。

main.ts (コード全体)
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass";
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

/**
 * Three.js関連のユーティリティモジュール
 */
export module ThreeUtils {
  /**
   * GLTFファイルを読み込む
   */
  export function loadGltf(url: string, onProgress?: (event: ProgressEvent) => void): Promise<GLTF> {
    return new Promise((resolve, reject) => {
      new GLTFLoader().load(url, resolve, onProgress, reject);
    });
  }
}

const bloomParams = {
  /** トーンマッピング: 露光量 */
  exposure: 1.8,

  /** 発光エフェクト: 強さ */
  bloomStrength: 3.0,

  /** 発光エフェクト: 半径 */
  bloomRadius: 1.2,

  /** 発光エフェクト: 閾値 */
  bloomThreshold: 0.0,
};

async function main() {
  // DOMを取得
  const appElement = document.querySelector<HTMLElement>(`#myApp`)!;

  // モデルをロード
  const model = await ThreeUtils.loadGltf(`static/PrimaryIonDrive.glb`);

  // ライト(平行光)のセットアップ
  const directionalLight = new THREE.DirectionalLight(0xffffff);
  directionalLight.position.set(7, 10, -2);
  directionalLight.color.set(0xffffff);
  directionalLight.castShadow = true; // ライトの影を有効
  directionalLight.intensity = 0.3;
  directionalLight.shadow.radius = 3.0;

  // ライト(環境光)のセットアップ
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.9);

  // XYZ軸のセットアップ
  const axes = new THREE.AxesHelper(25);

  // シーンのセットアップ
  const scene = new THREE.Scene();
  scene.add(model.scene);
  scene.add(directionalLight);
  scene.add(ambientLight);
  scene.add(axes);

  // カメラのセットアップ
  const camera = new THREE.PerspectiveCamera(
    50,
    appElement.clientWidth / appElement.clientHeight,
    0.01,
    1000
  );
  camera.position.set(4, 2, 2);
  camera.lookAt(scene.position);

  // アニメーションミキサーのセットアップ
  const mixer = new THREE.AnimationMixer(model.scene);
  mixer.clipAction(model.animations[0].optimize()).play();

  // レンダラーのセットアップ
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(appElement.clientWidth, appElement.clientHeight);
  renderer.setClearColor(0x000000); // 背景色
  renderer.shadowMap.enabled = true; // レンダラー:シャドウを有効にする
  renderer.toneMapping = THREE.ReinhardToneMapping;
  renderer.toneMappingExposure = Math.pow(bloomParams.exposure, 4.0);

  // エフェクト: 通常レンダリング
  const renderPass = new RenderPass(scene, camera);

  // エフェクト: 発光エフェクト
  const bloomPass = new UnrealBloomPass(
    new THREE.Vector2(appElement.clientWidth, appElement.clientHeight),
    bloomParams.bloomStrength,
    bloomParams.bloomRadius,
    bloomParams.bloomThreshold,
  );

  // エフェクトのセットアップ
  const effectComposer = new EffectComposer(renderer);
  effectComposer.addPass(renderPass);
  effectComposer.addPass(bloomPass);
  effectComposer.setSize(appElement.clientWidth, appElement.clientHeight);

  // カメラコントローラーのセットアップ
  const orbitControls = new OrbitControls(camera, renderer.domElement);
  orbitControls.maxPolarAngle = Math.PI * 0.5;
  orbitControls.minDistance = 1;
  orbitControls.maxDistance = 100;
  orbitControls.autoRotate = false;    // カメラの自動回転設定
  orbitControls.autoRotateSpeed = 1.0; // カメラの自動回転速度

  // DOMに追加
  appElement.appendChild(renderer.domElement);

  // 最終更新時間
  let lastTime = 0;

  // リフレッシュレートに応じて呼び出しをリクエスト
  requestAnimationFrame(time => {
    // 最終更新時間を設定
    lastTime = time;

    // アニメーションを開始
    animate(time);
  });

  /**
   * フレーム描画処理
   */
  function animate(time: number) {
    // リフレッシュレートに応じて呼び出しをリクエスト
    requestAnimationFrame(time => animate(time));

    // 経過時間を算出
    const delta = (time - lastTime) / 1000;

    // アニメーションミキサーを更新
    mixer.update(delta);

    // カメラコントローラーを更新
    orbitControls.update();

    // シーンを描画する
    effectComposer.render(time);

    // 最終更新時間を設定
    lastTime = time;
  }
}

main();

(1) import 部分

main.ts
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass";
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

発光エフェクト関連のライブラリは three.js に含まれていますが、import * as THREE from "three" だけでは使用できません。
使用するためには上記コードのように UnrealBloomPass モジュールを改めてインポートする必要があります。

(2) 発光エフェクトのパラメータ設定部分

main.ts
const bloomParams = {
  /** トーンマッピング: 露光量 */
  exposure: 1.8,

  /** 発光エフェクト: 強さ */
  bloomStrength: 3.0,

  /** 発光エフェクト: 半径 */
  bloomRadius: 1.2,

  /** 発光エフェクト: 閾値 */
  bloomThreshold: 0.0,
};
main.ts
// エフェクト: 通常レンダリング
const renderPass = new RenderPass(scene, camera);

// エフェクト: 発光エフェクト
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(appElement.clientWidth, appElement.clientHeight),
  bloomParams.bloomStrength,
  bloomParams.bloomRadius,
  bloomParams.bloomThreshold,
);

// エフェクトのセットアップ
const effectComposer = new EffectComposer(renderer);
effectComposer.addPass(renderPass);
effectComposer.addPass(bloomPass);
effectComposer.setSize(appElement.clientWidth, appElement.clientHeight);

トーンマッピングの露光量 (exposure)

exposure … 0.9 / 1.8 / 2.7

発光エフェクトの強さ (bloomStrength)

bloomStrength … 1.5 / 3.0 / 6.0

発光エフェクトの半径 (bloomRadius)

bloomRadius … 0.6 / 1.2 / 2.4

発光エフェクトの閾値 (bloomThreshold)

bloomThreshold … 0.0 / 0.5 / 1.0

(3) 発光エフェクトの描画

main.ts
// 描画する
effectComposer.render(time);

既にエフェクトに関する設定関係は完了しているので、あとはアニメーションループでの描画したいタイミングで effectComposer.render(time); を呼び出すだけとなります。

おわりに

今回は発光エフェクトについてでした。やはり動きやビジュアルのあるプログラムは楽しいですね~✨しばらくは Three.js 関連の記事を書ければと思います。

今回のデモと GitHub のソースコードの URL を記載しておきます。ライセンスは MIT としていますのでご自由に利用ください。