[Tree.js] 게임과 3D 웹 개발을 위한고급 카메라 시스템 구현 하기

궁금하면 500원·2024년 12월 14일

1. 카메라 전환 시스템 구현

게임이나 3D 애플리케이션에서 상황에 따라 다양한 카메라 앵글을 자연스럽게 전환하는 것은 매우 중요합니다.

여러 시점을 부드럽게 전환하는 시스템을 구현해보겠습니다.

class CameraManager {
    private cameras: Map<string, THREE.Camera>;
    private currentCamera: THREE.Camera;
    private transitionDuration: number = 1.0;
    private isTransitioning: boolean = false;
    private startPosition: THREE.Vector3 = new THREE.Vector3();
    private startRotation: THREE.Quaternion = new THREE.Quaternion();
    private targetPosition: THREE.Vector3 = new THREE.Vector3();
    private targetRotation: THREE.Quaternion = new THREE.Quaternion();
    private transitionStartTime: number = 0;

    constructor() {
        this.cameras = new Map();
    }

    addCamera(name: string, camera: THREE.Camera) {
        this.cameras.set(name, camera);
        if (!this.currentCamera) {
            this.currentCamera = camera;
        }
    }

    async transitionTo(cameraName: string, duration?: number) {
        const targetCamera = this.cameras.get(cameraName);
        if (!targetCamera || this.isTransitioning) return;

        this.transitionDuration = duration || 1.0;
        this.isTransitioning = true;
        this.transitionStartTime = performance.now();

        // 현재 카메라 상태 저장
        this.startPosition.copy(this.currentCamera.position);
        this.startRotation.copy(this.currentCamera.quaternion);

        // 목표 카메라 상태 저장
        this.targetPosition.copy(targetCamera.position);
        this.targetRotation.copy(targetCamera.quaternion);
    }

    update() {
        if (!this.isTransitioning) return;

        const elapsed = (performance.now() - this.transitionStartTime) / 1000;
        const progress = Math.min(elapsed / this.transitionDuration, 1.0);

        // easeInOutCubic 이징 함수 적용
        const t = progress < 0.5
            ? 4 * progress * progress * progress
            : 1 - Math.pow(-2 * progress + 2, 3) / 2;

        // 위치와 회전 보간
        this.currentCamera.position.lerpVectors(
            this.startPosition,
            this.targetPosition,
            t
        );

        this.currentCamera.quaternion.slerpQuaternions(
            this.startRotation,
            this.targetRotation,
            t
        );

        if (progress >= 1.0) {
            this.isTransitioning = false;
        }
    }
}

2. 카메라 충돌 처리 시스템

카메라가 벽이나 장애물을 통과하지 않도록 하는 충돌 처리 시스템을 구현합니다.

class CollisionAwareCamera {
    private raycaster: THREE.Raycaster;
    private originalDistance: number = 5;
    private minDistance: number = 1;
    private obstacles: THREE.Mesh[];

    constructor(camera: THREE.PerspectiveCamera, scene: THREE.Scene) {
        this.raycaster = new THREE.Raycaster();
        this.obstacles = [];
        
        // 씬에서 충돌 대상이 되는 메시들을 필터링
        scene.traverse((object) => {
            if (object instanceof THREE.Mesh && object.userData.collidable) {
                this.obstacles.push(object);
            }
        });
    }

    update(target: THREE.Vector3, camera: THREE.PerspectiveCamera) {
        const directionToCamera = camera.position.clone()
            .sub(target).normalize();

        this.raycaster.set(target, directionToCamera);
        const intersects = this.raycaster.intersectObjects(this.obstacles);

        if (intersects.length > 0) {
            const collision = intersects[0];
            const distanceToCollision = collision.distance;

            // 충돌 지점보다 약간 앞에 카메라 위치 조정
            if (distanceToCollision < this.originalDistance) {
                const newDistance = Math.max(
                    this.minDistance,
                    distanceToCollision - 0.5
                );
                
                camera.position.copy(target)
                    .add(directionToCamera.multiplyScalar(newDistance));
            }
        } else {
            // 충돌이 없으면 원래 거리로 복귀
            const currentDistance = camera.position.distanceTo(target);
            if (currentDistance < this.originalDistance) {
                const newDistance = THREE.MathUtils.lerp(
                    currentDistance,
                    this.originalDistance,
                    0.1
                );
                camera.position.copy(target)
                    .add(directionToCamera.multiplyScalar(newDistance));
            }
        }
    }
}

