CSS로 구현한 3D DOOM 렌더링

3 hours ago 1
  • CSS만으로 3D DOOM을 렌더링한 실험으로, 모든 벽과 오브젝트를 <div>와 3D 변환(transform) 으로 구성한 프로젝트
  • 게임 로직은 JavaScript가 담당하지만, 렌더링은 전적으로 CSS가 수행되며 브라우저와 현대 CSS의 한계를 탐구
  • 삼각함수, clip-path, @property, SVG 필터, 앵커 포지셔닝 등 최신 CSS 기능을 활용해 벽, 바닥, 조명, 스프라이트, 폭발 효과까지 구현
  • 카메라 개념이 없는 CSS 특성상 플레이어 대신 세계를 이동시키는 방식으로 시점을 처리하고, 모든 이동은 커스텀 속성 업데이트로 제어
  • WebGL 수준의 성능은 아니지만, CSS의 표현력과 계산 능력 확장 가능성을 입증한 사례

CSS로 구현한 3D DOOM 렌더링

  • CSS만으로 DOOM을 렌더링한 실험 프로젝트로, 모든 벽·바닥·오브젝트가 <div>로 구성되고 3D 변환(transform) 으로 배치됨
    • 게임 로직은 JavaScript에서 실행되지만, 렌더링은 전적으로 CSS가 담당
    • 프로젝트의 목적은 브라우저와 현대 CSS의 한계 탐구

고등학교 수학으로 돌아가기

  • 원본 DOOM의 WAD 파일 데이터(vertices, linedefs, sidedefs, sectors)를 추출해 수천 개의 <div>로 정적 장면 구성
  • 각 벽은 시작·끝 좌표와 바닥·천장 높이를 CSS 커스텀 속성으로 전달받음
  • hypot()atan2() CSS 함수로 벽의 길이와 회전 각도를 계산
  • JavaScript는 원시 데이터를 전달하고, CSS가 삼각함수를 계산해 렌더링 수행
  • 게임 루프와 렌더러는 분리되어 JS는 상태 관리와 좌표 갱신만 담당

좌표계 변환 문제

  • DOOM은 Y가 북쪽으로 증가하는 2D 좌표계를 사용하지만, CSS 3D는 Y가 위쪽, Z가 시청자 방향으로 향함
  • 변환 시 translate3d(x,-z,-y) 형태를 사용해 좌표계를 맞춤
  • rotateY(atan2(var(--delta-y), var(--delta-x))) 계산이 추가 변환 없이 작동하는 점이 특징

카메라 대신 세계를 움직이기

  • CSS에는 카메라 개념이 없어, 플레이어 대신 세계를 반대로 이동시키는 방식 사용
  • --player-x/y/z/angle 네 개의 커스텀 속성만 JS에서 갱신
  • translate: 0 0 var(--perspective)로 시점 보정, rotateY와 translate3d로 시야 회전 및 위치 이동
  • 모든 이동은 속성 업데이트만으로 처리

바닥은 눕힌 div

  • 기본 DOM 요소는 수직 평면이므로, 바닥은 rotateX(90deg)로 눕혀 수평 배치
  • clip-path, polygon(), path() 로 복잡한 다각형 영역과 구멍 표현
  • 최신 CSS의 shape() 함수로 퍼센트 기반 경로와 evenodd 규칙을 함께 사용 가능

텍스처 정렬

  • 인접 섹터 간 텍스처가 끊기지 않도록 월드 좌표 기반의 background-position 사용
  • 모든 섹터가 동일한 텍스처 그리드를 공유해 매끄러운 경계 연결 구현

문, 리프트, @property 애니메이션

  • 문 열림은 섹터 천장을 올리는 동작으로, 컨테이너 <div>의 transform을 CSS 전환(transition) 으로 처리
  • 리프트는 플레이어가 함께 움직이므로 JS에서 --player-z를 동기화
  • @property로 커스텀 속성을 숫자형으로 등록해 부드러운 낙하·이동 효과 구현

스프라이트와 미러링

  • 적 스프라이트는 항상 카메라를 향하는 빌보드 방식
  • 8방향 중 5세트만 실제 이미지, 나머지는 좌우 반전(scaleX) 으로 처리
  • steps() 애니메이션으로 걷기·공격·사망 프레임 전환
  • 모든 적이 동시에 걷는 문제는 JS의 무작위 animation-delay 로 해결

발사체, 폭발, 총탄 효과

  • 로켓·화염구 등은 CSS 애니메이션으로 A→B 이동 자동 처리
  • JS는 시작·끝 좌표와 지속시간만 설정, 충돌 시 요소 제거 및 폭발 스프라이트 생성
  • 폭발과 총탄 연기는 steps() 기반 3프레임 애니메이션 후 자동 삭제

조명과 필터

  • 섹터별 밝기값을 --light 속성으로 지정하고, 내부 요소는 filter: brightness() 로 상속
  • 깜박이는 조명은 @keyframes로 --light 값을 주기적으로 변경
  • 투명한 적(Spectre)은 SVG 필터(feColorMatrix, feTurbulence, feDisplacementMap)로 왜곡된 실루엣 표현

반응형 UI와 앵커 포지셔닝

  • 게임은 모바일 대응형으로, HUD는 flex-wrap으로 줄바꿈
  • 무기 스프라이트는 HUD 높이에 맞춰 anchor-name / position-anchor 로 위치 자동 조정
  • 터치 조작 버튼도 동일한 앵커 방식으로 배치

관전자 모드

  • 맵 전체 조망3인칭 추적 시점 지원
  • CSS의 sin()·cos() 함수를 이용해 플레이어 뒤쪽 카메라 위치 계산
  • rotate·translate 속성을 분리해 부드러운 시점 전환 구현
  • JS는 위치·각도만 갱신하고, 카메라 수학은 CSS가 처리

컬링과 성능

  • 수천 개의 3D 요소로 인해 브라우저 컴포지터 부하 발생
  • JS 기반 컬링: 시야 밖 요소를 hidden 처리
  • CSS 기반 컬링 실험: 계산값으로 visibility 제어, 타입 그라인딩(type grinding) 트릭 사용
  • if() 함수가 표준화되면 더 간결한 조건식으로 대체 가능

깊이 정렬

  • 브라우저가 깊이 정렬(z-order) 을 자동 처리
  • 동일 평면의 오브젝트는 미세한 오프셋을 주어 깜빡임 방지

DOOM의 ‘속임수’와 하늘 처리

  • 원본 DOOM은 하늘을 2D 텍스처로 “벽” 위에 그리는 투영 트릭 사용
  • CSS 렌더러는 실제 3D 공간에 하늘을 배치해야 하므로, 일부 장면에서 맵 뒤쪽이 노출되는 문제 발생
  • 해결책은 컬링 단계에서 하늘 벽 뒤의 요소를 렌더링 제외

결론 — CSS의 한계와 가능성

  • 전체 게임 루프는 JS로, 렌더링은 순수 CSS 기반으로 분리
  • 삼각함수, @property, clip-path, SVG 필터, 앵커 포지셔닝 등 최신 CSS 기능을 극한까지 활용
  • WebGL 수준의 성능은 아니지만, CSS 표현력의 확장 가능성을 입증
  • Safari·Chrome의 3D 관련 버그와 성능 이슈 다수 발견
  • 최종 결론: “CSS로 DOOM을 실행할 수 있는가?” → 가능하다. Yes, it can.
Read Entire Article