1 ポイント 投稿者 GN⁺ 2026-03-29 | 1件のコメント | WhatsAppで共有
  • 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の4つのカスタムプロパティだけをJSから更新
  • translate: 0 0 var(--perspective)で視点補正を行い、rotateYtranslate3dで視界の回転と位置移動を処理
  • すべての移動はプロパティ更新だけで処理

床は寝かせたdiv

  • 基本のDOM要素は垂直平面なので、床はrotateX(90deg)で寝かせて水平配置
  • clip-pathpolygon()、**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フィルターfeColorMatrixfeTurbulencefeDisplacementMap)で歪んだシルエットを表現

レスポンシブUIとアンカーポジショニング

  • ゲームはモバイル対応で、HUDはflex-wrapで折り返し
  • 武器スプライトはHUDの高さに合わせて**anchor-name / position-anchor**で位置を自動調整
  • タッチ操作ボタンも同じアンカー方式で配置

観戦モード

  • マップ全体の俯瞰三人称追跡視点をサポート
  • CSSのsin()cos()関数を使ってプレイヤー後方のカメラ位置を計算
  • rotatetranslateプロパティを分離し、滑らかな視点切り替えを実現
  • 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.

1件のコメント

 
GN⁺ 2026-03-29
Hacker Newsのコメント
  • 「これでDOOMを動かしてみた」系の人たちは、政府の宇宙推進システム部門に雇われるべきだと思う
    指先を動かすだけでは物足りない、もっと風変わりな課題が必要な人たちだ

    • とはいえ、結局その人たちが作る推進システムですらDOOMを動かせるようにしてしまいそう
  • これは「できるからやってみる」タイプのプロジェクトっぽい
    CSSはもともと宣言的なスタイリング言語だったのに、今では条件分岐や数学関数、レンダリングのトリックまで入り込み、どんどんプログラム可能なシステムへと変わってきている
    重要なのは「CSSでDOOMを動かせるか」ではなく、本来その用途ではなかったレイヤーにどれだけ多くのロジックを押し込んでいるか

    • これは典型的な**抽象化の反転(abstraction inversion)**の例だ
      CSSはプログラミング言語になりたがっているのを隠しているが、結局は完全に間違った抽象化になってしまった
    • 核心は**表現(CSS)相互作用(JavaScript)**の境界がどこまでなのかということだ
      以前はドロップダウンやツールチップ、レイアウトのためにJSが必要だったが、今ではCSSプロパティでアンカー位置や条件(if())まで指定できる
      アニメーション、detailsのトグル、アクセシビリティ関連の効果まで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に角丸を付けるのに4枚のGIFが必要だった時代を思い出す

    • divどころか、その前は全部tableレイアウトでやっていた時代もあった
  • 本当に印象的だ! divを1つ消すだけで**壁抜け(wall hack)**ができる

    • さらに .wallopacity: 0.7 を与えるだけで、昔ながらの透明壁ハック感までそのまま再現できる
  • 「これどこで実際に試せるんだ?」と思ったが、cssdoom.wtfで試せる

    • スマホで起動した瞬間に端末が熱くなった
    • モバイルでDOOMがこんなに滑らかに動くのは初めて見た
    • Safariでも完璧に動く — こんなことはめったにない
    • Firefoxではよく動いたが、Altキーの割り当てがメニューを開閉してしまって不便だった
      Chromiumではむしろさらにカクつき、ストレーフキーは見つけられなかった
      それでも全体として驚くべき実装だ
  • CSSは委員会設計の限界を示す代表的な仕様だ
    SVGと並んで「最も醜い仕様」の座を争っている

    • もしかしてタイトルだけ見てコメントしてない?という反応もあった
  • この素晴らしい実装について1つ付け加えると、
    実際にはプレイヤーが動いているのではなく、世界のほうが動いている
    カメラは単に視野角(frustrum)を計算するための概念的な道具にすぎない