3. 카메라 쉐이크 효과

게임에서 충돌이나 폭발 등의 이벤트 발생 시 카메라에 흔들림 효과를 주는 시스템을 구현합니다.

class CameraShake {
    private camera: THREE.Camera;
    private decay: number;
    private intensity: number;
    private isShaking: boolean = false;
    private originalPosition: THREE.Vector3 = new THREE.Vector3();
    private originalRotation: THREE.Euler = new THREE.Euler();

    constructor(camera: THREE.Camera) {
        this.camera = camera;
        this.decay = 0.9;
        this.intensity = 0.0;
    }

    shake(intensity: number, duration: number = 0.5) {
        this.intensity = intensity;
        this.isShaking = true;
        this.originalPosition.copy(this.camera.position);
        this.originalRotation.copy(this.camera.rotation);

        // duration 후에 자동으로 쉐이크 종료
        setTimeout(() => {
            this.isShaking = false;
            // 원래 위치로 부드럽게 복귀
            this.camera.position.lerp(this.originalPosition, 0.1);
            this.camera.rotation.set(
                this.originalRotation.x,
                this.originalRotation.y,
                this.originalRotation.z
            );
        }, duration * 1000);
    }

    update(deltaTime: number) {
        if (!this.isShaking) return;

        // 랜덤한 오프셋 생성
        const offsetX = (Math.random() - 0.5) * this.intensity;
        const offsetY = (Math.random() - 0.5) * this.intensity;
        const offsetZ = (Math.random() - 0.5) * this.intensity;

        // 카메라에 오프셋 적용
        this.camera.position.set(
            this.originalPosition.x + offsetX,
            this.originalPosition.y + offsetY,
            this.originalPosition.z + offsetZ
        );

        // 강도 감소
        this.intensity *= this.decay;
    }
}

4. 시야 제한 및 FOV 동적 조정

상황에 따라 카메라의 시야각(FOV)을 동적으로 조정하고, 회전 각도를 제한하는 시스템을 구현합니다.

class AdvancedCameraControl {
    private camera: THREE.PerspectiveCamera;
    private minFOV: number = 45;
    private maxFOV: number = 90;
    private defaultFOV: number = 75;
    private currentFOV: number = 75;
    private targetFOV: number = 75;
    private rotationLimits: {
        minX: number;
        maxX: number;
        minY: number;
        maxY: number;
    };

    constructor(camera: THREE.PerspectiveCamera) {
        this.camera = camera;
        this.rotationLimits = {
            minX: -Math.PI / 3,  // -60도
            maxX: Math.PI / 3,   // 60도
            minY: -Math.PI,      // -180도
            maxY: Math.PI        // 180도
        };
    }

    setFOV(fov: number, immediate: boolean = false) {
        this.targetFOV = THREE.MathUtils.clamp(
            fov,
            this.minFOV,
            this.maxFOV
        );
        
        if (immediate) {
            this.currentFOV = this.targetFOV;
            this.camera.fov = this.currentFOV;
            this.camera.updateProjectionMatrix();
        }
    }

    update(deltaTime: number) {
        // FOV 부드럽게 조정
        if (this.currentFOV !== this.targetFOV) {
            this.currentFOV = THREE.MathUtils.lerp(
                this.currentFOV,
                this.targetFOV,
                deltaTime * 5
            );
            this.camera.fov = this.currentFOV;
            this.camera.updateProjectionMatrix();
        }

        // 회전 각도 제한
        const rotation = this.camera.rotation;
        rotation.x = THREE.MathUtils.clamp(
            rotation.x,
            this.rotationLimits.minX,
            this.rotationLimits.maxX
        );
        rotation.y = THREE.MathUtils.clamp(
            rotation.y,
            this.rotationLimits.minY,
            this.rotationLimits.maxY
        );
    }

    // 달리기와 같은 효과를 위한 FOV 조정
    startSprintEffect() {
        this.setFOV(this.defaultFOV * 1.2);  // 20% 증가
    }

    endSprintEffect() {
        this.setFOV(this.defaultFOV);
    }
}

5. 사용 예시

이러한 고급 카메라 시스템들을 실제로 적용하는 예시입니다.

