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つの主要な部分で構成される
- ユーザーインターフェース(3Dツール、キーボードショートカット、サイドバー、ゲームコントローラー)
- GLSLシェーダジェネレータ + レイマーチングレンダラー
- GPUベースのマウス選択
- エクスポート向け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件のコメント
Hacker Newsコメント