チュートリアル 7 : ファサード モデリング

チュートリアル データ

チュートリアル データは、[Help] メニュー → [Download Tutorials and Examples…] を選択し、[CityEngine Tutorial] からダウンロードできます。

概要

このチュートリアルでは、写真から建物をモデリングする方法を解説し、いくつかの複雑な CGA のテクニックを紹介します。最初のパートでは、CGA ルールでどのようにファサード(建物の前面)の基本構造を作成するかを学習します。

演習
Part 1 :ファサードの構造をモデリングする
Part 2 :アセットの挿入
Part 3 :ファサードへのテクスチャ貼り付け

Part 1 :ファサードの構造をモデリングする

チュートリアルのセットアップ

  1. プロジェクト Tutorial_07_Facade_Modeling を自分の CityEngine ワークスペースにインポートします。

  2. シーン Tutorial_07_Facade_Modeling/scenes/FacadeModeling_01.cej を開きます。

ファサード モデリング

このチュートリアルでは、実際に撮影した写真からどのようにファサードを再現する一連の CGA を記述するかを説明します。これからモデリングするファサードは以下に示すものです。ルールセットの作成が進むにつれて、より詳細に写真を解析していくことになります。

どのように実際のファサードを解析し、その構造を CGA グラマールールに翻訳していくか、そしてどのように既存のモデルアセットを CGA ルールに使用するかを学習します。

ルール ファイルの作成

  1. [File] メニュー → [New] ...[CityEngine][CGA Rule File] を選択して、新規ルール ファイルを作成します。

  2. Container がTutorial_07_Facade_Modeling プロジェクトの rules フォルダーに設定されていることを確認し、ファイル名を myFacade_01.cga として Finish をクリックします。

新しいCGAルール ファイルが作成され、CGA Rule Editor ウィンドウが起動します。いくつかのヘッダー情報 (/** .… */) とバージョン タグ version "2021.1" 以外は何もない状態です。

ボリュームとファサード

これから建物を作成します。まず押し出し操作によりマスモデルを作成します。建物の height 属性を使用します。

  1. ルール ファイルの最初に属性 height を追加します。
attr height 			= 24
  1. スタートルールを extrude コマンドを使用して記述し、その結果のシェープを Building とします。
Lot --> extrude(height) Building
  1. この例では、ファサードのみが必要なので、component split で前面以外のすべての面を除去し、次の Frontfacade を呼び出します。
Building --> comp(f) { front : Frontfacade}

属性は @Group や @Range のようなオプションのアノテーションを持つことができ、これにより Inspector での属性の表示を制御します。CGA アノテーションの詳細については、[Help] メニュー → [CityEngine Help] の [CGA Rule Reference] を参照してください。

フロア

前面ファサードは水平方向に分割されて、それぞれfloor_height(フロアの高さ)の属性を持つフロアになります。Floor シェープはフロアのインデックスである split.index によってパラメーターで制御されます。このパラメーターは下位のルールに渡され、特定のフロアに対して何のエレメントを作成するかを決定します。

  1. 最上階についてはフロア インデックスとして 999 を割り当てます。このフロアを簡単に見分けられるようにするためです。

    中間階に対しては split を繰り返すことに注意してください。これは、様々な高さの建物に動的に対応させ、残りの高さ方向のスペースを中間階で埋めるためです。

  2. ここでもフロアのサイズについての属性をいくつか定義します。

attr groundfloor_height 	= 5.5
attr floor_height 		= 4.5
  1. そして以下のルールを追加します。
Frontfacade -->
    split(y){ groundfloor_height    : Floor(split.index) // 地上フロア
        | floor_height : Floor(split.index) // 第一フロア(二階)
        | floor_height : Floor(split.index) // 第二フロア(三階)
        | {~floor_height : Floor(split.index)}* // 中間階
        | floor_height : Floor(999) // 最上階、インデックスは999
        | 0.5 : s('1,'1,0.3) LedgeAsset} //屋根直下のトップレッジ

