LoginSignup
6
2

HoudiniでSketchUp風のスケッチスタイルレンダリング

Posted at

この記事はHoudiniアドベントカレンダー2023 22日目の記事です

はじめに

Procedural DesignAkira Saitoです。
Houdini等を使用しロボットなどのメカデザインを自動で行う研究をしています。
image

今回はHoudini20でSketchUpの様なスケッチスタイルレンダリングを行う方法を紹介します。

動作環境

Houdini Indy Version 20.0.551(apple silicon)
macOS Sonoma バージョン14.12
MacBook Pro (16-inch, 2021)
プロセッサ Apple M1 Max
メモリ 64 GB

Sketch Upとは?

元々は、Last Softwareが開発と販売を行なっていた建築系3Dデザインツールです。途中Googlに買収されたりと色々ありましたが、近年は米Trimbleが開発、提供しています。

建築向けに作られだツールですが、CADとしての精度、ブーリアンや押し出しの使い勝手の良さなど、魅力的な機能が多く、私もメカデザイン(モデリング)で度々使用していました。そして、今回Houdiniで実装しようとする、スケッチスタイルのビューポートレンダリングも大きな魅力の一つです。

デフォルトのSketchUpのレンダリング。
image.png
スケッチスタイルのレンダリング
image.png

SketchUpの作例

image.png
image.png
image.png

SketchUpの観察

SketchUpで、どの様な表現が行われているのかを観察します。
image.png

再現する要素

テクスチャ効果など、良い表現がありますが今回は以下の3点に絞って再現しました。

輪郭強調
輪郭線が太くなる
はみ出し
線が角を通り越している
ぶれ
直線が歪んでいる

Houdiniでの実装

基本方針としてはその効果をビューポートで確認できる方法で実装していきます。
すなわち、スケッチスタイルのエッジをモデルと同じ空間に生成する方法です。

仕様

・モデルを入力すれば自動でスケッチスタイルのエッジを生成します。
・スケッチスタイルのエッジは大きく分けて二種類。通常と輪郭線。
・輪郭線以外のエッジは、エッジグループで指定します。
・線の太さは奥行きに関わらず一定で任意の太さを設定できます。

輪郭線の生成

image.png
まずは、このような輪郭線を生成します。

シーンの準備

対象のオブジェクトを生成します。
image.png

輪郭線というものはカメラに依存するものなので、通常のカメラを生成します。
image.png
構図や画角、解像度などは自由です。

カメラ空間への変換

今回の処理の肝である、オブジェクトをワールド空間からカメラ空間への変換を行います。

3DCGの基礎を学んだ方なら、3Dワールド座標系のモデルは透視変換という処理を経てカメラから見た形状に変換される事はご存知だと思います。その透視変換は通常の場合、描画の直前に自動で行われる処理なので、一般的なアーティストはあまり意識する事はありませんが、今回は輪郭線などを抽出するためにモデリングの途中に意図的に行います。

NDC(Normal Device Cordinates)

カメラ座標の定義として、標準デバイス座標、NDC(Normal Device Cordinates)というものがあります。
その定義は、カメラに映る領域の横方向をX軸として0〜1の範囲、縦方向をY軸とした0〜1の範囲。
image.png

カメラの位置を0、カメラからの距離をZ軸のマイナス方向としています。すなわちZがプラスの場合はカメラより後ろの物なので見ることができません。
image.png

toNDC

HoudiniのVEXには、ワールド座標をNDCに変換する関数toNDCが用意されています。

image.png

attribute wrangle(point)に以下のコードを書くだけで、ワールド座標をNDCに変換することが出来ます。

@P = toNDC('/obj/cam1',@P);

変換前のcam1からの見え方。
image.png
NDC変換後の形状。
image.png

簡単ですね。

しかし、少し注意が必要です。
基本的にNDC変換後のオブジェクトを、Fron(Z+方向からの)平行投影カメラで見れば、変換時に参照したカメラからの見た目になるのですが、レンダリング解像度が縦横で異なる場合、縦も横も0〜1の範囲で表されるNDCでは、一般的な横長のレンダリング解像度の場合、NDCでは縦に伸びた絵になってしまいます。
image.png
image.png

そこで、カメラに設定された、レンダリング解像度情報を参照し、画像の縦横比を求め、モデルをスケールする必要があります。
先ほどのAttribute Wrangle(point)を以下のように書き換えます。

