世界を動かす技術を、日本語で。

HNに表示: ラスタライズなしでテクスチャを投影する3D SVGレンダラーを作成しました

概要

SVGではCSSのような 透視変換(パース変換) がサポートされていないため、3Dオブジェクトの画像テクスチャ投影には工夫が必要。 アフィン変換 のみで近似する手法を模索し、画像の細分割+局所的なアフィン変換で高精度な見た目を実現。 SVGファイルサイズも抑えられ、 軽量かつ見た目が正確な3D表示 が可能。 GitHubでの回路基板レビューや視覚的スナップショットテストにも応用可能。 バグ修正も行い、安定した投影アルゴリズムを実装。

SVGで3Dオブジェクトに近いパース投影を実現する工夫

  • SVG ではCSSのような perspective変換 が標準サポートされていない現状
  • アフィン変換 (transform: matrix(a,b,c,d,e,f))のみが利用可能
  • アフィン変換は 2D変形(拡大縮小・回転・平行移動) には有効だが、 3D的なパース投影 には非対応
  • 1枚のアフィン変換画像では パースの歪み を再現できない制約

近似手法の検討と実装

  • 画像をラスタライズして歪ませる案は SVGの軽量性 を損なうため不採用
  • レイトレーシング的な全ピクセル計算も SVGの利点 を活かせないため不採用
  • 画像を細分割し、各領域ごとに最も近似的なアフィン変換を適用 する手法を採用
    • 各サブ領域に clip-path で切り抜き、個別のアフィン変換を割り当て
    • 画像の再利用は SVGのdefs で実現し、ファイルサイズを抑制

細分割数による見た目の変化

  • 細分割数2: 明らかな歪み (バルジ)発生
  • 細分割数4: 表面が粗い 印象
  • 細分割数16: まだ波打つ 見た目
  • 細分割数512: ほぼ平坦で自然 な投影に見える
    • 細分割を増やすことで パース近似精度 が向上

SVGファイルサイズと効率

  • 画像は1つだけ定義 し、各サブ領域ごとに clip-pathとアフィン変換 を割り当て
  • 細分割数を増やしても ファイルサイズは比較的コンパクト
  • 実際のSVG例やファイルサイズの変化を 表やコードスニペット で検証

活用例と今後の展望

  • GitHub上での回路基板レビュー視覚的スナップショットテスト への応用
  • tscircuit 等のプロジェクトで 3D SVGアーティファクト を活用
  • コードのバグ修正・最適化を進め、 正確なアフィン変換計算 を実装

まとめ

  • SVGで3D的パース投影を近似的に再現 するには、画像細分割+局所アフィン変換が有効
  • ファイルサイズと見た目のバランス を両立可能
  • オープンソース実装ビジュアルテスト にも応用できる
  • 今後も改良・応用が期待 される技術

Hackerたちの意見

サブディビジョンはいいテクニックだね。友達がフライトシミュレーターをゼロから作ってたんだけど(数学の参考にFoleyとvan Damを使ってた)。クラシックな視点の問題として滑走路を考えてみて。滑走路の真ん中に等間隔で点線が引かれてるのを想像してみて。もし3Dレンダラーがビットマップで四角形にテクスチャを貼れる段階まで来たら、滑走路のために大きな長方形を作って、真ん中に点線のビットマップを使うのは簡単そうに見えるかもしれない。でも、テクスチャマッピングはパースペクティブにならない(複雑な数学が絡まない限りは)。Foleyとvan Damはこう言ってる — 滑走路を12本くらいの「短い」滑走路に分けて、端から端に並べる(サブディバイド)。各滑走路のビットマップテクスチャはただの短いストライプ1本。こうすることで、たくさんの四角形が端から端に並ぶから、長い滑走路と一連の点線があるように見える。各滑走路の個々の部分(ストライプ1本)は本当の意味でパースペクティブではないけど、遠くに行くにつれて各四角形はパースペクティブを考慮している — 小さくなって、より短縮されてる。

