React Three Fiber 기술 분석(2/5)
Three.js

React Three Fiber 기술 분석 - Explode View 구현

3D 모델의 각 파츠를 중심에서 바깥으로 펼쳐서 내부 구조를 보여주는 분해 뷰 기능을 구현합니다.

2025-02-05
5 min read
#React Three Fiber#Three.js#애니메이션#3D

개요

3D 모델의 각 파츠를 중심에서 바깥으로 펼쳐서 내부 구조를 보여주는 기능입니다.


구현 원리

핵심 아이디어: 각 메쉬를 모델 중심에서 바깥 방향으로 이동

[조립 상태]          [분해 상태]
    ┌─┐                  ┌─┐
  ┌─┼─┼─┐              ↗ │ │ ↖
  │ │ │ │    ────→   ┌─┐ └─┘ ┌─┐
  └─┼─┼─┘            │ │     │ │
    └─┘              └─┘     └─┘

단계별 구현

1단계: 방향 벡터 계산

각 메쉬가 어느 방향으로 이동해야 하는지 계산합니다.

// 모델 로드 시 한 번만 실행
const calculateExplodeDirections = (meshes: THREE.Mesh[]) => {
  // 전체 모델의 중심점 계산
  const boundingBox = new THREE.Box3();
  meshes.forEach(mesh => boundingBox.expandByObject(mesh));
  const center = boundingBox.getCenter(new THREE.Vector3());

  return meshes.map(mesh => {
    const meshCenter = new THREE.Vector3();
    new THREE.Box3().setFromObject(mesh).getCenter(meshCenter);

    // 중심 → 메쉬 방향 = 폭발 방향
    const direction = meshCenter.clone().sub(center).normalize();

    return {
      mesh,
      originalPosition: mesh.position.clone(),
      direction,
    };
  });
};

2단계: useFrame으로 위치 업데이트

R3F의 useFrame은 매 프레임마다 실행됩니다. 여기서 위치를 업데이트합니다.

useFrame(() => {
  const { explodeLevel } = useModelStore.getState();

  parts.forEach(({ mesh, originalPosition, direction }) => {
    // 목표 위치 = 원래 위치 + (방향 × 분해 정도 × 거리)
    const targetPosition = originalPosition.clone().add(
      direction.clone().multiplyScalar(explodeLevel * EXPLODE_DISTANCE)
    );

    // 부드러운 보간
    mesh.position.lerp(targetPosition, 0.1);
  });
});

lerp를 사용하는 이유

Vector3.lerp(target, alpha)는 현재 위치에서 목표까지 alpha 비율만큼 이동합니다.

alpha = 0.1일 때:
프레임 1: 0 → 0.1 (10% 이동)
프레임 2: 0.1 → 0.19 (남은 거리의 10%)
프레임 3: 0.19 → 0.27
...

처음엔 빠르게, 목표에 가까워질수록 느려지는 자연스러운 easing 효과를 줍니다.

alpha 값에 따른 체감

alpha체감
0.05 이하너무 느림
0.1~0.15자연스러움
0.3 이상기계적인 느낌

대안 비교

Tween 라이브러리 (GSAP, Tween.js)

gsap.to(mesh.position, {
  x: targetX,
  duration: 0.5,
  ease: "power2.out"
});

장점: 정교한 easing, 시퀀스 제어

단점: 슬라이더로 실시간 제어하려면 매번 tween을 취소하고 다시 생성해야 함

결론

슬라이더 실시간 제어 → lerp 방식이 적합

버튼 클릭 애니메이션 → Tween이 적합


실제 구현 코드

// components/TestModel.tsx
const SelectableMesh = ({ part, index }: Props) => {
  const meshRef = useRef<THREE.Mesh>(null);
  const { explodeLevel, meshPositions } = useModelStore();

  const originalPosition = useRef(new THREE.Vector3());
  const explodeDirection = useRef(new THREE.Vector3());

  useEffect(() => {
    if (meshRef.current) {
      originalPosition.current.copy(meshRef.current.position);
      explodeDirection.current
        .copy(originalPosition.current)
        .normalize();
    }
  }, []);

  useFrame(() => {
    if (!meshRef.current) return;

    // 수동 이동 위치가 있으면 그걸 기준으로
    const basePos = meshPositions[part.id]
      ? new THREE.Vector3(
          meshPositions[part.id].x,
          meshPositions[part.id].y,
          meshPositions[part.id].z
        )
      : originalPosition.current.clone();

    // 분해 오프셋 적용
    const targetPosition = basePos.add(
      explodeDirection.current.clone().multiplyScalar(explodeLevel * 2)
    );

    meshRef.current.position.lerp(targetPosition, 0.1);
  });

  return <mesh ref={meshRef} geometry={part.geometry} />;
};

휠 컨트롤 연동

슬라이더 외에 Shift + 마우스 휠로도 분해 정도를 조절할 수 있습니다.

const handleWheel = useCallback((e: WheelEvent) => {
  if (!e.shiftKey) return; // 일반 휠은 줌으로 사용

  e.preventDefault();
  const delta = e.deltaY > 0 ? -0.05 : 0.05;
  const newLevel = Math.max(0, Math.min(1, explodeLevel + delta));
  setExplodeLevel(newLevel);
}, [explodeLevel, setExplodeLevel]);

// passive: false로 preventDefault 허용
container.addEventListener('wheel', handleWheel, { passive: false });

개선 가능한 점

조립 순서 기반 분해

현재는 "중심에서 바깥으로" 단순 방식입니다. 실제 조립 순서를 반영하면 더 직관적입니다.

// 조립 순서가 높은 부품(나중에 조립)이 먼저 분해
const adjustedLevel = Math.max(0, (level - (1 - orderRatio)) * 2);

분해 방향 커스터마이징

특정 부품은 위로, 특정 부품은 옆으로 분해되도록 방향을 지정할 수 있습니다.

// 모델 메타데이터에 분해 방향 포함
const customDirection = part.explodeDirection || calculateDefaultDirection();

시리즈: React Three Fiber 기술 분석

  1. 개요
  2. Explode View 구현 ← 현재 글
  3. 메쉬 선택/이동
  4. 포스트 프로세싱
  5. Zustand 상태 관리