Unityでモバイルでobjファイルを書き出したい人の最小コード

#はじめに
今回はUnityのモデルをランタイムにobj形式に変換して出力します。Editor上やPC上で動くものはUnityが提供してくれていたりするので、こちらではモバイル環境下での実装をしていきます。



#objについて簡単に
objについてはこちらに詳しく解説があるのでどうぞ。
https://yttm-work.jp/model_render/model_render_0001.html

Wavefront社が開発した3Dモデルフォーマットの１つです。
3Dモデルの中でも非常にわかりやすい構造をしているため3Dモデルを一から描画する時などの登竜門的な役割をしているとかなんとか。サブ的な知識も必要なので普通に難しいと思います。


●ファイル構成
1、.obj(v:頂点情報, vn:法線情報, vt:UV情報, f:面情報)
2、.mtl(テクスチャ情報, マテリアル情報等)
3、テクスチャ画像(.png, .jpgなど)

極端な話、3Dモデル描画ということであれば頂点と面情報だけでも描画できます。
今回はobj形式のコードを記述しておきます。


#実装方法(モバイル上で動作させたい)
実装方法はファイル構成に従ってファイルを生成し、そのファイルにObjフォーマットに従って記述していく流れになります。


```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class ObjExp : MonoBehaviour
{
    [SerializeField]
    private MeshFilter _obj;

    private string _storagePath;

    void Start()
    {
        //フォルダ生成
        _storagePath = Application.dataPath + "/" + "TestContents";
        Directory.CreateDirectory(_storagePath);

        //objファイル生成
        var _filePath = _storagePath + "/" + Application.productName + ".obj";
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("# OBJ File:" + Application.productName);
        sb.AppendLine("mtllib " + Application.productName + ".mtl");

        //頂点情報
        Vector3[] vertices = _obj.mesh.vertices;
        foreach (var child in vertices)
        {
            Vector3 v = child;
            v.x *= -1;
            sb.AppendLine("v " + v.x + " " + v.y + " " + v.z);
        }
        //法線情報
        Vector3[] vertnormal = _obj.mesh.normals;
        foreach (var child in vertnormal)
        {
            Vector3 v = child;
            v.x *= -1;
            sb.AppendLine("vn " + v.x + " " + v.y + " " + v.z);
        }
        //UV情報
        Vector2[] vertuvs = _obj.mesh.uv;
        foreach (var child in vertuvs)
        {
            sb.AppendLine("vt " + child.x + " " + child.y);
        }
        //面情報
        int faceOrder = (int)Mathf.Clamp((_obj.gameObject.transform.lossyScale.x * _obj.gameObject.transform.lossyScale.z), -1, 1);
        string matName = _obj.GetComponent<MeshRenderer>().sharedMaterial.name;
        sb.AppendLine("usemtl " + matName);

        int[] triangles = _obj.mesh.GetTriangles(i);
        for (int t = 0; t < triangles.Length; t += 3)
            {
                int p2 = triangles[t] + 1;
                int p1 = triangles[t + 1] + 1;
                int p0 = triangles[t + 2] + 1;
                if (faceOrder < 0)
                {
                    sb.AppendLine("f " + ConstructOBJString(p2) + " " + ConstructOBJString(p1) + " " + ConstructOBJString(p0));
                }
                else
                {
                    sb.AppendLine("f " + ConstructOBJString(p0) + " " + ConstructOBJString(p1) + " " + ConstructOBJString(p2));
                }
            }
            File.WriteAllText(_storagePath + "/" + Application.productName + ".obj", sb.ToString());
    }

    private string ConstructOBJString(int index)
    {
        string idxString = index.ToString();
        return idxString + "/" + idxString + "/" + idxString;
    }

}
```


#まとめ
今回SubMeshの情報やマテリアル情報を格納する(.mtl)ファイル生成は含めていません。
生成する際の位置、回転、スケールなども頂点に変更を加えて保存することでそのまま保存することも可能です。
取り敢えずの最低限の.Objファイル生成するためのコードでした。