それでは最初のファサード生成を行いましょう。

  1. [3D View] ウィンドウにて区画を選択します。

  2. ルール ファイルを割り当てます。[Shapes] メニュー → [Assign Rule File ...] から、作成した CGA ファイル (myFacade_01.cga) を選択して [OK] をクリックします。

  3. ツールバーにある [Generate] ボタン をクリックします(または Ctrl-g キーを押します)。
    結果は下の画像のようになります。

フロア レッジ(水平方向の張り出し)

各フロアをレッジとタイル シェープに分割します。ボトムレッジはフロアによって異なるため、このルールに対して floorindex パラメーターを使用します。

  • 地上階(floorindex 0)にはレッジはないため、Tiles のみを呼び出します。

  • 二番目のフロア(Floor 2)については、窓領域がフロアの一番下の高さから開始されるため、ボトムレッジはありません。このフロアのバルコニーは後のステップで考慮します。

  • それ以外のフロアにはボトムレッジ、タイルおよびトップレッジ領域があります。

Floor(floorindex) -->
    case floorindex == 0 :
        Subfloor(floorindex)
    case floorindex == 2 :
        split(y){~1 : Subfloor(floorindex) | 0.5 : TopLedge}
    else :
        split(y){1 : BottomLedge(floorindex) | ~1 : Subfloor(floorindex) | 0.5 : TopLedge}

サブフロア

サブフロアは各フロアの左右端に位置する小さな壁領域とそれらの間のタイル(同形の矩形領域の意)の繰り返しからなります。

  1. 先頭に新しい属性を追加します。
attr tile_width 		= 3.1

さらに以下のルールを追加します。

Subfloor(floorindex) -->
	split(x){ 0.5 : Wall(1) 
			| { ~tile_width : Tile(floorindex) }* 
			| 0.5 : Wall(1) }

ここではパラメーター化された Wall シェープを追加しました。これは後のステップでファサードにテクスチャを貼り付ける際に重要となります。ファサードの写真を見ると、3つの異なる壁タイプがあることが分かります。

  • 暗いブロックのダートテクスチャ

  • 明るいブロックのダートテクスチャ

  • ダートテクスチャのみ。これは主にブロック構造を持たないファサードアセットに対して必要となります。

以下のルールに見られるように、ここでは各々の壁のスタイルは全く同じ出力となります。後のステップで壁タイプごとに異なるテクスチャを追加することになります。

先頭に新しい属性を追加します。

attr wallColor          = "#ffffff"

Subfloor ルールの下に以下のルールを追加します。

Wall(walltype) -->
	// dark bricks with dirt
	case walltype == 1 :
		color(wallColor)
	// bright bricks with dirt	
	case walltype == 2 :
		color(wallColor)
	// dirt only	
	else :
		color(wallColor)

タイル

このファサードのタイルは均質になっています。ここで地上フロアのタイルとそれより上の階のタイルのみを区別する必要があります。

属性 door_width と window_width を使用して異なる分割サイズを設定します。

attr door_width		= 2.1	
attr window_width		= 1.4

Tile(floorindex) -->
	case floorindex == 0 :
		split(x){ ~1 : SolidWall 
			|  door_width : DoorTile
			| ~1 : SolidWall }		 
	else : 	
		split(x){ ~1 : Wall(getWalltype(floorindex))
			|  window_width : WindowTile(floorindex)
			| ~1 : Wall(getWalltype(floorindex)) }

この時点ではルールはまだ完成していないので、エラーが出ます。コードを完成させるとエラーは消えますので、続けて次のステップへ進んでください。

地上フロアのタイルでは、新しいシェープ SolidWall が追加されます。これは、地上フロアのドアがファサードからめり込んだ状態になっているために必要になります。ドアと壁の間に穴ができるのを防ぐために、特定の厚さを持った立体を挿入することにより、壁エレメントを作ります。この厚さは後で Door ルールでも使用するため、これを定数変数 wall_inset として定義します。複数回使用される値を宣言しておくと、同じ値が別のルールで使用されることを確認できることにもなり、便利です。

const wall_inset = 0.4

SolidWall -->
		s('1,'1,wall_inset) t(0,0,-wall_inset)
		i("builtin:cube:notex")	
		Wall(1)