パースペクティブに正しいテクスチャマッピングは、かなり前から過剰なサブディビジョンなしで解決されてるよ。FoleyとVan Damの時代には、ラスタライズされたピクセルごとに分割が必要だから避けられてたんだ。80年代後半はすごく遅かったからね。

あなたのCSSトライアングルはまだラスタライズを通してレンダリングされてるけど、良い仕事だよ。

でも、彼は特定の解像度に制限されてるわけじゃないよね。もしPNGを使ったら、制限されちゃうけど。

「軽量なSVGレンダラーだね」その一方で… 一つのテクスチャ付き四角形のために512のサブディビジョンを描いてる。確かに可愛いトリックだけど、これに数千の要素以上を描かせたら、すぐにダウンすると思うよ。パースペクティブに正しいテクスチャマッピングがハードウェアに組み込まれてるWebGLを使ったほうがいいよ。

このバニラTSレンダラーの目標は、GitHubでビジュアルディフを持ち、ブラウザ環境なしで動くレンダラーを作ることなんだ。ほとんどの3Dレンダラーはリアルタイムのスピードに焦点を当ててて、ファイルサイズやランタイムのポータビリティにはあまり気を使ってない。実際には、ファイルサイズのトレードオフを考えて、サブディビジョンを64くらいに設定すると思う。

パースペクティブ変換ができないという同じ制限は、PlayStation 1のグラフィックスの特徴の一つでもあることは注目に値するね。そして、サブディビジョンのワークアラウンドはPS1ゲームでも使われてた同じ方法なんだ。もっと読みたい人はここを見てね: https://retrocomputing.stackexchange.com/questions/5019/why-...

これは、初期のDOSの3Dソフトウェアでラスタライズされたゲーム(例えば、Descent)に多く見られた制限でもある。透視変換はピクセルごとに割り算が必要だから、当時のCPUではコストが高すぎて、許容できるパフォーマンスを得るためにそれをスキップしていたんだ。

それ、めっちゃクレイジーだね。記事を読んでるとき、頭の中でそのことを考えてた。車のゲームのことを思い出して、ガレージでパネルが回転する時の見た目を想像してたんだ。

SVGなのにすごく見栄えがいいね!一つ、ちょっと余計なお世話かもしれないフィードバックだけど: あのUSB-C接続は完成してるの?CCxピンの検出抵抗要件に準拠してるのかな?すごく素っ気なくて空っぽに見えたから、上流ホストがデバイスを識別できるようにするためのRdネットワークがあると思ってたんだけど。もし明らかなことを見逃してたらごめんね、私は電子工学のエンジニアじゃないから。例えば[1]を見てみて。 [1]: https://medium.com/@leung.benson/how-to-design-a-proper-usb-...

それは、単にパワーを使うためだけで、あまりパワーが必要ないから、私たちがレンダリングしたシンプルなボードにはうまくいくんだ。実際には、CC1とCC2の設定を抵抗器で調整したいところだよね!

すごくクールだね!数週間前にSVG 3Dレンダラーを実装したばかりなんだ [1]。でも、まだテクスチャリングは実装してなくて、どうやってやるのか気になってた。 [1]: https://youtu.be/kCNHQkG1Q24?si=3VxfVFtG2MiEEmlX

別のアプローチとしては、SVG要素に対して個別に変換を適用する方法がある。Inkscapeにはパスに適用できる透視変換ツールがあるけど、パス自体で近似と細分化を行う必要があるかもしれないから、ちょっと複雑かもね。

あなたのレンダラー、めっちゃすごい!ネイティブのTS/JSで「オフ・ザ・シェルフ」のSVGレンダラーがないことに驚いたよ。重いエンジンなしで3Dモデルを作れるのは大きなことだよね、ビジュアルスナップショットテストのために!

