今回は Three.js に付属しているモデルを読み込み、エフェクトを使って発光させます。発光させることで印象が変わり、いい感じに見えるようになります。
左が発光エフェクト『なし』、右が発光エフェクト『あり』です。発光エフェクトを使うと、SF 感がマシマシです。
メインプログラムコード
今回のメインコードファイルを載せます。
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 部分
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) 発光エフェクトのパラメータ設定部分
const bloomParams = {
/** トーンマッピング: 露光量 */
exposure: 1.8,
/** 発光エフェクト: 強さ */
bloomStrength: 3.0,
/** 発光エフェクト: 半径 */
bloomRadius: 1.2,
/** 発光エフェクト: 閾値 */
bloomThreshold: 0.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);
トーンマッピングの露光量 (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) 発光エフェクトの描画
// 描画する
effectComposer.render(time);
既にエフェクトに関する設定関係は完了しているので、あとはアニメーションループでの描画したいタイミングで effectComposer.render(time);
を呼び出すだけとなります。
おわりに
今回は発光エフェクトについてでした。やはり動きやビジュアルのあるプログラムは楽しいですね~✨しばらくは Three.js 関連の記事を書ければと思います。
-
デモ
https://stackblitz.com/github.com/alclimb/threejs-model-luminescence
-
ソース (GitHub)
-
ソース (ZIP ダウンロード)
https://github.com/alclimb/threejs-model-luminescence/archive/refs/heads/main.zip
今回のデモと GitHub のソースコードの URL を記載しておきます。ライセンスは MIT としていますのでご自由に利用ください。