실무에서는 단순히 정적인 조명만으로는 부족한 경우가 많습니다.
시간에 따라 변화하는 동적 조명 시스템을 구현하는 방법을 살펴보겠습니다.
class DynamicLightingSystem {
constructor(scene) {
this.scene = scene;
this.lights = [];
this.dayTime = 0; // 0-24 시간
this.setupLights();
}
setupLights() {
// 주 광원 (태양)
this.sunLight = new THREE.DirectionalLight(0xffffff, Math.PI);
this.sunLight.position.set(0, 100, 0);
this.sunLight.castShadow = true;
// 보조 광원 (달)
this.moonLight = new THREE.DirectionalLight(0x4444ff, Math.PI * 0.5);
this.moonLight.position.set(0, -100, 0);
// 환경광
this.ambientLight = new THREE.AmbientLight(0x404040, Math.PI * 0.2);
this.scene.add(this.sunLight);
this.scene.add(this.moonLight);
this.scene.add(this.ambientLight);
}
update(delta) {
// 시간 업데이트 (24시간 주기)
this.dayTime = (this.dayTime + delta * 0.1) % 24;
// 태양 위치 계산
const sunAngle = (this.dayTime / 24) * Math.PI * 2;
this.sunLight.position.x = Math.cos(sunAngle) * 100;
this.sunLight.position.y = Math.sin(sunAngle) * 100;
// 조명 강도 조절
const dayIntensity = Math.max(0, Math.sin(sunAngle));
const nightIntensity = Math.max(0, -Math.sin(sunAngle));
this.sunLight.intensity = Math.PI * dayIntensity;
this.moonLight.intensity = Math.PI * 0.5 * nightIntensity;
// 환경광 색상 조정
const skyColor = new THREE.Color();
skyColor.setHSL(0.6, 1, dayIntensity * 0.5);
this.ambientLight.color = skyColor;
}
}
실무에서 가장 중요한 것 중 하나가 성능 최적화입니다.
특히 그림자 처리는 성능에 큰 영향을 미칩니다
class ShadowOptimizer {
constructor(renderer, scene, camera) {
this.renderer = renderer;
this.scene = scene;
this.camera = camera;
this.setupShadowSettings();
}
setupShadowSettings() {
// 렌더러 그림자 설정
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 성능 최적화를 위한 그림자 맵 크기 조정
const shadowMapSize = this.calculateOptimalShadowMapSize();
// 씬 내 모든 조명의 그림자 설정 최적화
this.scene.traverse((object) => {
if (object instanceof THREE.Light) {
if (object.shadow) {
object.shadow.mapSize.width = shadowMapSize;
object.shadow.mapSize.height = shadowMapSize;
object.shadow.camera.near = 0.5;
object.shadow.camera.far = 500;
object.shadow.bias = -0.0001;
}
}
});
}
calculateOptimalShadowMapSize() {
// GPU 성능에 따른 최적 그림자 맵 크기 계산
const gpu = this.renderer.capabilities;
const maxTextureSize = gpu.maxTextureSize;
if (maxTextureSize >= 4096) return 2048;
if (maxTextureSize >= 2048) return 1024;
return 512;
}
updateShadowCameras() {
this.scene.traverse((object) => {
if (object instanceof THREE.DirectionalLight && object.shadow) {
const frustumSize = 50;
object.shadow.camera.left = -frustumSize;
object.shadow.camera.right = frustumSize;
object.shadow.camera.top = frustumSize;
object.shadow.camera.bottom = -frustumSize;
object.shadow.camera.updateProjectionMatrix();
}
});
}
}
여러 개의 조명을 사용할 때의 성능 최적화 방법입니다.
class LightingPerformanceManager {
constructor(scene) {
this.scene = scene;
this.lights = new Map();
this.maxActiveLights = 4; // 동시에 활성화할 수 있는 최대 조명 수
this.distanceThreshold = 50; // 조명이 활성화되는 거리
}
addLight(id, light) {
this.lights.set(id, {
light: light,
active: false,
priority: 1
});
}
setPriority(id, priority) {
const lightData = this.lights.get(id);
if (lightData) {
lightData.priority = priority;
}
}
update(cameraPosition) {
// 카메라와의 거리에 따라 조명 활성화/비활성화
const lightDistances = Array.from(this.lights.entries()).map(([id, data]) => ({
id,
distance: data.light.position.distanceTo(cameraPosition),
priority: data.priority
}));
// 거리와 우선순위에 따라 정렬
lightDistances.sort((a, b) => {
if (a.priority !== b.priority) {
return b.priority - a.priority;
}
return a.distance - b.distance;
});
// 조명 활성화/비활성화 처리
let activeCount = 0;
lightDistances.forEach(({ id, distance }) => {
const lightData = this.lights.get(id);
const shouldBeActive = activeCount < this.maxActiveLights &&
distance < this.distanceThreshold;
if (shouldBeActive !== lightData.active) {
lightData.light.visible = shouldBeActive;
lightData.active = shouldBeActive;
}
if (shouldBeActive) activeCount++;
});
}
// 조명 강도 자동 조정
adjustIntensities() {
const activeLights = Array.from(this.lights.values())
.filter(data => data.active);
const totalIntensity = activeLights.reduce((sum, data) =>
sum + data.light.intensity, 0);
if (totalIntensity > Math.PI * 2) {
const factor = (Math.PI * 2) / totalIntensity;
activeLights.forEach(data => {
data.light.intensity *= factor;
});
}
}
}
동적 그림자와 조명 연산을 최적화하는 레이캐스팅 시스템입니다.
class LightingRaycastSystem {
constructor(scene) {
this.scene = scene;
this.raycaster = new THREE.Raycaster();
this.lightObjects = [];
this.shadowCasters = [];
this.maxRayDistance = 100;
this.rayCount = 32;
}
addLightSource(light, intensity = 1) {
this.lightObjects.push({
light: light,
intensity: intensity,
rays: this.generateRays(this.rayCount)
});
}
addShadowCaster(object) {
this.shadowCasters.push(object);
}
generateRays(count) {
const rays = [];
for (let i = 0; i < count; i++) {
const theta = Math.PI * 2 * i / count;
const phi = Math.acos(2 * Math.random() - 1);
rays.push(new THREE.Vector3(
Math.sin(phi) * Math.cos(theta),
Math.sin(phi) * Math.sin(theta),
Math.cos(phi)
));
}
return rays;
}
update() {
this.lightObjects.forEach(lightObj => {
const { light, rays, intensity } = lightObj;
let hitCount = 0;
rays.forEach(ray => {
const direction = ray.clone();
this.raycaster.set(light.position, direction);
const intersects = this.raycaster.intersectObjects(this.shadowCasters);
if (intersects.length > 0) {
hitCount++;
}
});
// 레이캐스트 결과에 따른 조명 강도 조정
const occlusionFactor = 1 - (hitCount / this.rayCount);
light.intensity = intensity * Math.PI * occlusionFactor;
});
}
}
위의 시스템들을 통합하여 사용하는 방법입니다.
// 메인 애플리케이션 클래스
class LightingApplication {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
// 시스템 초기화
this.dynamicLighting = new DynamicLightingSystem(this.scene);
this.shadowOptimizer = new ShadowOptimizer(this.renderer, this.scene, this.camera);
this.performanceManager = new LightingPerformanceManager(this.scene);
this.raycastSystem = new LightingRaycastSystem(this.scene);
this.setupScene();
}
setupScene() {
// 기본 씬 설정
this.renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(this.renderer.domElement);
// 카메라 위치 설정
this.camera.position.set(0, 10, 20);
// 성능 모니터링 설정
this.stats = new Stats();
document.body.appendChild(this.stats.dom);
// 이벤트 리스너 추가
window.addEventListener('resize', this.onWindowResize.bind(this));
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
update(deltaTime) {
// 각 시스템 업데이트
this.dynamicLighting.update(deltaTime);
this.performanceManager.update(this.camera.position);
this.raycastSystem.update();
// 그림자 최적화
this.shadowOptimizer.updateShadowCameras();
// 렌더링
this.renderer.render(this.scene, this.camera);
this.stats.update();
}
start() {
const animate = (time) => {
requestAnimationFrame(animate);
this.update(time);
};
animate();
}
}
// 애플리케이션 시작
const app = new LightingApplication();
app.start();
이러한 최적화를 적용했을 때의 성능 향상 결과를 측정해보면
이러한 최적화는 특히 모바일 디바이스나 저사양 PC에서 큰 성능 향상을 가져올 수 있습니다.