ここからダウンロードできます。
ジオメトリの結合で動作を軽くする
3Dオブジェクトのジオメトリ(3D形状における頂点の座標群)が多いと動作が遅くなります。ひとつひとつに対してドローコールが発生するため、sceneオブジェクトに追加されているメッシュの個数が多いと動作が遅くなるのです。
解決法として、ジオメトリを結合させることでGPUに対するドローコールを減らすことができます。小さな3Dオブジェクトをひとつひとつ追加して表示するよりは、複数の三角形をまとめた巨大な3Dオブジェクトを1つだけにしたほうが負荷は少なくなるのです。
前回のOpenTKでつくった3DカーレースゲームをTypeScript/JavaScriptでもつくってみる(1)ではsceneオブジェクトに追加されたメッシュの個数が多すぎたため車1台では問題はありませんでしたが、3台に増やすと動作が重くなっていました。今回はジオメトリの結合で動作を軽くします。
ただジオメトリをまとめてしまうと、3Dオブジェクトとしては1つになるため、個別にマテリアルを設定することができなくなります。車の描画に使われている色の種類はそんなに多くないようなので、同じ色のものはひとつにまとめることにします。
同じ色のものだけジオメトリを結合する
TypeScriptで以下のような関数を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
function NewCreateCar() { let geometryTemp = null; // 3つの頂点座標を指定して三角形をつくる let meshTemp = null; // geometryTempからmeshTempをつくる let geometry = null; // geometryは複数のmeshTempを結合させたジオメトリ let material = null; // マテリアル let triangle = null; // geometryとmaterialからtriangleをつくる const group = new THREE.Group(); // 生成されたtriangleをグループに追加 { //@ts-ignore geometry = new THREE.Geometry(); geometryTemp = null; { { //@ts-ignore geometryTemp = new THREE.Geometry(); //頂点座標データを追加 geometryTemp.vertices.push(new THREE.Vector3(-0.2605618, 0.3918916, -2.293699)); geometryTemp.vertices.push(new THREE.Vector3(-0.3699993, 0.6465918, -2.2148)); geometryTemp.vertices.push(new THREE.Vector3(-0.1849993, 0.6465918, -2.2233)); //@ts-ignore geometryTemp.faces[0] = new THREE.Face3(0, 1, 2); meshTemp = new THREE.Mesh(geometryTemp); // メッシュを追加 geometry.mergeMesh(meshTemp); } { //@ts-ignore geometryTemp = new THREE.Geometry(); //頂点座標データを追加 geometryTemp.vertices.push(new THREE.Vector3(0.2605633, 0.3918916, -2.293699)); geometryTemp.vertices.push(new THREE.Vector3(0.1850007, 0.6465918, -2.2233)); geometryTemp.vertices.push(new THREE.Vector3(0.3700007, 0.6465918, -2.2148)); //@ts-ignore geometryTemp.faces[0] = new THREE.Face3(0, 1, 2); meshTemp = new THREE.Mesh(geometryTemp); geometry.mergeMesh(meshTemp); } // これを繰り返して同じ色のジオメトリをすべて結合させる } // 同じ色のジオメトリをすべて結合させたらマテリアルを作成 material = new THREE.MeshBasicMaterial({ color: 9722112 }); triangle = new THREE.Mesh(geometry, material); group.add(triangle); } // これを繰り返す // グループへの追加がすべて終わったらシーンに追加 scene.add(group); } |
コードをWindows.Formsアプリで取得する
コードの繰り返しの部分はWindows.FormsアプリでXMLファイルから頂点座標と色を取得して作成します。
XMLファイルはこれを使います。これはOpenTKでUnityのアセットは使えるのか?で作成したものです。
GetTrianglesメソッドで三角形を取得したら使用されている色でグループ分けをします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
StringBuilder sb = new StringBuilder(); List<Triangle> triangles = GetTriangles(); // 色でグループ分けする var groups = triangles.GroupBy(x => x.R * 256 * 256 + x.G * 256 + x.B); sb.Append("function NewCreateCar() {\n"); sb.Append(" const group = new THREE.Group();\n"); sb.Append(" let material = null;\n"); sb.Append(" let triangle = null;\n"); sb.Append(" let geometry = null;\n"); sb.Append(" let geometryTemp = null;\n"); sb.Append(" let meshTemp = null;\n"); string str = ""; int r = 0; int g = 0; int b = 0; foreach (var group in groups) { sb.Append("\n"); sb.Append(" {\n"); // geometryは複数のgeometryTempを結合させたジオメトリ sb.Append(" //@ts-ignore\n"); sb.Append(" geometry = new THREE.Geometry();\n"); sb.Append(" geometryTemp = null;\n"); sb.Append(" {\n"); // keyは色 var key = group.Key; foreach (Triangle triangle in group) { // マテリアルを作るときに色が必要なので取得しておく r = triangle.R; g = triangle.G; b = triangle.B; // geometryTempを作成 sb.Append(" //@ts-ignore\n"); sb.Append(" geometryTemp = new THREE.Geometry();\n"); sb.Append(""); // 3つの頂点を設定する sb.Append(" //頂点座標を追加\n"); // 1つめ str = String.Format( " geometryTemp.vertices.push(new THREE.Vector3({0}, {1}, {2}));// {3}, {4}, {5}\n", triangle.X0, triangle.Y0, triangle.Z0, triangle.R, triangle.G, triangle.B); sb.Append(str); // 2つめ str = String.Format( " geometryTemp.vertices.push(new THREE.Vector3({0}, {1}, {2}));\n", triangle.X1, triangle.Y1, triangle.Z1); sb.Append(str); // 3つめ str = String.Format( " geometryTemp.vertices.push(new THREE.Vector3({0}, {1}, {2}));\n", triangle.X2, triangle.Y2, triangle.Z2); sb.Append(str); sb.Append(" //@ts-ignore\n"); sb.Append(" geometryTemp.faces[0] = new THREE.Face3(0, 1, 2);\n"); sb.Append(" meshTemp = new THREE.Mesh(geometryTemp);\n"); // 同じ色である限り結合させていく sb.Append(" geometry.mergeMesh(meshTemp);\n"); } sb.Append(" }\n"); // 同じ色のジオメトリをすべて結合させたらマテリアルを作成 sb.Append(" //マテリアル(材質)の宣言と生成\n"); str = " material = new THREE.MeshBasicMaterial({ color: " + key.ToString() + " });\n"; sb.Append(str); // ジオメトリとマテリアルからメッシュを作成 sb.Append(" triangle = new THREE.Mesh(geometry, material);\n"); // 作成したメッシュをグループに追加 sb.Append(" group.add(triangle);\n"); sb.Append(" }\n"); sb.Append("\n"); } // グループへの追加がすべて終わったらシーンに追加 sb.Append(" scene.add(group);\n"); sb.Append(" return group;\n"); sb.Append("}\n"); // この文字列をTypeScriptに追加する richTextBox1.Text = sb.ToString(); |
実際に動かしてみる
実際に動かしてみるとこんな感じになります。車の種類と数を増やしても問題なく動いています。
ここからダウンロードできます。