class GameScene {
    private scene: THREE.Scene;
    private camera: THREE.PerspectiveCamera;
    private cameraManager: CameraManager;
    private collisionSystem: CollisionAwareCamera;
    private cameraShake: CameraShake;
    private cameraControl: AdvancedCameraControl;
    private clock: THREE.Clock;

    constructor() {
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.clock = new THREE.Clock();

        // 카메라 시스템 초기화
        this.cameraManager = new CameraManager();
        this.collisionSystem = new CollisionAwareCamera(this.camera, this.scene);
        this.cameraShake = new CameraShake(this.camera);
        this.cameraControl = new AdvancedCameraControl(this.camera);

        // 여러 카메라 뷰포인트 설정
        this.setupCameras();
    }

    private setupCameras() {
        // 메인 카메라
        this.cameraManager.addCamera('main', this.camera);

        // 탑뷰 카메라
        const topCamera = this.camera.clone();
        topCamera.position.set(0, 10, 0);
        topCamera.lookAt(0, 0, 0);
        this.cameraManager.addCamera('top', topCamera);

        // 사이드뷰 카메라
        const sideCamera = this.camera.clone();
        sideCamera.position.set(10, 2, 0);
        sideCamera.lookAt(0, 0, 0);
        this.cameraManager.addCamera('side', sideCamera);
    }

    update() {
        const deltaTime = this.clock.getDelta();

        // 모든 카메라 시스템 업데이트
        this.cameraManager.update();
        this.collisionSystem.update(new THREE.Vector3(0, 0, 0), this.camera);
        this.cameraShake.update(deltaTime);
        this.cameraControl.update(deltaTime);
    }

    // 이벤트 핸들러 예시
    onExplosion(position: THREE.Vector3) {
        // 폭발 위치와 카메라의 거리에 따라 쉐이크 강도 조절
        const distance = this.camera.position.distanceTo(position);
        const intensity = Math.max(0, 1 - (distance / 10)) * 0.5;
        this.cameraShake.shake(intensity);
    }

    onPlayerSprint(isSprinting: boolean) {
        if (isSprinting) {
            this.cameraControl.startSprintEffect();
        } else {
            this.cameraControl.endSprintEffect();
        }
    }

    // 카메라 전환 예시
    switchToTopView() {
        this.cameraManager.transitionTo('top', 1.5);
    }
}

성능 최적화 및 고려사항

1. 메모리 관리

  • Vector3, Quaternion 객체 재사용
  • 불필요한 객체 생성 최소화
  • RAF(requestAnimationFrame) 최적화

2. 충돌 검사 최적화

  • 공간 분할(Spatial Partitioning) 구현
  • 광선 투사 횟수 최소화
  • 충돌 대상 필터링

3. 전환 효과 최적화

  • 보간 계산 최적화
  • 불필요한 행렬 연산 제거
  • 캐싱 활용

이러한 고급 카메라 시스템은 게임이나 3D 애플리케이션에서 사용자 경험을 크게 향상시킬 수 있습니다.

특히 성능 최적화와 에러 처리는 실제 프로덕션 환경에서 안정적인 사용자 경험을 제공하는 데 큰 도움이 됩니다.

4. 렌더링 최적화

class RenderOptimizer {
    private lastCameraPosition: THREE.Vector3;
    private lastCameraRotation: THREE.Euler;
    private renderThreshold: number = 0.001;

    constructor() {
        this.lastCameraPosition = new THREE.Vector3();
        this.lastCameraRotation = new THREE.Euler();
    }

    shouldRender(camera: THREE.Camera): boolean {
        const positionChanged = this.lastCameraPosition
            .distanceTo(camera.position) > this.renderThreshold;
        
        const rotationChanged = 
            Math.abs(this.lastCameraRotation.x - camera.rotation.x) > this.renderThreshold ||
            Math.abs(this.lastCameraRotation.y - camera.rotation.y) > this.renderThreshold ||
            Math.abs(this.lastCameraRotation.z - camera.rotation.z) > this.renderThreshold;

        if (positionChanged || rotationChanged) {
            this.lastCameraPosition.copy(camera.position);
            this.lastCameraRotation.copy(camera.rotation);
            return true;
        }

        return false;
    }
}

5. 디버그 시스템 구현

class CameraDebugger {
    private debugElement: HTMLElement;
    private camera: THREE.Camera;
    private stats: {
        position: THREE.Vector3;
        rotation: THREE.Euler;
        fov: number;
        renderCalls: number;
    };

