Blenderで作ったメッシュをDirect3D12で描画してみる

Direct3D12で任意のメッシュを描画してみようと思い、まず、メッシュデータを作りました。

仕事ではMayaを使っていますが、今回は、Blenderでデータを作ってみます。 初めてなので、Kindle Unlimitedでこちらの書籍を読みながらロケットのメッシュを作りました。

https://amzn.to/4fcLOOz

シンプルで分かりやすい書籍だと思います。

次に、BlenderのデータをどうやってC++に持ってくるか、考えました。 今回、必要なデータは、頂点データとインデックスデータの2種類となります。(マテリアルは後回しです。)

Blenderのデータをエクスポートするとした場合、fbx、usd、glTFのフォーマットが使えると思います。 それぞれ、pythonのAPIも提供されているので、必要なデータを取得できます。 しかし、それぞれ、pythonで頂点データをダンプしてみたところ、思ったよりも面倒でした。 もう少し使いやすいフォーマットはないのですかね。

なので、勉強もかねて、BlenderのPythonAPIを使って、直接データを出力してみました。

import bpy
import os
import json

# 出力ファイル名をフルパスで
output_file = "mesh_data.json"

def append_vertex_list(vertex_list, vertex):
    # 既に同じ頂点が存在する場合、そのインデックスを返す
    if vertex in vertex_list:
        return vertex_list.index(vertex)
    # 新しくインデックスを追加
    vertex_list.append(vertex)
    return len(vertex_list) - 1


json_data = {}
# シーン内のすべてのオブジェクトをループ
for obj in bpy.context.scene.objects:
    # メッシュオブジェクトのみを対象とする
    if obj.type == "MESH":
        vertex_list = []
        triangle_list = []
        for poly in obj.data.polygons:
            local_triangle_list = []
            for i in range(2, len(poly.vertices)):
                local_triangle_list.append((poly.vertices[0], poly.vertices[i - 1], poly.vertices[i]))

            for tr in local_triangle_list:
                for i in range(3):
                    vertex = {}
                    vertex["position"] = tuple(obj.data.vertices[tr[i]].co)
                    vertex["normal"] = tuple(obj.data.vertices[tr[i]].normal)
                    index = append_vertex_list(vertex_list, vertex)
                    triangle_list.append(index)

        json_data[obj.name] = {"vertices": vertex_list, "indices": triangle_list}

with open(output_file, "w") as f:
    json.dump(json_data, f, indent=4)

json フォーマットで出力したので、シンプルに書くことができました。

8頂点のキューブを出力すると、このようなデータが出力できます。

{
    "Cube": {
        "vertices": [
            {
                "position": [
                    1.0,
                    1.0,
                    1.0
                ],
                "normal": [
                    0.5773502588272095,
                    0.5773502588272095,
                    0.5773502588272095
                ]
            },
            {
                "position": [
                    -1.0,
                    1.0,
                    1.0
                ],
                "normal": [
                    -0.5773502588272095,
                    0.5773502588272095,
                    0.5773502588272095
                ]
            },
            {
                "position": [
                    -1.0,
                    -1.0,
                    1.0
                ],
                "normal": [
                    -0.5773502588272095,
                    -0.5773502588272095,
                    0.5773502588272095
                ]
            },
            {
                "position": [
                    1.0,
                    -1.0,
                    1.0
                ],
                "normal": [
                    0.5773502588272095,
                    -0.5773502588272095,
                    0.5773502588272095
                ]
            },
            {
                "position": [
                    1.0,
                    -1.0,
                    -1.0
                ],
                "normal": [
                    0.5773502588272095,
                    -0.5773502588272095,
                    -0.5773502588272095
                ]
            },
            {
                "position": [
                    -1.0,
                    -1.0,
                    -1.0
                ],
                "normal": [
                    -0.5773502588272095,
                    -0.5773502588272095,
                    -0.5773502588272095
                ]
            },
            {
                "position": [
                    -1.0,
                    1.0,
                    -1.0
                ],
                "normal": [
                    -0.5773502588272095,
                    0.5773502588272095,
                    -0.5773502588272095
                ]
            },
            {
                "position": [
                    1.0,
                    1.0,
                    -1.0
                ],
                "normal": [
                    0.5773502588272095,
                    0.5773502588272095,
                    -0.5773502588272095
                ]
            }
        ],
        "indices": [
            0,
            1,
            2,
            0,
            2,
            3,
            4,
            3,
            2,
            4,
            2,
            5,
            5,
            2,
            1,
            5,
            1,
            6,
            6,
            7,
            4,
            6,
            4,
            5,
            7,
            0,
            3,
            7,
            3,
            4,
            6,
            1,
            0,
            6,
            0,
            7
        ]
    }
}

次に、このデータをC++で読み込みます。jsonの読み込み部分はこんな感じです。

#include <nlohmann/json.hpp>

	static void JsonToFloat3(const nlohmann::json& j, float3& v) {
        v.x = j.at(0).get<float>();
        v.y = j.at(1).get<float>();
        v.z = j.at(2).get<float>();
	}

	bool LoadMeshData(const char* filename, ID3D12Device* device)
	{
		std::ifstream file(filename);
		if (!file.is_open()) {
			std::cerr << "ファイルを開けません: " << filename << std::endl;
			return false;
		}
		// JSONデータをパース
		nlohmann::json file_json;
		file >> file_json;

        // メッシュデータを読み込む
        size_t mesh_num = std::distance(file_json.begin(), file_json.end());
        m_data.reserve(mesh_num);
        for (auto& [mesh_name, mesh_json] : file_json.items()) {

            MeshData mesh;

            // 頂点データを読み込む
            for (const auto& vertex_json : mesh_json["vertices"]) {
                VertexData vertex;
                JsonToFloat3(vertex_json["position"], vertex.position);
                JsonToFloat3(vertex_json["normal"], vertex.normal);
                mesh.PushBackVertex(vertex);
            }

            // インデックスデータを読み込む
            auto index_list = mesh_json["indices"].get<std::vector<int>>();
            mesh.SetIndexList(index_list);

            // データ追加
            m_data.push_back(mesh);

            // バッファの生成
            m_data[m_data.size() - 1].CreateBuffer(device);
        }

		return true;
	}

nlohmann.jsonはVisualStudioのメニューからnugetでインストールできます。

これで、先ほどのロケットのメッシュを描画するとこんな感じです。

マテリアルというかシェーダーが適当ですが、とりあえず、任意のメッシュを描画するまではできました。

なお、先ほどのコードは、トランスフォームの情報を出力していませんので、頂点データはFreezeしてから使ってください。 Blenderの場合は、オブジェクト→適用→全トランスフォームで、Freezeできると思います。