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

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

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개의 댓글