    constructor(camera: THREE.Camera) {
        this.camera = camera;
        this.stats = {
            position: new THREE.Vector3(),
            rotation: new THREE.Euler(),
            fov: 0,
            renderCalls: 0
        };

        this.setupDebugUI();
    }

    private setupDebugUI() {
        this.debugElement = document.createElement('div');
        this.debugElement.style.position = 'fixed';
        this.debugElement.style.top = '10px';
        this.debugElement.style.left = '10px';
        this.debugElement.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
        this.debugElement.style.color = 'white';
        this.debugElement.style.padding = '10px';
        this.debugElement.style.fontFamily = 'monospace';
        document.body.appendChild(this.debugElement);
    }

    update() {
        this.stats.position.copy(this.camera.position);
        this.stats.rotation.copy(this.camera.rotation);
        this.stats.fov = (this.camera as THREE.PerspectiveCamera).fov;
        
        this.debugElement.innerHTML = `
            Camera Debug Info:
            Position: ${this.formatVector3(this.stats.position)}
            Rotation: ${this.formatEuler(this.stats.rotation)}
            FOV: ${this.stats.fov.toFixed(2)}°
            Render Calls: ${this.stats.renderCalls}
        `;
    }

    private formatVector3(v: THREE.Vector3): string {
        return `x: ${v.x.toFixed(2)}, y: ${v.y.toFixed(2)}, z: ${v.z.toFixed(2)}`;
    }

    private formatEuler(e: THREE.Euler): string {
        return `x: ${THREE.MathUtils.radToDeg(e.x).toFixed(2)}°, 
                y: ${THREE.MathUtils.radToDeg(e.y).toFixed(2)}°, 
                z: ${THREE.MathUtils.radToDeg(e.z).toFixed(2)}°`;
    }
}

실제 활용 사례

1. 3인칭 액션 게임 카메라

class ThirdPersonCamera {
    private camera: THREE.PerspectiveCamera;
    private target: THREE.Object3D;
    private offset: THREE.Vector3;
    private smoothSpeed: number = 0.1;
    private collisionSystem: CollisionAwareCamera;

    constructor(
        camera: THREE.PerspectiveCamera, 
        target: THREE.Object3D, 
        scene: THREE.Scene
    ) {
        this.camera = camera;
        this.target = target;
        this.offset = new THREE.Vector3(0, 2, -5);
        this.collisionSystem = new CollisionAwareCamera(camera, scene);
    }

    update(deltaTime: number) {
        // 타겟의 월드 매트릭스를 고려한 목표 위치 계산
        const targetPosition = new THREE.Vector3();
        this.target.getWorldPosition(targetPosition);
        
        // 오프셋을 타겟의 회전에 맞춰 변환
        const offsetRotated = this.offset.clone()
            .applyQuaternion(this.target.quaternion);
        
        // 목표 카메라 위치 계산
        const desiredPosition = targetPosition.clone().add(offsetRotated);
        
        // 부드러운 카메라 이동
        this.camera.position.lerp(desiredPosition, this.smoothSpeed);
        
        // 충돌 처리
        this.collisionSystem.update(targetPosition, this.camera);
        
        // 카메라가 항상 타겟을 바라보도록 설정
        this.camera.lookAt(targetPosition);
    }

    // 숄더뷰 전환
    switchToShoulderView(rightShoulder: boolean = true) {
        const shoulderOffset = rightShoulder 
            ? new THREE.Vector3(1, 2, -3)
            : new THREE.Vector3(-1, 2, -3);
        
        this.offset.lerp(shoulderOffset, 0.1);
    }

    // 줌 기능
    zoom(delta: number) {
        const newOffset = this.offset.clone();
        newOffset.z += delta;
        
        // 줌 한계 설정
        if (newOffset.z >= -10 && newOffset.z <= -2) {
            this.offset.copy(newOffset);
        }
    }
}

2. 시네마틱 카메라 시스템

class CinematicCamera {
    private camera: THREE.PerspectiveCamera;
    private waypoints: THREE.Vector3[];
    private lookAtPoints: THREE.Vector3[];
    private currentWaypoint: number = 0;
    private transitionSpeed: number = 0.02;
    private isPlaying: boolean = false;

    constructor(camera: THREE.PerspectiveCamera) {
        this.camera = camera;
        this.waypoints = [];
        this.lookAtPoints = [];
    }

