홈으로 돌아가기

브라우저에서 Three.js로 3D 아파트 렌더링

이 기사는 Next.js와 Three.js를 사용한 프로시저럴 가구를 포함한 브라우저 3D 아파트 렌더링 구현을 설명합니다. 장면 아키텍처, 조명, Socket.io 멀티플레이어, 최적화, 게임화 등을 다룹니다. 중/시니어 개발자에게 적합합니다.

브라우저에서 3D 아파트: Three.js + 프로시저럴 가구
Advertisement 728x90

브라우저에서 절차적 3D 아파트 렌더링: Next.js, Three.js, WebSocket 멀티플레이어

프리콜냐 프로젝트에서는 외부 모델 없이 픽셀화된 3D 아파트가 멀티플레이어 아케이드용으로 렌더링됩니다. Next.js 16과 React Three Fiber를 사용하며, 기본 도형에서 절차적 가구 생성, 콘텐츠용 Canvas 텍스처, 위치 동기화를 위한 Socket.io를 활용합니다. 300~800개의 오브젝트가 있는 장면이 최신 기기에서 원활하게 실행됩니다.

장면 아키텍처와 아파트 템플릿

아파트는 공유 벽과 문이 있는 구역(방)으로 구성됩니다. 템플릿은 하드코딩된 로직 없이 데이터로 정의됩니다:

type RoomDef = {
  id: string;
  name: string;
  x: number; z: number;
  width: number; depth: number;
  isSecret?: boolean;
};

type WallDef = {
  x1: number; z1: number;
  x2: number; z2: number;
  hasDoor?: boolean;
  doorOffset?: number;
};

const LOFT: ApartmentTemplate = {
  id: "loft",
  rooms: [
    { id: "lounge", name: "라운지", x: 0, z: 0, width: 12, depth: 8 },
    { id: "gaming", name: "게임방", x: 12, z: 0, width: 10, depth: 8 },
    { id: "bar", name: "바", x: 0, z: 8, width: 10, depth: 6 },
    { id: "studio", name: "스튜디오", x: 10, z: 8, width: 12, depth: 6 },
  ],
  walls: [
    { x1: 0, z1: 0, x2: 22, z2: 0 },
    { x1: 22, z1: 0, x2: 22, z2: 14 },
    { x1: 12, z1: 0, x2: 12, z2: 8, hasDoor: true },
  ],
};

템플릿은 스튜디오(12x10)에서 펜트하우스(24x22)까지 다양합니다. 렌더링은 선언적입니다: 데이터가 Three.js 오브젝트로 변환됩니다.

Google AdInline article slot

조명: 레이어와 분위기 프리셋