フロア インデックスから壁のタイプを取得する関数を定義します。ファサードを見ると、地上フロアおよび一番目のフロア(二階)は暗いテクスチャ、それ以外には明るいテクスチャとなっているのが分かります。関数 getWallstyle はフロア インデックスを対応する壁タイプに割り当てます。

getWalltype(floorindex) = 
	case floorindex == 0 : 1
	case floorindex == 1 : 1
	else : 2

Rules フォルダーの façade_02.cga ルール ファイルを適用すると、模範例を確認できます。

次の Part 2 では、このファサードにアセットを使用する方法を学習します。

Part 2 :アセットの挿入

ファサード モデリング チュートリアルの2番目のパートでは、ファサードに予めモデリングされたアセットを使用する方法について学習します。

  1. シーンファイル Tutorial_07_Facade_Modeling/scenes/FacadeModeling_02.cej を開きます。

  2. CGA ファイル Tutorial_07_Facade_Modeling/rules/facade_02.cga を開きます。

アセット

現在モデリングしているファサードの写真を見ると、以下の項目についてアセットが必要であることが分かります。

  • 窓: 窓エレメントとして使用

  • 丸い窓上部: 窓上部の装飾

  • 三角の窓上部: 窓上部の装飾

  • 半円弧: 地上フロアのアーチとして使用

  • レッジ: すべてのレッジとして使用

  • モディリオン: 窓や地上フロアのアーチの装飾として使用

これらのアセットはすでにチュートリアルプロジェクトの asset フォルダーに含まれています。 [Navigator] ウィンドウで目的のアセット(今回はtriangle_windotop.obj)を選択するとこれらのアセットを [Inspector] ウィンドウでプレビューすることができます(プレビュー画面が無い場合は[Navigator] ウィンドウの [show file preview] ボタンをクリックしてください)。

アセットの宣言

すべてのアセットの宣言を同じ場所で行っておくことをお勧めします。したがって、以下の行をルール ファイルの属性の宣言の下に追加します。

const window_asset 		= "facades/elem.window.frame.obj"
const round_wintop_asset 	= "facades/round_windowtop.obj"
const tri_wintop_asset 		= "facades/triangle_windowtop.obj"
const halfarc_asset 		= "facades/arc_thin.obj"
const ledge_asset 		= 
                           "facades/ledge.03.twopart_lessprojection.obj"
const modillion_asset 		=
             "facades/ledge_modillion.03.for_cornice_ledge_closed.lod0.obj"

  1. ここまでの作業で、窓アセットを配置する場所についてのルールは構築済みですので、WindowTile ルールの中で Window シェープを呼び出すだけです。
WindowTile(floorindex) --> Window
  1. そして次のルールを追加して窓アセットとその背後のガラス面をサイズ調整、配置、挿入します。
Window -->
	s('1,'1,0.2) t(0,0,-0.2)
	t(0,0,0.02)
	[ i(window_asset) Wall(0) ]
	Glass

窓の装飾

再度ファサードの写真を見てみると、実際はもっと複雑であることが分かります。フロアが異なると窓(または窓エレメント)が異なります。

WindowTile ルールとそれをトリガーするシェープをフロア インデックスで固有になるように拡張する必要があります。

  • 第一フロア(二階)と最上階には特別な装飾はないため、Window のみが呼び出されます。

  • 第二フロア(三階)には新しいシェープ WindowOrnamentRound を追加します。このエレメントは Window の上の境界線に接しなければならないため、現在のスコープの上向きを '1 により y 軸に読み替えます。

  • 他の窓タイル(中間階)も同様に Y に沿って読み替えた上で装飾 WindowOrnamentTriangle を取得し、さらに追加の WindowLedge シェープを取得します。

