2 ポイント 投稿者 GN⁺ 2024-05-03 | 1件のコメント | WhatsAppで共有

1週間でC言語で3Dモデラーを作る

  • 1年前の秋に「Wheel Reinvention Jam」という1週間のプログラミングイベントに参加した
  • 既存ソフトウェアシステムを新しい視点で見直すことが目的だった
  • 「ShapeUp」という3Dモデラーを作成し、この本文を読む前にShapeUpのデモ動画を先に見ると理解しやすい
  • ShapeUpはブラウザ上で直接試すことができる

言語選択: C

  • TypeScriptコンパイラが遅いことへの不満からJamに参加した
  • esbuildやBunのTypeScriptパーサから始めるなら、TypeScriptの高速な部分集合を実装するプロジェクトになるだろうと考えられた
  • しかし、ターミナルのコマンド実行速度比較では面白いデモにはならなさそうだったため、3Dプロジェクトに方向転換した
  • Ray marched signed distance fields(SDF)のおかげで、1週間でいちから3Dプロジェクトを作ることが可能に思えた
  • 同等の三角形ベースレンダラーより、SDFを使ったシーンの方がずっと高速に実装できる
  • 以前にSDFシェーダを作成したことはあったが、かなり基礎レベルにとどまっており、コードを編集してモデリングするのは不自然に感じた
  • マウスで形状を編集したかった。今回のJamはそれを実現する機会だと考えた
  • プロジェクト名をShapeUpにした

C言語使用の利点

  • Cは非常にシンプルで原始的な言語なので、組み込みデータ構造の不足を埋めることやポインタバグの修正に多くの時間を費やすだろうと見込まれた
  • しかしCのシンプルさは強みになった
    • 速くコンパイルされる
    • 複雑な計算を隠さない構文
    • シンプルなので、常に構文を調べ続ける必要がない
    • ネイティブとWebAssemblyに容易にコンパイル可能
  • Cの欠点は22年間の使用で培った習慣で避けられる
  • ShapeUpは小さな単一のCファイルで構成され、非常にシンプルである

ShapeUpのデータ構造

  • モデルはShapesという構造体の配列で構成される
  • Shapesは静的に割り当てられた配列に保存される
    • 割り当て失敗やメモリリークのリスクがない
    • 100個のShape制限は実際には制約にならなかった
    • レンダラー最適化時間が不足していたため、100件に達する前にフレーム速度が落ちていただろう
    • 時間があれば、モデルを小さなブロックに分割し、各ブロック内でraymarchingを実行したはず
  • 動的メモリは3か所だけでmallocを呼ぶ
    • 保存(ドキュメント全体を収容できる十分なサイズのバッファを割り当てる)
    • OBJエクスポート(すべての頂点を収容できる十分なサイズのバッファを割り当てる)
    • GLSLシェーダ生成(シェーダーソース用バッファ)
  • すべての場合に、関数末尾の単一freeがある
  • Cでメモリ管理がシンプルになることを示す例
  • C#, JavaScript, Pythonのような言語は、Shapeごとに個別でmallocし、そのポインタを動的配列に保存する割り当て構造を強制する
  • Cはメモリレイアウトを制御できる点が良い

ユーザーインターフェース

  • immediate mode user interface(IMGUI)で実装
  • IMGUI方式のUIが好き
    • デバッグが非常に簡単
    • 要素を配置するために実際のプログラミング言語を使う(CSS、constraints、SwiftUIとは異なり)
  • 大半のIMGUIと同様、enumを使ってどの要素がフォーカスされているか、マウスがどの操作を行っているかを追跡する
  • このプロジェクトでは動的配列やハッシュマップは必要なかったが、必要だったならstb_ds.hのようなものを使うつもりだった

Raylibライブラリの課題

  • Cを使う決断は良かったが、raylibには問題があった
  • 開発者体験を損なう奇妙な設計上の選択がある
    • enum型が想定されるべき箇所でintが使われており、コンパイラの型検査を無効化し、関数が自己説明的でなくなる
    • デフォルトのパラメータ妥当性検査を行わない(設計上の選択)
    • 依存関係に対する責任を負わない(GLFWの問題を解決したりパッチを提出したりしない)
  • raygui UIライブラリはおもちゃにすぎない
    • 浮動小数点数を表示できない
    • 重なり合う要素やクリッピングされた要素のマウスイベントルーティングを処理しない
    • 角丸を作れない
    • 見栄えのよいスタイル設定ができない
  • バグもある
    • フォント変更防止バグ
    • 描画関数が三角形間で頂点を共有しないため、ピクセル間隔が生じる
  • 問題が見つかるたびに報告したが、ほとんどは「won't fix」で終了し、バグレポート作成に時間がかかり断念してしまった
  • OpenGLウィンドウを作成してくれたのは良かったが、その利便性には大きな代償を払った
  • 幸い、OpenGL関数を直接使うか、機能をゼロから実装する逃げ道を見つけられた
  • 今後はsokolを使う予定

