0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Godot Engineでメッシュに関するいくつかのTips

Last updated at Posted at 2024-10-24

はじめに

今回は比較的単発なテクニックを試したのでそれを公開したい。

UnityとGodotの違い

Tipsを紹介する前に、今回のTipsに関わるUnityとGodotでの決定的な違いについて、簡単に触れておきたい。

3Dモデル(オブジェクト)の表示・非表示に関するレイヤーの場所が違う

UnityでVRMを読み込むと次のようなヒエラルキー構造になる。

image.png

UnityではGameObjectがレイヤーを持っており、どの位置でもレイヤーを切り替えて好きな目的で使用することができる。

一方、GodotでVRMを読み込むと次のようになる。

image.png

たとえばVRM自身のノードには、プロパティのどこにもレイヤーはない。そもそもUnityとGodotでは基本的な構造が違うから仕方ない。

単に表示・非表示するだけならshow()とかそういう感じのメソッドを使えばいいだけだが、レイヤー分けして管理したり、優先度をつけて表示するときには困りそうだ。

GodotではMeshInstance3Dのレイヤーを使う

MeshInstance3DはVisualInstance3Dを継承しており、そのクラスには layers プロパティがある。

image.png

この layers プロパティはプロジェクトの設定からもレイヤー名を変えられるようになっている。どうやらこれがUnityのGameObjectのlayerに相当するものと考えてよさそうだ。

CSGPrimitive3Dを継承する基本的な図形も、当然このVisualInstance3Dを継承しているので layers は存在するのがわかる。

GodotのノードというのはUnityのGameObject(とComponent)と違って、一つのノードに複数の機能をもたせる考え方ではないようだ。
つまり、機能単位でノード・子ノードの構成を作る必要がある。

  • 3Dモデルの表示に関わるノード
  • 3Dモデルのアニメーションの再生に関わるノード
  • 3Dモデルの衝突に関するノード

逆に言うと、特定の操作をするなら取得すべきノードが自ずと決まってくるので、わかりやすいかもしれない。

VisualInstance3Dを継承するクラスでしか通用しない点に注意。
ノードを追加する際のウィンドウでNode3D - VisualInstance3D のツリーを開けばどのクラスがこの layers を使えるのかざっと確認できるだろう。

image.png

Tips

さて、違いを踏まえた上で、今回の記事のメインに進む。

3Dモデルのメッシュのレイヤーを一度に変える

前述の通り、Unityならば親のGameObjectのレイヤーを切り替えるだけで済んだが、Godotではそうも行かない。

VisualInstance3Dを継承するクラスを型に持つノード全部の layers を切り替える

test.gd
func set_layer_value(layer:int):
    return 1 << (layer - 1)

func recur_set_layer(layer: int, targets: Node):
	var children = targets.get_children(true)
	for child in children:
		if child is MeshInstance3D:
			(child as MeshInstance3D).layers = set_layer_value(3) #設定したいレイヤー番号
		recur_set_layer(layer, child)

再帰処理でMeshInstance3Dを型に持つノードに対して layers を設定してあげればいい。

これで例えばVRMを特定のレイヤーに表示して、別のレイヤーの3Dモデルと重ねて表示などができるようになる。

ただメッシュの部位によってレイヤーを変えたいとかそういう追加機能をつけたい場合、渡す引数を工夫する必要がある。

3Dモデルの全体の大きさを取得する

ファイルサイズではなく、3Dモデルを構成するメッシュの端から端までのつまりBoundingのサイズのことである。
このテクニックはなにもGodotだけではなく、Unityでもよくあるものだろう。

公式マニュアルでは次のページだ。

MeshInstance3D
そしてGodotではBoundingなクラスがAABBだ。
AABB

ぶっちゃけ、Claude先生に教わって作ったので、AABBとかギャグかなと思う名前のクラスがあることにも驚いた。

test.gd
func get_total_aabb(targets) -> AABB:
	var total_aabb = AABB()
	var first = true
	
	# 全てのMeshInstance3Dを再帰的に検索
	var allmeshes = get_all_mesh_instances(targets)
	for child: MeshInstance3D in allmeshes:
		var local_transform = child.get_global_transform()
		var mesh_aabb = child.get_aabb()
		
		# メッシュのAABBをグローバル座標系に変換
		var global_aabb = local_transform * mesh_aabb

		if first:
			total_aabb = global_aabb
			first = false
		else:
			# AABBを結合
			total_aabb = total_aabb.merge(global_aabb)
	
	return total_aabb

func get_all_mesh_instances(node: Node) -> Array:
    """
    子・孫階層すべてのMeshInstance3Dをひとまとめに取得する
    """
	var mesh_instances = []

	if node is MeshInstance3D:
		mesh_instances.append(node)

	for child in node.get_children():
		mesh_instances.append_array(get_all_mesh_instances(child))

	return mesh_instances

func any_function():
    ...
    get_total_aabb(vrm)
    ...

get_total_aabb がメインのメソッドである。
その中では受け渡された最初のノードを起点とし、 get_all_mesh_instances メソッドで再帰処理にてMeshInstance3Dを探し出し、配列にしている。

そしてMeshInstance3Dの配列に対して get_aabb() メソッドでAABB型のデータを取得し、Transformを使ってグローバル座標のAABBに変換し、それをひたすら merge() メソッドで結合していく。

printで出力して確認

AABBにはpositionとsizeがある。Sがおそらくsizeだろう。
出力された値と、Unityで作った現行のVRMViewMeisterを使い、身長として表示した値を比べてみた。

image.png

positionを考慮する必要があるのかもしれない。

1.77005 - 0.13554 = 1.634496

sizeをpositionで減算したところ大体同じ値となった。Unityで算出した値とGodotの今回のテクニックで取得した値、どうやら問題なさそうだ。
(若干の誤差は気にしないでおく)

おわりに

自分にとって必要なテクニックを発見したので、覚え書きとして今回の記事にまとめた次第である。
この記事が皆様のお役に立てれば幸いだ。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?