Blenderで作ったメッシュをDirect3D12で描画してみる
Direct3D12で任意のメッシュを描画してみようと思い、まず、メッシュデータを作りました。
仕事ではMayaを使っていますが、今回は、Blenderでデータを作ってみます。 初めてなので、Kindle Unlimitedでこちらの書籍を読みながらロケットのメッシュを作りました。
シンプルで分かりやすい書籍だと思います。
次に、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できると思います。