장면은 세 개의 레이어로 조명됩니다:

  • 앰비언트 — 기본 레이어, 시간대에 따라 다름(아침: #ffd4a0, 강도 1.1; 밤: #1a2244, 0.4).
  • 방향성 — 그림자가 있는 주 조명 + 채광.
  • 포인트 라이트 — 방당 하나, 높이 2.3 단위.

여덟 개의 분위기 프리셋이 추가됩니다. 파티: 천장 #ff2288 (1.8), 앰비언트 #110022 (0.2), 액센트 #00ffff (3.0). 지오메트리를 변경하지 않고 완전한 변환.

기본 도형에서 절차적 가구 생성

가구는 boxGeometrycylinderGeometry로 조립됩니다. 장점: 다운로드 불필요, 즉시 생성, AI 설명에 간단함.

Google AdInline article slot

아케이드 기계 예시:

function ArcadeModel({ color, accent }: { color: string; accent: string }) {
  return (
    <group>
      <Block args={[0.7, 1.6, 0.7]} position={[0, 0.8, 0]} color={color} />
      <Block args={[0.5, 0.35, 0.05]} position={[0, 1.2, 0.33]}
             color="#0a0a2a" emissive={accent} emissiveIntensity={0.3} />
      <Block args={[0.5, 0.05, 0.25]} position={[0, 0.85, 0.25]}
             color="#1a1a2e" rotation={[-0.3, 0, 0]} />
      <mesh position={[-0.1, 0.92, 0.25]}>
        <cylinderGeometry args={[0.02, 0.02, 0.12]} />
        <meshStandardMaterial color="#ff2e63" />
      </mesh>
      {[0.05, 0.15].map((x) => (
        <mesh key={x} position={[x, 0.9, 0.2]}>
          <cylinderGeometry args={[0.025, 0.025, 0.02]} />
          <meshStandardMaterial color={accent} emissive={accent}
                                emissiveIntensity={0.5} />
        </mesh>
      ))}
    </group>
  );
}

장면: 300~800개의 오브젝트. AI가 브랜드 전시용 기본 도형 JSON을 생성합니다.

CanvasTexture를 통한 벽 콘텐츠

포스터는 동적으로 렌더링됩니다:

Google AdInline article slot
  • Canvas 256x170.
  • 배경 #1a1a2e, 작가 색상의 프레임, 단어 줄바꿈 텍스트.
  • NearestFilter가 있는 THREE.CanvasTexture.
  • emissiveIntensity: 0.05가 있는 meshStandardMaterial.
const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 170;
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = "#1a1a2e";
ctx.strokeStyle = authorColor;
// 단어 줄바꿈, 그리기

const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.NearestFilter;

벽당 3개의 슬롯(0.25, 0.5, 0.75), 높이 1.3 단위. 이미지, 비디오, 오디오 지원.

픽셀 아바타와 펫

아바타는 빌보드의 Canvas 텍스처입니다. 레이어: 몸체(5톤), 눈, 바지(6), 의류(10), 머리(12), 액세서리(10). 스케일 16x24 → 128x192, NearestFilter.

펫은 lerp 0.05(가까움) / 0.15(멈)로 보간됩니다.

보간을 통한 멀티플레이어

Socket.io가 위치를 브로드캐스트합니다(~20 FPS):

useFrame(() => {
  const now = Date.now();
  if (now - lastRef.current.t < 50) return;
  const dx = Math.abs(pos.x - lastRef.current.x);
  const dz = Math.abs(pos.z - lastRef.current.z);
  if (dx < 0.05 && dz < 0.05) return;

  socketRef.current?.volatile.emit("position", { x: pos.x, z: pos.z });
});

보간: position.x += (target.x - position.x) * 0.15 (프레임당 15%).

volatile.emit은 오래된 패킷을 버립니다.

날씨와 효과

BufferGeometry의 파티클 시스템:

  • 비: 600 파티클, lineSegments.
  • 눈: 400 파티클, 드리프트가 있는 points.
  • 별: 200 파티클, Y에서 깜빡임.
useFrame(() => {
  for (let i = 0; i < count; i++) {
    positions[i * 3 + 1] -= velocities[i];
    positions[i * 3] += Math.sin(Date.now() * 0.0008 + i * 0.7) * 0.004;
    if (positions[i * 3 + 1] < -0.5) {
      positions[i * 3 + 1] = 7 + Math.random() * 2;
    }
  }
  posAttr.needsUpdate = true;
});

문과 애니메이션

벽 법선의 내적을 기반으로 81°(π*0.45)까지 열림:

const wallNormal = { x: -(z2 - z1), z: x2 - x1 };
const toPlayer = { x: playerX - doorX, z: playerZ - doorZ };
const dot = wallNormal.x * toPlayer.x + wallNormal.z * toPlayer.z;
const direction = dot > 0 ? 1 : -1;
currentAngle += (targetAngle * direction - currentAngle) * 0.1;

성능 최적화

| 솔루션 | 효과 |

|---------|--------|

| dynamic(() => import("./Apartment3D"), { ssr: false }) | Three.js 지연 로드, ~150 KB gzip |

| antialias: false | -15-20% 채우기율 |

| dpr: Math.min(devicePixelRatio, 2) | HiDPI에서 2-4x 절약 |

| NearestFilter | 삼선형 필터링 없음 |

| castShadow 가구에만 | 그림자 선택적 적용 |

| receiveShadow 바닥에만 | 하나의 수신자 |

3D 번들: ~600 KB 원본, 트리 셰이킹된 Three.js.

게이미피케이션과 계획

  • 경제: 게시물당 코인, 하루 100개 제한.
  • 레벨: 10, 최대 3500 XP.
  • 성장하는 건물: 콘텐츠 기반 1-6층.
  • 이벤트: 연간 8회, x3 XP.
  • 비밀 방: 코드를 통한 10개.

다음: AI 가구, 트로피, 인스턴싱/LOD.

핵심 요약

  • 4-15개의 기본 도형에서 절차적 가구 생성으로 glTF 다운로드 제거.
  • Socket.io와 volatile.emit 및 15% lerp로 부드러운 멀티플레이어 보장.
  • CanvasTexture + NearestFilter로 픽셀 스타일 콘텐츠와 아바타.
  • 최적화로 300-800 오브젝트와 함께 번들을 150 KB gzip으로 축소.
  • 선언적 템플릿으로 코드 없이 5가지 아파트 변형 가능.

— Editorial Team

Advertisement 728x90

다음 읽기