kick the base

Houdiniと、CG技術と、日々のこと。

Houdini: 裏面ポリゴンを自動反転するシステムをつくった話 - 前編

今回は裏面ポリゴンを自動反転する仕組みをつくった話についてです。

  • システム作成の背景
  • そもそも裏面ポリゴンとはなにか
  • 裏と表、どう判定すればいいのか

なんてことをお話したいと思います。

ポリゴンの表裏に関する仕様やツールをブラッシュアップする工程にも触れるので少し長いお話になるかと思います。よかったらお付き合いください。

また、本記事には現時点での僕の理解に基づいて記述していますので、間違いがあればTwitterID、kickbaseまでご指摘いただけるととても喜びます。

【 追記 】 後編をアップしました。

※ 長くなってしまったので前後編に分けました。サンプルシーンも記事に合わせ分割しています。

目次

データ配布

記事で使用したシーンはこちらからダウンロードできます。

  • Windows10
  • Houdini 18.5.408

※ 最終形のデータは含まれていません。後編の記事にて配布予定です

なぜ必要だったか

普通にジオメトリを変形していく過程ではあまり問題が起こらないですし、一点物だったら手で直しちゃえばよいのですが、データドリブンのプロシージャルモデリングとなると話が変わってきます。「ポリゴンを数学やプログラミングで生成し、自動的にUVを貼る」ようなシステムでは裏面ポリゴンが作られる可能性もグッと上がってきますし、それらも自動的に直すシステムを盛り込みたいですよね。

そうと決まったら、まずはポリゴンの表裏についての性質を調べ、自分の理解を整理しましょう。

何事も敵を知り己を知れば百戦危うからずです。

裏面ポリゴン is 何

皆さんは裏面ポリゴンにどんなイメージを持っているでしょうか。

Houdiniのデフォルトセッティングだとこんな感じかと思います。(ここではプリミティブナンバー4が裏面ポリゴンです)

f:id:kickbase:20210403164451p:plain

ただしこの裏面ポリゴンの描画に関してはディスプレイオプションがあり、Remove Backfacesにチェックを入れると裏面ポリゴンは描画されません。

f:id:kickbase:20210403164647p:plain

Remove Backfacesオンでビューポートを動かすとこんな感じになります。

f:id:kickbase:20210403165114g:plain

裏面ポリゴンと頂点番号

上記で裏面ポリゴンがどんな表示をされるかはわかりましたが、CGの世界ではどのようなデータ構造を指すのでしょう。そこには頂点番号*1の理解が不可欠です。

ディスプレイオプションからVertex > Numbersにチェックを入れましょう。緑色で表示されているのが頂点番号です。

f:id:kickbase:20210403171213p:plain

よく見てみるとわかりますが、裏面ポリゴン(真ん中のフェース)だけ頂点番号が左回りですね。この頂点番号が右回りか左回りかによってポリゴンの表裏というのは決定されます。

そしてもうひとつ重要な性質を付け加えるならば、ひとつのプリミティブに裏面ポリゴンが存在していると、その部分は穴として認識されます。

f:id:kickbase:20210403172026g:plain

普通にモデリングをしている上ではあまり意識することはありませんが、いつか役に立つ日が来るかもしれません。

裏面ポリゴンと面法線

ポリゴンの裏表は頂点番号の回る方向によって決まるということをご紹介しました。

そしてポリゴンの表裏にはもうひとつ別の特徴があります。それはポリゴンの表方向に対して面法線が向くというものです。下記動画を見ていただければわかりますが、真ん中のポリゴンだけ面法線(黄色い線でビジュアライズしています)が逆を向いていますね。

f:id:kickbase:20210403170922g:plain

しかしここで重要な点として、Houdiniはいつでも強制的に任意のベクトルをノーマルに与えることができます。下記動画を見てください。

f:id:kickbase:20210403173207g:plain

attribwrangle_force_Nノードで@N = {0,1,0};というコードを記載し、すべてのフェースに対してY軸上向きの面法線を与えていますが、ビューポートを見れば分かる*2通り、面法線を変更してもポリゴンの表裏は変更されないというのがわかるかと思います。

さあ、これらをまとめてみましょう。

ポリゴンの表裏に関するまとめ

こんな性質がありそうです。

  • 裏面ポリゴンの表示はオプションによって見え方が変わる
  • ポリゴンの表裏は頂点番号の回転方向によってのみ決定される
  • 初期値ではポリゴンの表方向に面法線が設定される
  • 面法線を変更しても頂点番号は変更されない(つまりポリゴンの表裏は変更されない)

この性質を用いて早速ツールを作っていきましょう。

ツールを作っていく

手を動かす前に

急いては事を仕損じると言いますし、Houdiniを起動する前にどういう戦略で行くか考えてみましょう。

表裏の判定に「頂点番号を使う」か「面法線を使う」かによって大きく処理が変わりそうです。

ポリゴンの表裏判定は頂点番号によって決定されるのでこれを調べるのが最も原理的な気がしますが、下記懸念があります。

  1. 右回り左回りの基準を設けるためには面法線が必要になりますが、面法線と頂点番号順のふたつを管理しなければいけません
  2. コンケイブ・ポリゴンとコンベックス・ポリゴンも影響がでるかも
  3. プリミティブごとに計算を行うためFor-Loopが必要?

