やらなイカ?

たぶん、iOS/Androidアプリの開発・テスト関係。

PLATEAU 3D都市モデルの水ぜんぶ抜く大作戦

国土交通省 Project PLATEAU で、横浜市の3D都市モデルが公開されました。 このデータを使用する際、水面にあたる部分に面が張られており使いづらかったので*1Blenderで水面をすべて削除して、改めて水面用のメッシュを用意しました。 本記事はこの手順メモです。

3D都市データファイルをBlenderにインポート

今回使用したのは、横浜市(2020年度)のOBJデータです*2。 こちらをダウンロード・解凍して、目当てのエリアのデータをBlenderにインポートします。今回使用したのはBlender 2.93です。

www.geospatial.jp

Project PLATEAUのOBJデータは以下の4つに別れています。それぞれフォルダ内にエリアごとのファイルがありますが、今回は、みなとみらいエリアを含む 533915 のみ使用します。

  • bldg(建築物)
    • LOD1(全域)
    • LOD2(一部のみ)
  • dem(地形)
  • tran(道路)

なお、インポート時はTransformを Y Forward, Z Up に設定します。 また、原点が離れた位置に設定されているので移動しておくと便利です*3

今回加工するdemは次のように水面まですべて面が張られています。

f:id:nowsprinting:20210612010420p:plain

ランドマークタワー近辺を拡大すると、地面には細かく頂点があり、水面は地面のキワから対岸などに長い辺が伸びて面が張られていることがわかります。

f:id:nowsprinting:20210612010547p:plain

水面を削除する

地面と水面のキワにあたる頂点は、実際には測量された地面の端と思われ、このあたりでZ = 2m〜2.2mほどあります。 頂点の高さで水面を判断することは難しいので、「辺の長さが20m以上のものは水面」と判断して削除するPythonスクリプトを書いて処理します。

該当部分だけ抜粋すると次のコードです。

def expose_water_surface(target_pattern=r"^\d+_dem_\d+$", edge_length=20.0):

(snip)

    pattern = re.compile(target_pattern)
    for obj in bpy.context.scene.objects:
        if not pattern.match(obj.name):
            continue

(snip)

        for edge in mesh.edges:
            if edge.calc_length() >= edge_length:
                mesh.edges.remove(edge)

(snip)

これをBlenderのScriptsワークスペースで開き、実行します(詳しくは後述)。

処理後はこうなりました。左上のくぼみはドックヤードガーデンなので、削除されなくて正解です。

f:id:nowsprinting:20210612011159p:plain

この時点で俯瞰して見ると、海上に格子状に頂点が残っています。

f:id:nowsprinting:20210612012408p:plain

これはエリアの切れ目に置かれた頂点ですので、面積0の面および孤立した頂点というルールで削除します。 この処理は、次のブログ記事にあるスクリプトをほぼそのまま使用しました。

bluebirdofoz.hatenablog.com

削除後がこちら。まだ細かいゴミは残っていますが、自動処理はここまでとしました*4

f:id:nowsprinting:20210612012425p:plain

このデータをFBX形式でエクスポートしてUnityにインポート、clusterワールドとして公開したのがこちらです。水色は改めて追加した水面メッシュです*5

f:id:nowsprinting:20210612021746p:plain

cluster.mu

今回使用したスクリプトはこちらに公開しています。 Blender初心者の書いたコードなので無駄なステップがあるかもしれませんし事故を起こす恐れもあります。ご利用は自己責任で、バックアップを必ず取ってから使ってください。

github.com

Blender上でPythonスクリプトを実行する方法

以下は補足です。

Blender上でPythonスクリプトを実行するには、まずScriptingワークスペースを選択し、中央のエディタ上部にあるOpen Textアイコンで実行するPythonスクリプトを開きます。 次いで、Run Scriptアイコンで実行します。

f:id:nowsprinting:20210612014203p:plain

macOSの場合はGUI上にログが出ないため*6、実行ログを参照するためにはコマンドプロンプトからBlenderを実行する必要があります。

PyCharmでBlender向けスクリプトを書く

新規プロジェクト作成の際、Base interpreterにはBlenderが内包しているPythonを指定します。 Homebrewでインストールした場合、次のパスにあるはずです。

/Applications/Blender.app/Contents/Resources/2.90/python/bin/python3.7m

続いて、BlenderAPIであるbpyパッケージのFakeをインストールします。Fakeなので実行はできませんが、コード補完が効くようになります。

Preferences... | Python Interpreter を開き、 fake-bpy-module-x.xx を追加します。末尾はBlenderバージョンと合わせてください。

github.com

リダクションとFBXエクスポート設定

前述のように地表には約5m間隔で頂点が配置されており、特にみなとみらい地区のような平地では贅沢です。 今回は、demにのみDecimateモディファイアを追加、Ratioは0.01に設定して、△約3,000,000から30,000に削減しました*7

モディファイアを含めたFBXエクスポート設定は以下のようにしています。

  • Object Types: Meshのみ選択
  • Transform
    • Apply Scalings: FBX All
    • -Z Forward, Y Up(FBXのデフォルト)
  • Geometry
    • Apply Modifiers: on
  • Armature
  • Bake Animation: off

ファイルサイズは、モディファイアなしで約110MBだったところ、42.3MBになりました。

参考

bluebirdofoz.hatenablog.com

zenn.dev

*1:横浜市の3D都市モデルを基準にしています。他の都市にはあてはまらない可能性がありますのでご注意ください

*2:先に公開されていた東京都23区はFBX形式のデータが提供されていましたが、横浜市ではCityGMLとOBJのみ提供されています

*3:エリアの中央もしくはVRChat/clusterなどのワールドで使用するのであればスポーン地点など

*4:本来はここから温かみのある手作業で消していきますが、今回は手作業なしでフィニッシュしました

*5:正しく処理されたはずのドックヤードガーデンに水面が張られているのは、一律平面の水面メッシュを追加したためです。そのうち対処しなければ

*6:調べた範囲では

*7:元町方面を見るとだいぶカクカクしているのがわかります