3DCADモデルの見せたくない内側をアレする話―中身の詰まった3DモデルをBlenderでサクッとハリボテ化する手順―

さて、様々な大人の事情で、3DCADモデルを中身スカスカのハリボテにしたいときに使えるテクニックの話です。

たとえば、内部構造は内緒だけど外形のデータだけ渡したい、ゲームエンジンで扱うためにメッシュ削減したい、NFTとして販売したい、などなど、さまざまな用途に使えるんじゃないかと思います。

ひらめいたアイディアをPoC的に実装してみたらけっこううまく行ったので、もっと良いアイディアが浮かぶまではこれを使っていこうかなと。

予備知識

面コーナーカラー

  • 「面コーナーカラー」は、3DCGでポリゴンの面の各コーナー(角)ごとに割り当てられる色情報のこと。頂点カラーと違って、同じ頂点でも面ごとに異なる色を設定でき、より複雑なシェーディングやグラデーション効果などに活用される。

ベイク

  • 複雑な計算結果(ライティング、影、反射、法線マップなど)をテクスチャや頂点カラーなどに固定化(焼き付け)するプロセス。これにより、リアルタイムレンダリングの負荷が軽減され、効率よく表示できるようになる。

アイディア

3DCADデータのうち、外から見える形状だけを残して中身を削除したい

→外から見える = 外から光で照らした時に光が当たる形状ってことでは?

しくみ

全天球の照明をメッシュの面コーナーにベイクする。

→影になった面(ループの面コーナーカラーがすべて黒)は外から見えない形状と判断して削除する。

手順

※Blender4.2.1 LTSで動作確認しています。

① ワールドのサーフェスを「放射」にセット,強さを1,000〜10,000くらいにする。(最適値がわかりませんが、とにかくすごく明るくする)

② モデルをインポートして、データ→カラー属性に以下を追加する。(すでにマテリアルや頂点カラーが設定されたら先に削除してから)

  • 名前:カラー
  • ドメイン:面コーナー
  • データタイプ:カラー
  • カラー:白

③ レンダーをCyclesにセット、デバイスはGPU演算推奨

④ Cyclesのレンダーの最大サンプル数をとりあえず10にセットする。(後述する方法でベイク結果を確認して、余計なところに影が出るようであれば100くらいに増やすと良くなるかも)

④ レンダー→ベイク→出力を「アクティブカラー属性」にセットしてベイク

⑤ 下のPythonスクリプトを実行して、ループの面コーナーがすべて黒い面を削除する

④〜⑤ を数回繰り返すとよさそう

大きいメッシュを処理する場合は結構時間がかかるので、フリーズしてるように見えても気長に待ちましょう。

システムコンソールを表示してから実行するとPrint処理のぶん遅くなるとは思いますが、進捗が見えて精神衛生には良いです。

import bpy

def delete_faces_based_on_vertex_colors():
    obj = bpy.context.active_object
    if not obj or obj.type != 'MESH':
        print("メッシュオブジェクトを選択してください。")
        return

    mesh = obj.data
    if "カラー" not in mesh.attributes:
        print("オブジェクトのデータ→カラー属性に面カラーを追加してください。")
        return

    color_layer = mesh.attributes["カラー"].data

    initial_face_count = len(mesh.polygons)
    print(f"Initial face count: {initial_face_count}")

    faces_to_delete = []

    for poly in mesh.polygons:
        # デフォルトはフラグTrue
        delete_face = True
        r_values = []

        for loop_index in poly.loop_indices:
            color = color_layer[loop_index].color
            r_values.append(color[0])
            # print(f"Face {poly.index}, Loop {loop_index}, R value: {color[0]}")

            # 黒〜濃グレー以外はフラグをFalseに
            if color[0] > 1:
                delete_face = False

        if delete_face:
            faces_to_delete.append(poly.index)
            print(f"Face {poly.index} del")
        else:
            print(f"Face {poly.index} not del")

    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='DESELECT')
    bpy.ops.object.mode_set(mode='OBJECT')

    # 点選択モードだと余計な面も選択されちゃうので面選択モードに切り替える
    bpy.context.tool_settings.mesh_select_mode = (False, False, True)
    for face_index in faces_to_delete:
        mesh.polygons[face_index].select = True

    bpy.ops.object.mode_set(mode='EDIT')

    # オブジェクト分割するならこっち、デバッグに適しています
    bpy.ops.mesh.delete(type='SELECTED')

    # すぐに削除するならこっち
    #bpy.ops.mesh.delete(type='FACE')

    bpy.ops.object.mode_set(mode='OBJECT')

    # 結果を表示
    final_face_count = len(mesh.polygons)
    print(f"Deleted {initial_face_count - final_face_count} faces.")
    print(f"Final face count: {final_face_count}")

def main():
    delete_faces_based_on_vertex_colors()

if __name__ == "__main__":
    main()

使ってみた結果

例のごとく大人の事情でモデルはお見せできませんが、手元にあったCADデータで試してみました。

元がが794万Triだったモデルから、外からの見た目はそのままで608万Tri削減、なんと186万Triまで落とすことができました! ※「Tri」は三角面の数の単位です

さらに処理前、処理後のモデルをSTLで保存してみると、 前:1.9GB → 後:90MB

すごい気がする、これは使える...!