WindowTile(floorindex) -->
	case floorindex == 1 || floorindex == 999: Window
	case floorindex == 2 : Window t(0,'1,0) WindowOrnamentRound
	else : Window WindowLedge t(0,'1,0) WindowOrnamentTriangle

最終的なアセットを直接使用する代わりに、まずは代理となる立方体(キューブ)を試しに挿入します。こうすると実際のアセットのサイズをより設定し易くなります。ここではビルト イン(はめ込み)のキューブ アセットを使用します。

サイズを設定し、位置をスコープの x 軸方向の中央に設定し、キューブを挿入し、さらに見やすいように色を付けます。

WindowOrnamentTriangle --> 
	s('1.7, 1.2, 0.3) center(x) i("builtin:cube")  color("#ff0000") 
        # 三角窓エレメントのサイズを設定して挿入
WindowOrnamentRound -->
	s('1.7, 1.2, 0.4) center(x) i("builtin:cube") color("#00ff00")
WindowLedge -->
	s('1.5, 0.2, 0.1) t(0,-0.2,0) center(x) i("builtin:cube")
color("#0000ff")

代理アセットを実際のアセットに交換

窓装飾のサイズは問題なさそうであることが分かりましたので、実際のアセットをキューブの代わりに挿入します。 WindowLedge については、まだキューブのままにしておきます。

WindowOrnamentTriangle --> 
	s('1.7, 1.2, 0.3) center(x) i(tri_wintop_asset)
WindowOrnamentRound -->
	s('1.7, 1.2, 0.4) center(x) i(round_wintop_asset)

第二フロアの窓についてはまだ完了していません。円形の窓装飾にはサイドピラー(側柱)が付いていません。そこで、先に作成した WindowOrnamentRound ルールに対して新たに split コマンドを追加します。この行により、以下のモディリオン アセット用のスコープが準備されることになります。

WindowOrnamentRound -->
		s('1.7, 1.2, 0.4) center(x) i(round_wintop_asset) Wall(0)
		split(x){~1 : WindowMod | window_width : NIL | ~1 : WindowMod }

さらにサイズを設定し、モディリオン アセットを挿入します。相対的に y 方向をマイナスにする変換 ('-1) が適用され、アセットの上面が装飾の下面に一致していることに注意してください。

WindowMod -->
	s(0.2,'1.3,'0.6) t(0,'-1,0) center(x) i(modillion_asset) Wall(0)

ドア

ドアタイルは垂直方向に分割されて、ドア、アーチおよび上部エリアに分かれます。

楕円形にならないようにするために、アーチ部の高さはドアの幅(現在の x スコープ)の半分である必要があります。

DoorTile -->
	split(y){~1 : Door | scope.sx/2 : Arcs | 0.5 : Arctop}

上部エリアでは、壁エレメントとその上に重なったモディリオン アセットが挿入されます。

# 壁素材と中央に配置したモディリオンを追加
Arctop -->
	Wall(1)
	s(0.5,'1,0.3) center(x) i(modillion_asset) Wall(1)

アーチ エリアはさらに分割され、2 つのアーチ アセットが挿入されます。ここで、先に定義した変数 wall_inset を使用します。アーチの右半分を回転して正しい向きになるようにする必要があります。

Arcs -->  
	s('1,'1,wall_inset) t(0,0,-wall_inset)
	Doortop
	i("builtin:cube")
	split(x){ ~1 : ArcAsset 
			| ~1 : r(scopeCenter,0,0,-90) ArcAsset}

最後に、Doortop と Door に Wall シェープをセットして実際のアーチ アセットを挿入します。

Doortop -->	Wall(0)
		
Door --> t(0,0,-wall_inset) Wall(0)

ArcAsset --> i(halfarc_asset) Wall(1)

レッジ

続いてはレッジです。ここではトップレッジ用およびボトムレッジ用のルールが必要です。トップレッジにはシンプルな壁のストライプを使用し、ボトムレッジにはレッジ アセットを挿入して他のフロアとの区別がつくようにする必要があります。

TopLedge --> WallStripe 

BottomLedge(floorindex) -->
	case floorindex == 1 : split(y){~1 : Wall(0) | ~1 : s('1,'1,0.2) LedgeAsset}
	case floorindex == 999 : split(y){~1 : WallStripe | ~1 : s('1,'1,0.2) LedgeAsset}
	else : WallStripe

WallStripe --> split(x){ 0.5 : Wall(1) | ~1 : Wall(2) | 0.5 : Wall(1) } 

LedgeAsset --> i(ledge_asset) Wall(0)

バルコニー

次はバルコニーです。

  1. 再度 Floor ルールに注目して第二フロアの case 文に Balcony シェープを追加します。
case floorindex == 2 : 
		split(y){~1 : Subfloor(floorindex) Balcony | 0.5 : TopLedge}
  1. ここでも最初はシンプルな代理アセットを使用してバルコニーの配置やサイズをチェックします。
Balcony --> 
	s('1,2,1) t(0,-0.3,0) i("builtin:cube") color("#99ff55")
  1. バルコニー ボックスは 3つの要素:桁(Beam)、床(Floor)、手すり(Railing)に分割されます。
Balcony --> 
	s('1,2,1) t(0,-0.3,0) i("builtin:cube") 
	split(y){0.2 : BalconyBeams 
			| 0.3 : BalconyFloor 
			| 1 : RailingBox }
  1. バルコニーを支える桁は split の繰り返しにより作成します。
BalconyBeams -->			
	split(x){ ~0.4 : s(0.2,'1,'0.9) center(x)  Wall(0) }*

BalconyFloor シェープは Wall ルールのみをトリガーします。

BalconyFloor --> Wall(0)
  1. RailingBox に対して component split を使用し、バルコニーの手すりとして使用する面を抽出します。
# RailingBox シェープの前、左、右要素を取得
RailingBox -->
	comp(f){front : Rail | left : Rail | right : Rail}
  1. 最後に、バルコニーの手すりとなるキューブのサイズを決定し挿入します。
Rail --> 
	 s('1.1,'1,0.1) t(0,0,-0.1) center(x) i("builtin:cube") Wall(0)

ジオメトリ アセットによるファサードの完成形

これで最終的なモデルができましたので、このルールを別の区画シェープに適用したり、[Inspector] ウィンドウにある属性を変更してファサードのデザインを変えたりして遊んでみましょう。

次の Part 3 では、このファサードにテクスチャを貼り付ける方法を学習します。

Part 3 :ファサードへのテクスチャ貼り付け

このパートではここまでに作成したファサードへテクスチャを貼り付けるテクニックを紹介します。

  1. シーン ファイル Tutorial_07_Facade_Modeling/scenes/FacadeModeling_03.cej を開きます。

  2. CGA ファイル Tutorial_07_Facade_Modeling/rules/facade_03.cga を開きます。

テクスチャ アセット

  1. ルール ファイルの先頭に、属性として使用するテクスチャを追加します。
const wall_tex				= "facades/textures/brickwall.jpg"
const wall2_tex				= "facades/textures/brickwall_bright.jpg"
const dirt_tex				= "facades/textures/dirtmap.15.tif"
const doortop_tex			= "facades/textures/doortoptex.jpg"
  1. 窓またはドアのテクスチャについてはテクスチャ文字列を取得する関数を使用するため、すべてのテクスチャを個別に並べる必要はありません。
randomWindowTex = fileRandom("*facades/textures/window.*.jpg")
randomDoorTex = fileRandom("*facades/textures/doortex.*.jpg")

グローバル UV 座標の設定

シェープ グラマーでのテクスチャリングは次の 3 つのコマンドで行われます。

  • setupProjection(): uv 座標空間を定義します。

  • set(material.map): テクスチャ ファイルを設定します。

  • projectUV(): UV 座標を適用します。

ここでは、ブロック テクスチャとダート テクスチャの 2 つのテクスチャ レイヤーをファサードに追加します。ファサード全体で一貫したテクスチャ座標としたいので、Facade ルールの中で UV セットアップを行う必要があります。前もってテクスチャリング設定をテストできるようにするために、新たに中間的なルールFrontfacadeTex を追加します。

  1. Building ルールを次のように変更します。
Building --> comp(f) { front : FrontfacadeTex}
  1. 新しいルールを作成します。
FrontfacadeTex -->
	setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1) 	
	setupProjection(2, scope.xy, '1, '1) 	
	Frontfacade

setupProjection (0, scope.xy, 2.25, 1.5, 1) はテクスチャのチャンネル 0 (色チャンネル)に対してテクスチャ座標を定義します。UV 座標はスコープの xy 平面に投影され、x 方向に 2.25 単位ごとに、y 方向に 1.5 単位ごとに繰り返されます。カラーマップを "builtin:uvtest.png" にセットします。これは UV 座標をすばやくチェックするためのテクスチャです。最後に UV 座標をチャンネル 0 に焼きこんで UV 座標を適用します。

  1. UV 設定を確認するためにファサードを生成してみます。
  1. それでは、ダート チャンネル用の UV セットアップを追加しましょう。
FrontfacadeTex -->
	setupProjection(0, scope.xy, 2.25, 1.5,  0, 0, 1) 
	texture("builtin:uvtest.png")  
	projectUV(0)
	
	setupProjection(2, scope.xy, '1, '1) 	
	set(material.dirtmap, "builtin:uvtest.png")  
	projectUV(2)

このテクスチャはファサード全体にわたって適用されなければなりません。したがって相対オペレーター '1 および '1 を UV セットアップに使用します。これはファサードと同じサイズになります。

  1. 再度ファサードを生成すると以下のような結果になります。
  1. 実際のテクスチャでファサードがどのように見えるか興味があるなら、ビルト インの uvtest テクスチャを実際のものと入れ替えてください。
FrontfacadeTex -->
	setupProjection(0, scope.xy, 2.25, 1.5,  0, 0, 1) 
	texture(wall_tex)  
	projectUV(0)
	
	setupProjection(2, scope.xy, '1, '1) 	
	set(material.dirtmap, dirt_tex)  
	projectUV(2)
ファサード上でカラーおよびダート UV をテストした状態
左: ブロック テクスチャ; 中央: ダート テクスチャ; 右: ブロックとダート テクスチャの合成

このファサードに対して UV 座標は問題ないようです。

  1. 実際の建物に対しては、この時点では UV セットアップ以外は必要ないため、 FrontfacadeTex ルールを以下のように書き換えます。
FrontfacadeTex -->
	setupProjection(0, scope.xy, 2.25, 1.5,  0, 0, 1) 		
	setupProjection(2, scope.xy, '1, '1) 	
	Frontfacade

これで元々の Frontfacade ルールに以降の要素で使用する UV 座標のセットアップが正しく付与されました。

壁へのテクスチャリング

Wall ルールにどのようなタイプ パラメーターを追加したか覚えていますか?ここでやっとその恩恵を受けることができます。異なるテクスチャを持つ3つの壁タイプを作成したいと思います。

  1. Wall ルールを以下のように変更します。
Wall(walltype) -->
	// dark bricks with dirt
	case walltype == 1 :
		color(wallColor)
		texture(wall_tex)  
		set(material.dirtmap, dirt_tex)
		projectUV(0) projectUV(2)
	// bright bricks with dirt	
	case walltype == 2 :
		color(wallColor)
		texture(wall2_tex)  
		set(material.dirtmap, dirt_tex)
		projectUV(0) projectUV(2)
	// dirt only	
	else :
		color(wallColor)
		set(material.dirtmap, dirt_tex)
		projectUV(2)

このようにすべての要素にテクスチャが付与されました。

窓アセットへのテクスチャリング

窓アセットに対しては、窓テクスチャのセットを使用してガラス面を色付けしたいと思います。そのためには UV 座標をガラスシェープ全体にわたって適用されるようにする必要がありますが、これは x および y 方向に '1 を使用することで可能です。

  1. テクスチャとして、以前定義した randomWindowTex 関数を呼び出して使用します。
Glass -->
	setupProjection(0,scope.xy, '1, '1)
	projectUV(0)
	texture(randomWindowTex)
  1. さらにガラスに反射効果を与えます。
Glass -->
	setupProjection(0,scope.xy, '1, '1)
	projectUV(0)
	texture(randomWindowTex) 
	set(material.specular.r, 1) set(material.specular.g, 1)   
      set(material.specular.b, 1)
	set(material.shininess, 4)

ドアシェープへのテクスチャリング

ドア面は窓ガラスと同様にテクスチャリングします。

Doortop -->
	setupProjection(0, scope.xy, '1, '1)
	texture(doortop_tex) 
	projectUV(0)
Door -->
	t(0,0,-wall_inset)
	setupProjection(0,scope.xy, '1, '1)
	texture(randomDoorTex) 
	projectUV(0)