他にもいくつか考えられますが、いちばん重要なのは1です。プログラマとして名高いJamie Zawinski氏のこんな言葉があります。

ひとつの問題に直面した時「なるほど、正規表現を使えばいいのか」と考える人がいる。

こうして彼らはふたつの問題を抱えることになる。

原理的に物事を考えることは大切ですが、問題を増やしてしまっては意味がありません。

ここで思い出してほしいのは初期値ではポリゴンの表方向に面法線が設定されるという特性です。Houdiniはいつでも好きなノーマル(アトリビュート)を与えることができますが、逆に言うといつでも初期値のノーマルをつけることができます。

渡されたジオメトリのノーマルは一旦置いておいて、NormalSOPをつないで面法線を初期化しちゃえばそれを基準に裏表の判定ができるじゃん。

このアイデアが出せれば、一気に問題はシンプルになります。元ジオメトリが持つ面法線を再設定したいときは、表裏を整えてから移植してしまえばいいのです。

さあ、気が楽になったところで実際のツール制作に進みましょう。

プロトタイプ(最もシンプルな方法)

  1. NormalSOPで面法線をリフレッシュ
  2. 反対方向を向いているprimitive(フェース)をグループ化
  3. 該当グループにReverseSOPを当てる

やるべきことはこれだけです。行ってみましょう。

MESHという名前のNullノードまでが入力されるジオメトリという形で進めていきます。

f:id:kickbase:20210403195438p:plain

NormalSOPで面法線を初期化します。

f:id:kickbase:20210403195532p:plain

GroupSOPのKeep by Normalsオプションをオンにして{0, -1, 0}方向(つまり下向き方向)の面法線を持っているprimitiveをグループ化します。

f:id:kickbase:20210403195646p:plain

ReverseSOPを上記手順で作ったグループに適用します。*3

楽勝ですね。ハイ終わり終わり。と行かないのが世の常です。

f:id:kickbase:20210403195857p:plain

この方法だと面が傾いていると使えません。GroupSOPのDirectionがハードコードされているためです。ここを修正していきましょう。

ジオメトリの回転に対応する

面が傾いていることをわかりやすくするため、ブタさんを輪切りにしました。NormalSOPで面法線を初期化するまでは同じです。

f:id:kickbase:20210403200520p:plain

Wrangleを用いてプリミティブ番号ゼロのprimitiveが持つ面法線NをDetailアトリビュートとして保持します。

@N = prim(0, "N", 0);

f:id:kickbase:20210403200745p:plain

今までと同様にGroupSOPで面法線を基準にグルーピングしますが、ここで先程のDetailアトリビュートを参照するように修正します。

  • X: detail(0,"N",0)
  • Y: detail(0,"N",1)
  • Z: detail(0,"N",2)

この仕組を使うことでハードコードがなくなるためジオメトリがどんな回転をしていても問題なく取得できるようになるわけです。

f:id:kickbase:20210403201212p:plain

しかしビューポートを見ていただければ分かる通り、表示上は全部が裏面になってしまっています(全部が裏面というのも変な話ですが)。

これはプリミティブ番号ゼロのprimitiveが持つ面法線Nと同じ方向を向いているprimitiveを反転させたことによって起こる現象です。別にこのままでも良いですが、ふたつの方向のうち少数の方を反転させる方が自然な結果になりそうですね。

最後にそこを対応しましょう。

少数派を反転させる

NormalSOPまでは同じです。

f:id:kickbase:20210403201803p:plain

ジオメトリスプレッドシートを見てもprimitive Normalが取れているのが確認できますね。

f:id:kickbase:20210403201900p:plain

今度はプリミティブ番号ゼロのprimitiveを闇雲に取り出すのではなく、LabsのMin Max AverageSOPを利用します。ただし僕のシーンでは改造したバージョンを使っていまして、そのHDAのダウンロードと実装方法の解説はこちらの記事に以前書きましたのでご参照ください。

使い方はほとんど一緒で、ポイントはAnalysis MethodをModeにしている点です。Modeというのは最頻値のことで、僕はよくPythonで取得することが多いのですが、今回はこのノードに同様の機能があるため利用しています。これで多数派の面法線を取得できたということになります。

f:id:kickbase:20210403202401p:plain

GroupSOPで方向を先程作った最頻値の面法線ベクトルを与えてあげて

f:id:kickbase:20210403202443p:plain

ReverseSOPでは!modeとしているところがミソですね。最頻値以外、つまり少数派の面法線をもつグループを反転するということになります。

to be continued...

いやーうまく行ったうまく行った。と胸を撫で下ろしたあとこんな事を考えました。

f:id:kickbase:20210403202629p:plain

こういうやつも対応したくない?

したいですよね。

というわけで後編は面が色んな方向を向いていても対応できる仕組みを追加していきます。

まとめ

だいぶ長い記事になってしまいましたが 後編もご一読いただけると幸いです。お楽しみに。

参考サイト

本記事で登場したエクスプレッション、ポイントと頂点の違い、理解のサポートになりそうな記事などを記載しておきます。

*1:Houdiniではポイントと頂点は明確に区別されますが、本記事では割愛します

*2:裏面ポリゴンの表示も変更なし、頂点番号も変更なしということです

*3:勘のいい皆さんならおわかりかと思いますが、ReverseSOPは頂点番号順を逆にするノードというわけです