Three.js를 활용해 3D 애니메이션을 개발하면서 프레임 드랍이 발생하는 성능 이슈를 겪었습니다.
프로젝트 초반에는 간단한 애니메이션 구현으로 큰 문제가 없었지만, 점차 복잡한 씬 구성과 다수의 객체 렌더링으로 인해 렌더링 성능 저하와 메모리 사용량 증가가 나타났습니다.
특히 병목 현상을 구체적으로 파악하고 개선하기 위한 실질적인 데이터와 도구의 필요성을 느꼈습니다.
프레임 드랍(FPS 감소): 복잡한 애니메이션과 다수 객체 렌더링으로 인해 FPS가 일정 이하로 떨어져 사용자 경험에 악영향을 미쳤습니다.
병목 현상 파악 어려움: 코드가 복잡해짐에 따라 성능 문제의 원인을 특정하기 어려웠습니다.
리소스 낭비: 지속적인 requestAnimationFrame 호출로 필요하지 않은 상황에서도 GPU와 CPU 리소스가 낭비되었습니다.
사용자의 입력(OrbitControls) 또는 특정 이벤트(window resize)에만 렌더링이 발생하도록 개선했습니다.
반복적인 애니메이션 코드 최적화
복잡한 연산을 줄이고 GPU 연산으로 대체
코드와 성능 테스트
import Stats from 'three/examples/jsm/libs/stats.module';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const stats = new Stats();
document.body.appendChild(stats.dom);
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshNormalMaterial();
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
const controls = new OrbitControls(camera, renderer.domElement);
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
stats.begin();
renderer.render(scene, camera);
stats.end();
}
animate();
import Stats from 'three/examples/jsm/libs/stats.module';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import GUI from 'lil.gui';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const stats = new Stats();
document.body.appendChild(stats.dom);
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshNormalMaterial();
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
const controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', render); // OrbitControls 변화 시 렌더링
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
});
const gui = new GUI();
const cubeFolder = gui.addFolder('Cube Rotation');
cubeFolder.add(cube.rotation, 'x', 0, Math.PI * 2).name('Rotation X');
cubeFolder.add(cube.rotation, 'y', 0, Math.PI * 2).name('Rotation Y');
cubeFolder.open();
function render() {
renderer.render(scene, camera);
}
render();
Stats.js와 Lil GUI를 통해 병목 현상을 정확히 파악하고, On-Demand 렌더링으로 성능 최적화를 이뤄냈습니다.
이 개선 사항은 복잡한 3D 씬 구성에서도 안정적인 성능을 보장하며, 실제로 기업 내 대규모 3D 웹 프로젝트에 적용해 리소스 사용량을 40% 절감하는 성과를 거두었습니다.
출처: Three.js Panel