スザンヌをロードしたとき、モデルを動かすときにフレームレートが落ちるのが目に見えたよ。計算の中でホットパスはどこなの?

あなたのアルゴリズムは正しくないと思う。少なくともキューブの面のチェッカーボードの例では、対角線が曲がってるよ。透視変換ではそんなことは起こらない。おそらく、ターゲット空間でエッジに沿って均等に細分化して、それをソース空間の均等な細分化にマッピングしてるんだろうけど、それは正しくないよ。編集: 記事の透視変換と正しい透視変換の比較: https://imgur.com/RbRuGxD

著者が自分の給料に見合わない数学を軽視していることを考えると、間違っているのも驚きではないね。

いわゆる「直線は透視図法でも直線のまま」ってやつだね!

実際、アフィン変換だけで正しい透視を描くことは可能なのかな?それがこの記事のポイントだと思ってたんだけど。

もっと明らかに言うと、前の四角は後ろの四角より大きくないよね。形が変わっても、各四角の面積は同じに見える。ぱっと見は信じられないくらいリアルに見えるのに、よく見ると明らかに間違ってるのが面白いよね。

著者です: コメントした人は同じ焦点距離を設定してないと思う。焦点距離によって表面が曲がって見えることがあるから、アルゴリズムの歪みへの対応能力をテストするために、低い値に設定したんだ。「焦点距離 歪み キューブ」でググれば、焦点距離がグリッドをどう歪めるかの例が見れるし、「フィッシュアイレンズ キューブ」とかでもいいよ。編集: キューブのエッジ(黒い線)が、エッジ全体で透視変換を取り入れていないから、混乱が生じていると思う。焦点距離を考慮するとテクスチャはおそらく正しいし、キューブのエッジは誤解を招くほど真っ直ぐなんだ。俺のミスだけど、技術自体は有効だけど、キューブのエッジの黒い線は誤解を招くほど真っ直ぐ(テクスチャとは同じようにレンダリングされていない)だよ。

彼はSVGが裏で何をしてると思ってるんだろう?ラスター化だよ。プロセスのどこかで必ずラスター化が行われる。たった一つのクワッドを描くのに512個のクリップパスを計算するなんて、頭おかしいよね。

SVGには3D空間の概念がないから、パースペクティブをレンダリングしたいなら自分でSVGラスターライザーを書く必要があるよ。

いつか、シンプルなHTML/CSS標準の「テキスト」ページだけで済む時代が来るといいな。JavaScriptもDOMもなしで。これでウェブの利用ケースの70%をカバーできるはず。「それ以外」は、ブラウザプラグインとして配布されるプラグイン可能な実行ランタイムになる感じかな:[WASMエンジン、JVMエンジン、SPIR-Vエンジン、BEAMエンジンなど]、SVGが唯一の表示技術として。最後に定義するのは、システムとユーザーのインタラクションのための割り込みとイベントモデルだね。

すごいテクニックだね。俺は2000年にAdobeの元々のSVGチームにいて、この技術を使った最初の公開デモのいくつかを作ったんだ。こういう3Dの作業は最初に挑戦したことの一つで、変換の制限があってうまくいかなかったんだよね。自分なりの回避策もあったけど。一つのデモでは、地図用に建物の3Dフロアスタックを作ったんだ。それはアイソメトリックな視点を使っていて(平行線が交わらないやつね)、結構うまくいったよ。これは簡単で、回転とスケーリングの変換で実現できるんだ。もう一つは3D分子ビューワーで、クリックしてドラッグすることで構造を見られるやつ。これは基本的にSVGをキャンバスとして使って、描画用のxとy座標を使ってた。3Dの動きは全部JavaScriptでやってて、xとy座標を計算してSVG DOM内の形を更新してたんだ。スタイルを使って単結合、二重結合、三重結合を扱い、可読性のために別のグループを使って全てを重ねてたよ。