1P by GN⁺ 5시간전 | ★ favorite | 댓글 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)로 시점 보정, rotateYtranslate3d로 시야 회전 및 위치 이동
  • 모든 이동은 속성 업데이트만으로 처리

바닥은 눕힌 div

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

텍스처 정렬

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

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

  • 문 열림은 섹터 천장을 올리는 동작으로, 컨테이너 <div>transformCSS 전환(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.
Hacker News 의견들
  • “이걸 DOOM으로 돌려봤다”류의 사람들은 정부의 우주 추진 시스템 부서에 고용되어야 한다고 생각함
    그들은 손가락만 굴리기엔 너무 이색적인 과제가 필요한 사람들임

    • 하지만 결국 그들이 만드는 추진 시스템조차 DOOM을 돌릴 수 있게 만들 것 같음
  • 이건 “할 수 있으니까 해본다”식의 프로젝트 같음
    CSS가 원래는 선언적 스타일링 언어였는데, 이제는 조건문, 수학 함수, 렌더링 트릭까지 생기면서 점점 프로그래밍 가능한 시스템으로 변하고 있음
    중요한 건 “CSS로 DOOM을 돌릴 수 있나”가 아니라, 원래 그런 용도가 아니었던 레이어에 얼마나 많은 로직을 밀어넣고 있는가

    • 이건 전형적인 추상화 역전(abstraction inversion) 의 사례임
      CSS는 프로그래밍 언어가 되려는 욕망을 숨기고 있지만, 결국 완전히 잘못된 추상화로 변해버렸음
    • 핵심은 표현(CSS)상호작용(JavaScript) 의 경계가 어디까지인가임
      예전엔 드롭다운, 툴팁, 레이아웃을 위해 JS가 필요했지만, 이제는 CSS 속성으로 앵커 위치나 조건(if())까지 지정할 수 있음
      애니메이션, 디테일 토글, 접근성 관련 효과까지 CSS로 처리 가능해짐
  • CSS로 3D 장면을 만드는 건 예전부터 가능했지만, 상호작용엔 JS가 필요했음
    이제는 x86CSS 프로젝트처럼 JS 없이도 CPU를 CSS만으로 에뮬레이션할 수 있음
    그래서 DOOM도 순수 CSS로 실시간 구현이 가능할지 궁금함

    • 하지만 CSS x86 CPU는 너무 느려서 게임 루프를 처리할 수 없다고 함. 결국 JS가 필요함
    • 이런 CSS의 진화는 예견된 결과였고, HTML 진영이 애초에 DSSSL을 채택했어야 했다고 생각함
  • 이 사례는 사람들이 왜 TypeScript 기반 CSS를 원하게 되는지를 잘 보여줌
    Chrome에서만 작동하는 if() 같은 기능 때문에, 개발자들은 이런 꼼수를 씀
    예를 들어, animation-delay@keyframes를 이용해 가시성 토글을 흉내내는 트릭을 쓰는 식임
    CSS if()가 표준화되면 이런 해킹 없이 깔끔하게 조건 처리가 가능해질 것임

  • DOOM 치트 코드인 IDDQDIDKFA는 아쉽게도 작동하지 않았음

  • 예전에 div에 둥근 모서리를 만들려면 GIF 네 장이 필요했던 시절이 떠오름

    • div라니, 그 전엔 전부 table 레이아웃으로 하던 시절도 있었음
  • 정말 인상적임! div 하나만 지워도 벽 통과(wall hack) 가 가능함

    • 더 나아가 .wallopacity: 0.7만 주면, 예전식 투명 벽핵 느낌을 그대로 재현할 수 있음
  • “이걸 어디서 직접 돌려볼 수 있나?” 싶었는데, cssdoom.wtf에서 가능함

    • 휴대폰으로 실행하자마자 기기가 뜨거워짐
    • 모바일에서 DOOM을 이렇게 부드럽게 돌려본 건 처음임
    • Safari에서도 완벽히 작동함 — 이런 일은 거의 없음
    • Firefox에서는 잘 돌아가지만, Alt 키 매핑이 메뉴를 열고 닫아서 불편했음
      Chromium에서는 오히려 더 버벅였고, strafing 키는 찾지 못했음
      그래도 전반적으로 놀라운 구현임
  • CSS는 위원회 설계의 한계를 보여주는 대표적인 명세임
    SVG와 함께 “가장 보기 흉한 스펙” 경쟁을 벌이고 있음

    • 혹시 제목만 보고 댓글 단 거 아니냐는 반응도 있었음
  • 이 멋진 구현에 대해 한 가지 덧붙이자면,
    실제로는 플레이어가 움직이는 게 아니라 세상이 움직이는 것
    카메라는 단지 시야각(frustrum)을 계산하기 위한 개념적 도구일 뿐임