//カメラのパスを取得
string cameraPath = chs('cameraPath');
//カメラに設定された解像度を取得
int resX = chi(cameraPath+"/resx");
int resY = chi(cameraPath+"/resy");
//縦横の解像度を元にアスペクト比を求める
float aspect = (float)resY/resX;
@P = toNDC(cameraPath,@P);
//Y座標にアスペクト比を掛ける
@P.y *= aspect;

カメラパスを指定するパラメーターが追加しました。
image.png
これで、縦横比の正しいモデルを生成する事ができました。
image.png

輪郭線の抽出

輪郭線の抽出を行なっていきます。
まずは、Group SOPを使ってカメラに背を向けるポリゴン(Zマイナス方向に法線が向いているポリゴン)のprimitiveグループを生成します。
image.png
その後、Blast SOPで先ほどのprimitiveグループに含まれるポリゴンを削除します。
image.png

この削除で出来る境界エッジが、輪郭線になります。
見やすいようにマテリアルを非表示にし、境界エッジを青く表示させました。
image.png

その後、Group SOPのUnshared EdgeオプションをONし、境界エッジからEdge Groupを生成します。
image.png

PolyPath SOPでパスに変換。
image.png

Dissolve SOPを仕様し境界エッジを元に作られたEdge Groupに含まれるエッジ以外を消去します。
image.png

現状ではPrimitiveが細切れなので、
image.png

PolyPath SOPを使用し、Primitiveを連結させます。
image.png

このままでも問題はないのですが、負荷軽減のために、Ray SOPを使用して、遮蔽により見えない輪郭線を削除することも出来ます。
image.png
image.png

ここまでで、輪郭線の抽出は完了です。
image.png
斜め上から見ると、輪郭線とは思えない形状になっていますが、これでOKです。
image.png

輪郭線を太くする。

シンプルに Poly Wire SOPでポリゴン化し太くします。
image.png
NDCの中では、すでに透視変換が行われた状態なので、この空間で太さが一定の線を描けば、カメラからの見た目では、奥行きに関係なく太さが一定になります。

NDCのモデルをワールド空間に変換する。

fromNDC

先ほど使用したtoNDC関数の逆の変換を行う、fromNDCという関数があります。

attribute wrangle(point)に以下のコードを書くと、NDCをワールド座標に変換することが出来ます。
fromNDCを使用する前に、アスペクト(縦横)比の逆数を@P.yに掛けることを忘れないでください。

//カメラのパスを取得
string cameraPath = chs('cameraPath');
//カメラに設定された解像度を取得
int resX = chi(cameraPath+"/resx");
int resY = chi(cameraPath+"/resy");
//縦横の解像度を元にアスペクトを求める
float aspect = (float)resY/resX;
//Y座標にアスペクト比の逆数を掛ける
@P.y *= 1/aspect;
//NDCからワールド空間へ変換
@P = fromNDC(cameraPath,@P);

image.png

Color SOPで色を頂点カラーとして設定します。
image.png

最後にMarge SOPを使用し、入力メッシュと合成すれば完成です。
image.png

NDCをワールド座標に変換する、Attribute Wrangleの前に、Transform SOPで、Zプラス方向にオフセットすると線が強調されます。調整してみましょう。
image.png

これで、輪郭線はとりあえずの完成です。
image.png

image.png

「はみ出し」と「ぶれ」を加える

スケッチスタイルの特徴である、「はみ出し」と「ぶれ」を加えていきます。

まずは、対象のオブジェクトを輪郭線に直線と曲線を含む物に変えます。
今回は、Test Geometry:ShaderBall SOPの、vMantra Classicを使用します。
image.png
image.png
輪郭線が入った状態。
image.png

「はみ出し」と「ぶれ」は、線を太くする、Poly Wire SOPの直前で行います。
再利用するために、effectというサブネットの中に実装していきます。
image.png
NDCで輪郭線の抽出が終わった状態から始めます。
image.png

準備

「はみ出し」も「ぶれ」も2次元的な効果なので、そのための準備をしていきます。
サブネット、effectの中でまず初めに使用するノードは、
attribute wrangle(point)です。