1週間の開発プロセス

  • ShapeUpは6日で完了する必要があった4つの主要な部分で構成される
    1. ユーザーインターフェース(3Dツール、キーボードショートカット、サイドバー、ゲームコントローラー)
    2. GLSLシェーダジェネレータ + レイマーチングレンダラー
    3. GPUベースのマウス選択
    4. エクスポート向けMarching Cubes
  • どれも難しくはなかったが、優先順位を正しく付けて脱線しないことが難しかった
  • こつこつしたり時間のかかる問題は、設計で解決するか、90%のケースで機能する賢くない解決策を使うと助かった
  • 時には機能を1日ほど先送りすると、無意識に解決策を見つけられることがあった
  • 常に動く3Dモデラーを持ち、時間が許す限り段階的に改善するよう努めた
  • ピラミッドを作るのと同じようなもの。層ごとに作れば最終段階でピラミッドが完成しないが、どこで止めても完全なピラミッドになるように設計できる

プロジェクトの結果

  • 1週間後には、意味のある3Dモデルを作成し、.objファイルとしてエクスポートできる3Dプログラムを持つことができた
  • マルチプラットフォームで実行され、ファイルの開く/保存機能もある
  • プロジェクトは2024行のCコードと250行のGLSLで構成されている
  • たった2300行ほどで、ある程度実用的な3Dモデラーを表現できることに少し驚いた
  • Jamの要約とHandmade SeattleカンファレンスでShapeUpのデモを見せてほしいという要請を受けた
  • 人々はShapeUpに感銘を受けていたようだが、大きな成果があったとは思えない。比較的シンプルなプロジェクトだったからだ
  • 私がやったことの本質は、何を作るか判断するセンス、それを作るための知識、そして1週間でやりきるための規律にあった

GN⁺の意見

  • Cのシンプルさと速度の利点をよく示す興味深いプロジェクトである。ただしCの低い抽象化レベルのため、商用プロジェクトにそのまま使うのは難しそうだ。現代的な3Dモデリングツールのすべての機能をCで直接実装するには、莫大な労力が必要になるだろう
  • 1週間で動くプログラムを完成させたこと自体は印象的だが、長期的な観点でコード保守と機能拡張を考えると、C++やRustのような言語を選ぶほうがよい選択だったかもしれない
  • SDFを使ったレンダリング手法は高速でシンプルだが、モデリングの自由度や品質には限界があるようだ。商用モデリングツールは主にSubDやNURBSといったサーフェスモデリング技術を使う。一方、ゲームやデモなどリアルタイム性が重要な領域では、SDFレンダリングは依然価値が高い
  • オープンソースライブラリ選定の難しさをよく示す事例である。ドキュメント、コード品質、サポート有無などを的確に見極め慎重に選ぶべきだ。独自実装も有効な代替手段になりうる
  • 動くプログラムを先に作ってから徐々に改善していく方式は、実務でも非常に有効。コア機能から完成させ、ディテールを改善していく形で優先順位を適切に調整することが重要に見える

1件のコメント

 
GN⁺ 2024-05-03
Hacker Newsコメント
  • Raylib の限界について、著者と完全に同意します
    • 現在 Raylib で始めたタワーディフェンス風ゲームの開発を進めていますが、同じ制約に直面しています
    • プラットフォーム間での全画面切り替えの不整合、画面モードの列挙不可、ランタイムでのレンダリング機能切り替え不可、コンパイル済みシェーダーの保存不可などの問題があります
    • Raylib はプロトタイプ作成には良いですが、重大な制約を受け入れない限りそれ以上は難しいです
    • 開発が進みすぎていて、今になって Raylib を SDL などへ置き換えるには遅すぎます
  • 形状を静的に割り当てた配列に保持する方法は、割り当て失敗やメモリリークのリスクがない素晴らしい方法です。実際、100 個の形状制限は制約になりません。
  • このプロジェクトが引き続き発展してほしいです。数か月後には、学習曲線がずっと緩やかな Blender/FreeCAD の特定ユースケースに対する本格的な代替になるかもしれません。
  • 動画のライブデモが本当に気に入りました。アプリ制作はもちろんのこと、そんな動画を 1 週間で作れるとは思いません。
  • メモリ処理など、さまざまな意思決定について語られていて興味深い記事でした。Crafting Interpreters 第2部に取り組みながら C を再学習している立場として、C が得意なことを思い出させてくれました。
  • 2024 行の C コードでこれを可能にした努力に感謝です :)
  • よく知っているツールを使って、ただ素晴らしいものを作れることには本当に強い何かがあります。記事はよくできていました。
  • C に関する主張には本当に同意します。特に「文法が複雑な演算を隠さないこと。シンプルだからずっと調べる必要がないこと」にとても共感します。また、C について何かを調べる必要がある場合でも、非常に簡単で有益です。これはシンプルで古い言語であることの利点です。
  • 時には C が必要なすべてだと思います。
  • 驚異的な開発速度です。説明動画も本当に面白く見ました!