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.
-
Homepage
-
개발자
- CSS로 구현한 3D DOOM 렌더링