    addKeyframe(
        position: THREE.Vector3, 
        lookAt: THREE.Vector3
    ) {
        this.waypoints.push(position.clone());
        this.lookAtPoints.push(lookAt.clone());
    }

    play() {
        this.isPlaying = true;
        this.currentWaypoint = 0;
    }

    update() {
        if (!this.isPlaying || this.waypoints.length < 2) return;

        const currentPos = this.waypoints[this.currentWaypoint];
        const currentLookAt = this.lookAtPoints[this.currentWaypoint];
        const nextPos = this.waypoints[(this.currentWaypoint + 1) % this.waypoints.length];
        const nextLookAt = this.lookAtPoints[(this.currentWaypoint + 1) % this.lookAtPoints.length];

        // 위치 보간
        this.camera.position.lerp(nextPos, this.transitionSpeed);
        
        // 시선 보간
        const currentLookAtPos = new THREE.Vector3();
        currentLookAtPos.lerp(nextLookAt, this.transitionSpeed);
        this.camera.lookAt(currentLookAtPos);

        // 다음 웨이포인트로 전환 체크
        if (this.camera.position.distanceTo(nextPos) < 0.1) {
            this.currentWaypoint = 
                (this.currentWaypoint + 1) % this.waypoints.length;
        }
    }
}

에러 처리 및 예외상황 관리

class CameraErrorHandler {
    private static readonly ERROR_TYPES = {
        COLLISION: 'COLLISION_ERROR',
        TRANSITION: 'TRANSITION_ERROR',
        RENDER: 'RENDER_ERROR'
    };

    private static handleError(error: Error, type: string) {
        console.error(`Camera System Error (${type}):`, error);

        switch (type) {
            case this.ERROR_TYPES.COLLISION:
                // 충돌 처리 실패 시 안전한 위치로 리셋
                this.resetToSafePosition();
                break;
            case this.ERROR_TYPES.TRANSITION:
                // 전환 실패 시 기본 카메라 설정으로 복구
                this.restoreDefaultCamera();
                break;
            case this.ERROR_TYPES.RENDER:
                // 렌더링 에러 시 최소 설정으로 전환
                this.switchToLowPerformanceMode();
                break;
        }
    }

    private static resetToSafePosition() {
        // 안전한 초기 위치로 카메라 리셋 로직
    }

    private static restoreDefaultCamera() {
        // 기본 카메라 설정 복구 로직
    }

    private static switchToLowPerformanceMode() {
        // 저성능 모드 전환 로직
    }
}

테스트 및 품질 관리

class CameraTestSuite {
    private camera: THREE.PerspectiveCamera;
    private tests: Map<string, () => boolean>;

    constructor(camera: THREE.PerspectiveCamera) {
        this.camera = camera;
        this.tests = new Map();
        this.setupTests();
    }

    private setupTests() {
        this.tests.set('position_bounds', () => {
            return this.camera.position.length() < 1000;
        });

        this.tests.set('fov_range', () => {
            return this.camera.fov >= 45 && this.camera.fov <= 90;
        });

        this.tests.set('rotation_limits', () => {
            return Math.abs(this.camera.rotation.x) <= Math.PI / 2;
        });
    }

    runTests(): TestReport {
        const results = new Map<string, boolean>();
        this.tests.forEach((test, name) => {
            try {
                results.set(name, test());
            } catch (error) {
                results.set(name, false);
                console.error(`Test failed: ${name}`, error);
            }
        });
        return this.generateReport(results);
    }

    private generateReport(results: Map<string, boolean>): TestReport {
        // 테스트 결과 리포트 생성
        return {
            totalTests: results.size,
            passed: Array.from(results.values()).filter(r => r).length,
            failed: Array.from(results.values()).filter(r => !r).length,
            details: Object.fromEntries(results)
        };
    }
}

interface TestReport {
    totalTests: number;
    passed: number;
    failed: number;
    details: { [key: string]: boolean };
}

고급 기능 구현

1. 카메라 패스 시스템

복잡한 카메라 움직임을 미리 정의하고 재생할 수 있는 시스템입니다.

class CameraPath {
    private curve: THREE.CatmullRomCurve3;
    private points: THREE.Vector3[];
    private duration: number;
    private startTime: number | null = null;
    private isPlaying: boolean = false;

    constructor(points: THREE.Vector3[], duration: number = 5) {
        this.points = points;
        this.duration = duration;
        this.curve = new THREE.CatmullRomCurve3(points, true);
    }

