アセットとは、Unityプログラミングにおいて用いるゲームの一部、 素材、部品の様な意味合いの言葉です。さてこれをC# OpenTKで使うことはできるのでしょうか?
よくある質問(FAQ)によると
アセットの全部または一部を Unity以外で使っても良いですか?
はい、結構です。なお、Unity以外でお使いの場合はアセットのパブリッシャーによるサポート範囲外となります。ご購入者の責任においてお使いください。
とのことです。では
Mobile Toon Cars Free | 3D 車両 | Unity Asset Store
をつかってみましょう。黄色い車を使います。
まず頂点の座標を知る必要があります。そのためにはこれを参考にしました。
Unityを起動してアセットをインポート。prefabをHierarchyビューにドラッグ&ドロップします。アタッチして以下を実行します。
Car_5というオブジェクトの子オブジェクトにそれぞれのパーツが入っており,それぞれにMeshFilterという頂点情報が乗ったメッシュがアタッチされています。取得するのは頂点インデックス、頂点の座標と法線ベクトル、UV座標です。
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 |
public class CarBehaviourScript1 : MonoBehaviour { void Start() { foreach (Transform child in transform) { MeshFilter meshFilter = child.GetComponent<MeshFilter>(); Mesh mesh = meshFilter.sharedMesh; string filename1 = string.Format("d:\\{0}.csv", meshFilter.sharedMesh.name); using (var sw = new System.IO.StreamWriter(filename1, false)) { int len = mesh.vertices.Length; for (int i = 0; i < len; ++i) { float x = child.localPosition.x; float y = child.localPosition.y; float z = child.localPosition.z; sw.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7}", mesh.vertices[i].x+ x, mesh.vertices[i].y+ y, mesh.vertices[i].z+ z, mesh.normals[i].x+ x, mesh.normals[i].y+ y, mesh.normals[i].z+ z, mesh.uv[i].x, mesh.uv[i].y); } } string filename2 = string.Format("d:\\{0}_index.csv", meshFilter.sharedMesh.name); using (var sw = new System.IO.StreamWriter(filename2, false)) { int len = mesh.triangles.Length; for (int i = 0; i < len; ++i) { sw.WriteLine("{0}", mesh.triangles[i]); } } } } void Update() { } } |
上記の方法で頂点の座標と法線ベクトル、UV座標を取得することができます。csvファイルを開いてみると取得できているのがわかります。
が、そのほかにテクスチャも取得しなければなりません。そうしないと色のついた車が描画されません。
テクスチャはAssets\Mobile Toon Cars Free\Models\Texturesのなかにあります。Shadow以外はPaletteTexture.pngです。ShadowはShadow.psdです。
では次にOpenTKで描画することにします。リソースにPaletteTexture.pngとShadow.psdはpngファイルに変換した物を追加します。
まず頂点情報を格納するクラスをつくります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class VertexInfo { public List<Vertex> Vertexes = new List<Vertex>(); public List<int> ints = new List<int>(); public string Name = ""; } public class Vertex { public float x = 0; public float y = 0; public float z = 0; public float nx = 0; public float ny = 0; public float nz = 0; public float u = 0; public float v = 0; } |
あとは上記コードでcsvファイルに保存したデータを呼び出してOpenTKで描画するだけです。
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); } List<int> Textures = null; private void glControlEx1_Load(object sender, EventArgs e) { GL.ClearColor(glControl.BackColor); // Projection の設定 SetProjection(); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); Textures = CreateTextures(); ReadCarFiles(); } private void SetProjection() { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定 GL.MatrixMode(MatrixMode.Projection); Matrix4 proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver3, glControl.AspectRatio, 0.5f, 100.0f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } protected List<int> CreateTextures() { List<int> textures = new List<int>(); GL.Enable(EnableCap.Texture2D); GL.AlphaFunc(AlphaFunction.Gequal, 0.5f); GL.Enable(EnableCap.AlphaTest); List<Bitmap> bitmaps = GetBitmaps(); foreach (Bitmap bitmap0 in bitmaps) { int texture; GL.GenTextures(1, out texture); textures.Add(texture); GL.BindTexture(TextureTarget.Texture2D, texture); bitmap0.RotateFlip(RotateFlipType.RotateNoneFlipY); System.Drawing.Imaging.BitmapData data = bitmap0.LockBits( new Rectangle(0, 0, bitmap0.Width, bitmap0.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); bitmap0.UnlockBits(data); GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); GL.BindTexture(TextureTarget.Texture2D, 0); } return textures; } protected virtual List<Bitmap> GetBitmaps() { List<Bitmap> bitmaps = new List<Bitmap>(); Bitmap bitmap1 = Properties.Resources.PaletteTexture; bitmaps.Add(bitmap1); Bitmap bitmap2 = Properties.Resources.Shadow; bitmaps.Add(bitmap2); return bitmaps; } List<VertexInfo> VertexInfos = new List<VertexInfo>(); void ReadCarFiles() { VertexInfo info = null; info = CreateVertexInfo("D:\\Body.csv", "D:\\Body_index.csv"); VertexInfos.Add(info); info = CreateVertexInfo("D:\\Shadow.csv", "D:\\Shadow_index.csv"); VertexInfos.Add(info); info = CreateVertexInfo("D:\\Wheel_Front_Left.csv", "D:\\Wheel_Front_Left_index.csv"); VertexInfos.Add(info); info = CreateVertexInfo("D:\\Wheel_Front_Right.csv", "D:\\Wheel_Front_Right_index.csv"); VertexInfos.Add(info); info = CreateVertexInfo("D:\\Wheel_Rear_Left.csv", "D:\\Wheel_Rear_Left_index.csv"); VertexInfos.Add(info); info = CreateVertexInfo("D:\\Wheel_Rear_Right.csv", "D:\\Wheel_Rear_Right_index.csv"); VertexInfos.Add(info); } VertexInfo CreateVertexInfo(string filePath1, string filePath2) { VertexInfo info = new VertexInfo(); System.IO.FileInfo fileInfo = new System.IO.FileInfo(filePath1); info.Name = fileInfo.Name; StreamReader reader = new StreamReader(filePath1); StreamReader reader_index = new StreamReader(filePath2); while (true) { string str = reader.ReadLine(); if (str == null) break; string[] vs = str.Split(new string[] { "," }, StringSplitOptions.None); float f0 = float.Parse(vs[0]); float f1 = float.Parse(vs[1]); float f2 = float.Parse(vs[2]); float f3 = float.Parse(vs[3]); float f4 = float.Parse(vs[4]); float f5 = float.Parse(vs[5]); float f6 = float.Parse(vs[6]); float f7 = float.Parse(vs[7]); info.Vertexes.Add(new Vertex() { x = f0, y = f1, z = f2, nx = f3, ny = f4, nz = f5, u = f6, v = f7 }); } while (true) { string str = reader_index.ReadLine(); if (str == null) break; info.ints.Add(int.Parse(str)); } reader_index.Close(); reader.Close(); return info; } private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.ClearColor(glControl.BackColor); SetSight(); Lighting(); Draw(); glControl.SwapBuffers(); } void SetSight() { float x = 0f; float y = 2f; float z = 4f; // 視界の設定 Vector3 eye = new Vector3(x, y, z); Matrix4 look = Matrix4.LookAt(eye, Vector3.Zero, Vector3.UnitY); GL.LoadMatrix(ref look); // 法線の自動調節 GL.Enable(EnableCap.Normalize); } void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); // ライト 0 の設定と使用 float[] position = new float[] { 1.0f, 1.0f, 1.0f, 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); float[] ambientColor = new float[] { 0.1f, 0.1f, 0.1f, 0.1f }; GL.Light(LightName.Light0, LightParameter.Ambient, ambientColor); GL.Light(LightName.Light0, LightParameter.Diffuse, Color.White); GL.Enable(EnableCap.Light0); } float X = 0; float Y = 0; float Z = 0; float rotate = 0; void Draw() { GL.PushMatrix(); { GL.Translate(X, Y, Z); GL.Rotate(rotate, 0, 1, 0); DrawCar(); } GL.PopMatrix(); } void DrawCar() { foreach (VertexInfo info in VertexInfos) { if (info.Name != "Shadow.csv") GL.BindTexture(TextureTarget.Texture2D, Textures[0]); else GL.BindTexture(TextureTarget.Texture2D, Textures[1]); GL.Begin(BeginMode.Triangles); { foreach (int i in info.ints) { GL.TexCoord2(info.Vertexes[i].u, info.Vertexes[i].v); GL.Normal3(new Vector3(info.Vertexes[i].nx, info.Vertexes[i].ny, info.Vertexes[i].nz)); GL.Vertex3(info.Vertexes[i].x, info.Vertexes[i].y, info.Vertexes[i].z); } } GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); } } private void glControl_Resize(object sender, EventArgs e) { SetProjection(); // 再描画 glControl.Refresh(); } protected override bool ProcessDialogKey(Keys keyData) { //キーの本来の処理を //させたくないときは、trueを返す if((keyData & Keys.KeyCode) == Keys.Left) { if(checkBoxTranslate.Checked) X -= 0.1f; if (checkBoxRotate.Checked) rotate -= 5; glControl.Refresh(); return true; } else if((keyData & Keys.KeyCode) == Keys.Right) { if (checkBoxTranslate.Checked) X += 0.1f; if (checkBoxRotate.Checked) rotate += 5; glControl.Refresh(); return true; } else if((keyData & Keys.KeyCode) == Keys.Up) { if (checkBoxTranslate.Checked) Z -= 0.1f; glControl.Refresh(); return true; } else if((keyData & Keys.KeyCode) == Keys.Down) { if (checkBoxTranslate.Checked) Z += 0.1f; glControl.Refresh(); return true; } return base.ProcessDialogKey(keyData); } } |
ところでUnity側でXMLファイルに保存してしまったほうがOpenTK側で使いやすいかもしれません。
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 |
using System.IO; using System.Xml.Serialization; public class CarData { public CarData() { } public List<VertexInfo> VertexInfos = new List<VertexInfo>(); } public class VertexInfo { public VertexInfo() { } public List<Vertex> Vertexes = new List<Vertex>(); public List<int> ints = new List<int>(); public string Name = ""; public float TranslateX = 0; public float TranslateY = 0; public float TranslateZ = 0; } public class Vertex { public Vertex() { } public float x = 0; public float y = 0; public float z = 0; public float nx = 0; public float ny = 0; public float nz = 0; public float u = 0; public float v = 0; } public class CarBehaviourScript1 : MonoBehaviour { void Start() { CarData carData = new CarData(); foreach (Transform child in transform) { MeshFilter meshFilter = child.GetComponent<MeshFilter>(); Mesh mesh = meshFilter.sharedMesh; VertexInfo vertexInfo = new VertexInfo(); carData.VertexInfos.Add(vertexInfo); vertexInfo.Name = child.name; vertexInfo.TranslateX = child.localPosition.x; vertexInfo.TranslateY = child.localPosition.y; vertexInfo.TranslateZ = child.localPosition.z; int len = mesh.vertexCount; var vertices = mesh.vertices; var normals = mesh.normals; var uv = mesh.uv; for (int i = 0; i < len; ++i) { Vertex vertex = new Vertex() { x = vertices[i].x, y = vertices[i].y, z = vertices[i].z, nx = normals[i].x, ny = normals[i].y, nz = normals[i].z, u = uv[i].x, v = uv[i].y, }; vertexInfo.Vertexes.Add(vertex); } int[] vs = mesh.triangles; int len1 = mesh.triangles.Length; for (int i = 0; i < len1; ++i) { vertexInfo.ints.Add(vs[i]); } } XmlSerializer xml = new XmlSerializer(typeof(CarData)); StreamWriter sw = new StreamWriter("D:\\CarData1.xml"); xml.Serialize(sw, carData); sw.Close(); } void Update() { } } |
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 |
using System.IO; using System.Xml.Serialization; public partial class Form1 : Form { CarData carData = null; void ReadCarFiles() { XmlSerializer xml = new XmlSerializer(typeof(CarData)); StreamReader sr = new StreamReader("D:\\CarData1.xml"); carData = (CarData)xml.Deserialize(sr); sr.Close(); } void DrawCar() { foreach (VertexInfo info in carData.VertexInfos) { if (info.Name != "Shadow") GL.BindTexture(TextureTarget.Texture2D, Textures[0]); else GL.BindTexture(TextureTarget.Texture2D, Textures[1]); GL.Enable(EnableCap.Normalize); GL.PushMatrix(); { GL.Translate(info.TranslateX, info.TranslateY, info.TranslateZ); GL.Begin(BeginMode.Triangles); { foreach (int i in info.ints) { GL.TexCoord2(info.Vertexes[i].u, info.Vertexes[i].v); GL.Normal3(new Vector3(info.Vertexes[i].nx, info.Vertexes[i].ny, info.Vertexes[i].nz)); GL.Vertex3(info.Vertexes[i].x, info.Vertexes[i].y, info.Vertexes[i].z); } } GL.End(); } GL.PopMatrix(); GL.BindTexture(TextureTarget.Texture2D, 0); } } // その他のメソッドは上記のForm1クラスと同じ } |
OpenTK側で追加するクラスとして以下があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class CarData { public List<VertexInfo> VertexInfos = new List<VertexInfo>(); } public class VertexInfo { public List<Vertex> Vertexes = new List<Vertex>(); public List<int> ints = new List<int>(); public string Name = ""; // 以下の3つを追加 public float TranslateX = 0; public float TranslateY = 0; public float TranslateZ = 0; } |
タイヤを回転させる処理をするかもしれないので座標を完全に一体化させずにフィールド変数を追加しました。このほうが回転処理のあとどれだけ平行移動させるかがわかりやすくなります。
Unityちゃんを使って同じことをしたいのだができるだろうか?