先日、 Blenderでオブジェクトを発光させました が、今回はThree.jsでオブジェクトを発光させる手順のメモです。
Three.jsとNext.jsを使っていますが、本質的な部分としては Three.jsのEffectComposer と postprocessing unreal bloom を使って実装します。
ソースコード
app/src/page.tsx
'use client'; import { useEffect, useRef } from 'react'; import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'; import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass'; import { useOrbitControls } from '@/hooks/useOrbitControls'; import { useEffectComposer } from '@/hooks/useEffectComposer'; import { useResize } from '@/hooks/useResize'; import { useThree } from '@/hooks/useThree'; export default function UnrealBloomPassPage() { const canvasRef = useRef<HTMLCanvasElement>(null); const { windowWidth, windowHeight } = useResize(); const { THREE, renderer, camera, scene } = useThree(canvasRef.current); const effectComposer = useEffectComposer(renderer); const orbitControls = useOrbitControls(camera, canvasRef.current); useEffect(() => { if (!renderer || !camera || !effectComposer || !orbitControls) { return; } camera.position.set(4, 4, 8); camera.lookAt(new THREE.Vector3(0, 0, 0)); const cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial() ); scene.add(cube); renderer.setClearColor(0x000000, 1); const light = new THREE.DirectionalLight(0xFFFFFF); light.position.set(1, 1, 1); scene.add(light) effectComposer.addPass( new RenderPass(scene, camera) ); effectComposer.addPass(new UnrealBloomPass( new THREE.Vector2(windowWidth, windowHeight), 1, // Strength 0, // Radius 0, // Threshold )); renderer.setAnimationLoop(() => { orbitControls.update(); effectComposer.render(); }); }, [renderer, camera, scene, effectComposer, orbitControls]); useEffect(() => { if (!renderer || !camera || !effectComposer) { return; } renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(windowWidth, windowHeight); effectComposer.setSize(windowWidth, windowHeight); camera.aspect = windowWidth / windowHeight; camera.updateProjectionMatrix(); }, [renderer, camera, effectComposer, windowWidth, windowHeight]); return ( <canvas ref={ canvasRef } /> ); }
src/hooks/useOrbitControls.tsx
import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; export function useOrbitControls(camera: THREE.PerspectiveCamera | undefined, canvas: HTMLCanvasElement | null) { if (!camera || !canvas) { return; } const orbitControls = new OrbitControls(camera, canvas); return orbitControls; }
src/hooks/useEffectComposer.tsx
import * as THREE from 'three'; import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'; export function useEffectComposer( renderer: THREE.WebGLRenderer | undefined ) { if (!renderer) { return; } const effectComposer = new EffectComposer(renderer); return effectComposer; }
src/hooks/useResize.tsx
import { useEffect, useState } from 'react'; export function useResize() { const [ windowWidth, setWindowWidth ] = useState(0); const [ windowHeight, setWindowHeight ] = useState(0); useEffect(() => { window.removeEventListener('resize', handleResize); window.addEventListener('resize', handleResize, { passive: true }); handleResize(); return () => { window.removeEventListener('resize', handleResize); } }, []); function handleResize() { setWindowWidth(window.innerWidth); setWindowHeight(window.innerHeight); } return { windowWidth, windowHeight }; }
src/hooks/useThree.tsx
import { useEffect, useState } from 'react'; import * as THREE from 'three' export function useThree(canvas: HTMLCanvasElement | null) { const [ renderer, setRenderer ] = useState<THREE.WebGLRenderer>(); const [ camera, setCamera ] = useState<THREE.PerspectiveCamera>(); const [ scene ] = useState<THREE.Scene>(new THREE.Scene); useEffect(() => { if (!canvas) { return; } setRenderer(new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true })); }, [canvas]); useEffect(() => { if (!renderer) { return; } setCamera(new THREE.PerspectiveCamera()); }, [renderer]); return { THREE, renderer, camera, scene }; }