    start() {
        this.startTime = performance.now();
        this.isPlaying = true;
    }

    update(camera: THREE.PerspectiveCamera) {
        if (!this.isPlaying || !this.startTime) return;

        const elapsedTime = (performance.now() - this.startTime) / 1000;
        const progress = (elapsedTime % this.duration) / this.duration;

        // 경로상의 현재 위치 계산
        const currentPoint = this.curve.getPoint(progress);
        camera.position.copy(currentPoint);

        // 다음 포인트를 바라보도록 설정
        const lookAtPoint = this.curve.getPoint((progress + 0.1) % 1);
        camera.lookAt(lookAtPoint);

        // 경로 완료 체크
        if (elapsedTime >= this.duration) {
            this.isPlaying = false;
            this.startTime = null;
        }
    }

    // 디버그용 시각화
    createDebugLine(): THREE.Line {
        const points = this.curve.getPoints(50);
        const geometry = new THREE.BufferGeometry().setFromPoints(points);
        const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
        return new THREE.Line(geometry, material);
    }
}

2. 고급 포커스 시스템

피사계 심도(Depth of Field) 효과를 구현하는 시스템입니다.

class AdvancedFocus {
    private camera: THREE.PerspectiveCamera;
    private focusTarget: THREE.Object3D | null = null;
    private bokehPass: any; // THREE.BokehPass
    private composer: any; // THREE.EffectComposer

    constructor(
        camera: THREE.PerspectiveCamera,
        renderer: THREE.WebGLRenderer,
        scene: THREE.Scene
    ) {
        this.camera = camera;
        this.setupPostProcessing(renderer, scene);
    }

    private setupPostProcessing(
        renderer: THREE.WebGLRenderer,
        scene: THREE.Scene
    ) {
        const composer = new THREE.EffectComposer(renderer);
        const renderPass = new THREE.RenderPass(scene, this.camera);
        composer.addPass(renderPass);

        const bokehPass = new THREE.BokehPass(scene, this.camera, {
            focus: 1.0,
            aperture: 0.025,
            maxblur: 1.0
        });
        composer.addPass(bokehPass);

        this.composer = composer;
        this.bokehPass = bokehPass;
    }

    setFocusTarget(target: THREE.Object3D) {
        this.focusTarget = target;
    }

    update() {
        if (this.focusTarget) {
            const distance = this.camera.position.distanceTo(
                this.focusTarget.position
            );
            this.bokehPass.uniforms.focus.value = distance;
        }
    }

    // 피사계 심도 효과 강도 조절
    setAperture(value: number) {
        this.bokehPass.uniforms.aperture.value = THREE.MathUtils.clamp(
            value,
            0,
            0.05
        );
    }
}

3. 카메라 모션 시스템

부드러운 카메라 움직임을 위한 스프링 기반 모션 시스템입니다.

class CameraMotionSystem {
    private position: THREE.Vector3;
    private velocity: THREE.Vector3;
    private target: THREE.Vector3;
    private stiffness: number;
    private damping: number;
    private mass: number;

    constructor(initialPosition: THREE.Vector3) {
        this.position = initialPosition.clone();
        this.velocity = new THREE.Vector3();
        this.target = initialPosition.clone();
        this.stiffness = 100;
        this.damping = 10;
        this.mass = 1;
    }

    setTarget(target: THREE.Vector3) {
        this.target.copy(target);
    }

    update(deltaTime: number) {
        // 스프링 힘 계산
        const force = new THREE.Vector3()
            .subVectors(this.target, this.position)
            .multiplyScalar(this.stiffness);

        // 감쇠력 계산
        const dampingForce = this.velocity
            .clone()
            .multiplyScalar(-this.damping);

        // 전체 힘
        force.add(dampingForce);

        // 가속도
        const acceleration = force.multiplyScalar(1 / this.mass);

        // 속도 업데이트
        this.velocity.add(acceleration.multiplyScalar(deltaTime));

        // 위치 업데이트
        this.position.add(this.velocity.multiplyScalar(deltaTime));

        return this.position.clone();
    }
}

4. 실시간 퍼포먼스 모니터링

class CameraPerformanceMonitor {
    private metrics: {
        updateTime: number[];
        renderCalls: number[];
        memoryUsage: number[];
    };
    private maxSamples: number = 60;