//座標のZを退避
f@__orgZ = @P.z;
//Zに0を設定し、XY平面の2次元に。
@P = set(@P.x,@P.y,0);

XY平面の2次元に
image.png

角を検出するために、Facet SOPの、Remove Inline PointsをONにし、Distanceを角が抽出できる丁度良い値に調整してください。
image.png

Group SOPを使用し、抽出したポイント全てを含んだPoint Groupを生成します。
image.png
Group Transferを使用し、先ほどのPoint Groupを元の輪郭線に転写します。
image.png
次に、polyCut SOPを用い、検出した角のPoint Groupを使用して、カーブを分離します。
image.png
Sort SOPを使用し、By Vertex Orderでポイントの順番を揃えます。
image.png
Orient Along Curve SOPを使用し、接線のベクトルを求めます。
image.png
attribute wrangle(point)でZ座標を復元すれば、準備完了です。
orientalongcurveimage.png

「はみ出し」の表現

基本的に、Primitive(カーブ)毎のループ処理で、
カーブの最初(Point番号 0)と、最後(Point 番号npoints(0)-1)をBlast SOPで抽出し、
Copy To Point SOPでLine SOPで生成された線分をコピーし、Merge SOPを使用し元のカーブと合成。
join SOPで1つのカーブに結合しています。
image.png
あとは、これまで通りの処理を通すことで、「はみ出し」の表現が完成します。
image.png
image.png
image.png

「ぶれ」の表現

「ぶれ」の表現は、「はみ出し」の表現のループ処理の後半に3つノードを足して実装します。
まずは、Resample SOPでカーブを均等に分割します。
image.png
次に、平面的なノイズを与えるためのアトリビュートをattribute wrangle(point)で生成します。
Z軸に0を代入した、XZ平面の座標を生成します。
image.png

最後にノイズをAttribute Noise SOPで加えます。
Location Attributeには、先ほど生成したZ軸に0を代入した位置アトリビュートを。
残りのパラメーターは、AmplitudeとElementSizeを中心にお好みで設定してください。
Offsetを以下のように設定すると、カーブ毎にノイズの起点が変わるので自然になります。

point(0,0,'P',0)*100

image.png
これまで通りの処理を通すことで、「ぶれ」の表現も完成します。
image.png

「はみ出し」と「ぶれ」のある輪郭線の完成

image.png
image.png

輪郭線以外のエッジの生成

準備

輪郭線以外の、描画したいエッジのEdge Groupを今回は簡単に、Group From Attrib Boundary SOPでノーマルを参照し法線のハードエッジから生成します。
image.png
エッジの抽出は、dissolveで輪郭線を抽出した同じタイミングで、同じくdissolveで行います。
指定したEdge Groupから、輪郭線のEdge Groupを含まないもののみを残してエッジを消去します。
image.png
以降は、輪郭線と全く同じ処理を行うだけです。
エッジの太さや、ノイズのパラメーターを輪郭線の物と変えることで表情を豊かにすることが可能です。
image.png
これで、目的の、「はみ出し」と「ぶれ」がある輪郭線と、それ以外のスケッチスタイルエッジの生成ができました。
image.png

HDA化

この仕組みを簡単に再利用できるようにHDAにします。
今回は私が実装したHDAのUIを解説することにします。皆さんも、自身の使いやすい機能とUIを設計してみてください。
image.png
輪郭線の他に二種類の通常エッジを生成できるようにしました。

作例

基本的にHoudiniのビューポートのキャプチャ画像を色調整したものです。
少し前に実装された、Labsのphysical ambient occlusion SOPで元のモデルにグラデーションをつけています。
image
image

image
image

image
image

image
image

最後に

今回、ワールド座標空間とカメラ座標空間NDCを行き来する手法で、スケッチスタイルのエッジ描画を紹介しました。
座標系を行き来する事で、さまざまな効果(エフェクト)を実現する事が可能です。ちょっとしたアイデアでも大きな効果を得られると思います。
陰線処理をZ bufferに頼ってしまいましたが、陰線処理を高速に行う事ができれば、より表情豊かなスケッチスタイルのエッジを生成することができると思うので、チャレンジしてみたいと思います。また、今回は「線」だけの表現を実装しましたが、今後、「塗り」の表現も試していければと思います。

この記事が皆様の何かのお役に立てていただければ幸いです。それでは良いお年を。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2