    constructor() {
        this.metrics = {
            updateTime: [],
            renderCalls: [],
            memoryUsage: []
        };
    }

    addMetric(
        updateTime: number,
        renderCalls: number,
        memoryUsage: number
    ) {
        this.metrics.updateTime.push(updateTime);
        this.metrics.renderCalls.push(renderCalls);
        this.metrics.memoryUsage.push(memoryUsage);

        // 샘플 수 제한
        if (this.metrics.updateTime.length > this.maxSamples) {
            this.metrics.updateTime.shift();
            this.metrics.renderCalls.shift();
            this.metrics.memoryUsage.shift();
        }
    }

    getAverages() {
        return {
            updateTime: this.calculateAverage(this.metrics.updateTime),
            renderCalls: this.calculateAverage(this.metrics.renderCalls),
            memoryUsage: this.calculateAverage(this.metrics.memoryUsage)
        };
    }

    private calculateAverage(array: number[]): number {
        return array.reduce((a, b) => a + b, 0) / array.length;
    }

    generateReport(): string {
        const averages = this.getAverages();
        return `
카메라 시스템 성능 리포트:
- 평균 업데이트 시간: ${averages.updateTime.toFixed(2)}ms
- 평균 렌더 콜: ${averages.renderCalls.toFixed(2)}
- 평균 메모리 사용량: ${(averages.memoryUsage / 1024 / 1024).toFixed(2)}MB
        `;
    }
}

5. 실제 사용 예제

모든 시스템을 통합하여 사용하는 예제입니다.

class AdvancedCameraSystem {
    private camera: THREE.PerspectiveCamera;
    private motionSystem: CameraMotionSystem;
    private focusSystem: AdvancedFocus;
    private pathSystem: CameraPath;
    private performanceMonitor: CameraPerformanceMonitor;
    private debugger: CameraDebugger;

    constructor(
        camera: THREE.PerspectiveCamera,
        renderer: THREE.WebGLRenderer,
        scene: THREE.Scene
    ) {
        this.camera = camera;
        this.motionSystem = new CameraMotionSystem(camera.position);
        this.focusSystem = new AdvancedFocus(camera, renderer, scene);
        this.performanceMonitor = new CameraPerformanceMonitor();
        this.debugger = new CameraDebugger(camera);

        // 카메라 패스 초기화
        const pathPoints = [
            new THREE.Vector3(0, 2, 5),
            new THREE.Vector3(5, 2, 0),
            new THREE.Vector3(0, 2, -5),
            new THREE.Vector3(-5, 2, 0)
        ];
        this.pathSystem = new CameraPath(pathPoints, 10);
    }

    update(deltaTime: number) {
        const startTime = performance.now();

        // 모션 시스템 업데이트
        const newPosition = this.motionSystem.update(deltaTime);
        this.camera.position.copy(newPosition);

        // 포커스 시스템 업데이트
        this.focusSystem.update();

        // 패스 시스템 업데이트
        this.pathSystem.update(this.camera);

        // 퍼포먼스 모니터링
        const updateTime = performance.now() - startTime;
        this.performanceMonitor.addMetric(
            updateTime,
            renderer.info.render.calls,
            performance.memory?.usedJSHeapSize || 0
        );

        // 디버그 정보 업데이트
        this.debugger.update();
    }

    // 카메라 설정 저장
    saveState(): CameraState {
        return {
            position: this.camera.position.clone(),
            rotation: this.camera.rotation.clone(),
            fov: this.camera.fov,
            target: this.motionSystem.getTarget().clone()
        };
    }

    // 카메라 설정 복원
    restoreState(state: CameraState) {
        this.camera.position.copy(state.position);
        this.camera.rotation.copy(state.rotation);
        this.camera.fov = state.fov;
        this.motionSystem.setTarget(state.target);
        this.camera.updateProjectionMatrix();
    }
}

interface CameraState {
    position: THREE.Vector3;
    rotation: THREE.Euler;
    fov: number;
    target: THREE.Vector3;
}

이 시스템들을 활용하면 전문적인 수준의 카메라 워크를 구현할 수 있습니다.

실제 게임이나 3D 애플리케이션에서 사용할 때는 프로젝트의 요구사항에 맞게 커스터마이징하여 사용해보면 좋을것같다는 생각이 든다.

profile
에러가 나도 괜찮아 — 그건 내가 배우고 있다는 증거야.

0개의 댓글