x形式オブジェクトをunityで表示する試み

assimpライブラリを使用して、x形式のオブジェクトをunity中へ読み込んでみたテストです。試してから1年以上放置していましたが、もったいないので公開します。

内房線アドオン同梱のTc208-2100f_Sotobo.x を表示させた例を次図に示します。

使い方は下記のとおりです。

  1. 下記コードを適当なC#スクリプトとして保存する
    • start関数中のfilepathを読み込みたいxファイルのパスへ書き換える
  2. 空のオブジェクトを作成して、1で作成したスクリプトファイルをアタッチする
  3. playすると、2で作成したオブジェクトがfilepathのxオブジェクトに置き換えられる

注意:テクスチャファイルはdds(DXT1)フォーマットのみ対応しています。png, bmpには未対応です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Assimp;
using System.IO;

public class Assimp2 : MonoBehaviour
{
    public struct DdsHeader
    {
        public System.String dwMagic;
        public System.UInt32 dwSize;
        public System.UInt32 dwFlags;
        public System.UInt32 dwHeight;
        public System.UInt32 dwWidth;
        public System.UInt32 dwDepth;
        public System.UInt32 dwPitchOrLinearSize;
        public System.UInt32 dwMipMapCount;
        public System.UInt32 dwPfSize;
        public System.UInt32 dwPfFlags;
        public System.String dwFourCC;
        public System.UInt32 dwRGBBitCount;
        public System.UInt32 dwAlphaBitMask;
        public System.UInt32 dwCaps;
        public System.UInt32 dwCaps2;
    }
    DdsHeader readDdsHeader(byte[] bytes)
    {
        int pointer = 0;
        DdsHeader header = new DdsHeader();
        System.Text.Encoding encascii = System.Text.Encoding.ASCII;
        if (System.BitConverter.ToUInt32(bytes, pointer) == 0x20534444)
        {
            header.dwMagic = encascii.GetString(bytes, pointer, 4);
            pointer += 4;
            header.dwSize = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwFlags = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwHeight = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwWidth = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwPitchOrLinearSize = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwDepth = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwMipMapCount = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            pointer += 4 * 11;
            header.dwPfSize = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwPfFlags = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwFourCC = encascii.GetString(bytes, pointer, 4);
            pointer += 4;
            header.dwRGBBitCount = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwAlphaBitMask = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwCaps = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;
            header.dwCaps2 = System.BitConverter.ToUInt32(bytes, pointer);
            pointer += 4;

            Debug.LogFormat("dwMagic: {0}", header.dwMagic);
            Debug.LogFormat("dwSize: {0}", header.dwSize);
            Debug.LogFormat("dwFlags: {0}", header.dwFlags);
            Debug.LogFormat("dwHeight: {0}", header.dwHeight);
            Debug.LogFormat("dwWidth: {0}", header.dwWidth);
            Debug.LogFormat("dwPitchOrLinearSize: {0}", header.dwPitchOrLinearSize);
            Debug.LogFormat("dwDepth: {0}", header.dwDepth);
            Debug.LogFormat("dwMipMapCount: {0}", header.dwMipMapCount);
            Debug.LogFormat("dwPfSize: {0}", header.dwPfSize);
            Debug.LogFormat("dwPfFlags: {0}", header.dwPfFlags);
            Debug.LogFormat("dwFourCC: {0}", header.dwFourCC);
            Debug.LogFormat("dwRGBBitCount: {0}", header.dwRGBBitCount);
            Debug.LogFormat("dwRGBAlphaBitMask: {0}", header.dwAlphaBitMask);
            Debug.LogFormat("dwCaps: {0}", header.dwCaps);
            Debug.LogFormat("dwCaps2: {0}", header.dwCaps2);
        }
        return header;
    }
    // Start is called before the first frame update
    void Start()
    {
        byte[] readPngFile(string path)
        {
            using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                BinaryReader bin = new BinaryReader(fileStream);
                byte[] values = bin.ReadBytes((int)bin.BaseStream.Length);
                Debug.LogFormat("{0}, {1}", path, bin.BaseStream.Length);
                bin.Close();
                return values;
            }

        }
        Texture readByBinary(byte[] bytes)
        {

            DdsHeader header = readDdsHeader(bytes);
            if (header.dwFourCC == "DXT1")
            {
                Debug.LogFormat("load {0}, ({1},{2})", header.dwFourCC, (int)header.dwWidth, (int)header.dwHeight);
                Texture2D texture = new Texture2D((int)header.dwWidth, (int)header.dwHeight, TextureFormat.DXT1, false);
                byte[] texData = new byte[bytes.Length - 128];
                Debug.LogFormat("len: {0}, {1}", bytes.Length - 128, texData.Length);
                System.Buffer.BlockCopy(bytes, 128, texData, 0, bytes.Length - 128);
                Debug.LogFormat("{0}", texData[texData.Length - 1]);

                texture.LoadRawTextureData(texData);
                texture.Apply();
                Texture2D flippedTexture = new Texture2D(texture.width, texture.height);
                for (int y = 0; y < flippedTexture.height; y++)
                {
                    for (int x = 0; x < flippedTexture.width; x++)
                    {
                        flippedTexture.SetPixel(x, flippedTexture.height - y, texture.GetPixel(x, y));
                    }
                }
                flippedTexture.Apply();
                return flippedTexture;
            }
            else
            {
                Debug.LogFormat("load {0}, ({1},{2})", header.dwFourCC, (int)header.dwWidth, (int)header.dwHeight);
                Texture2D texture = new Texture2D((int)header.dwWidth, (int)header.dwHeight, TextureFormat.DXT5, false);

                byte[] texData = new byte[bytes.Length - 128];
                Debug.LogFormat("len: {0}, {1}", bytes.Length - 128, texData.Length);
                System.Buffer.BlockCopy(bytes, 128, texData, 0, bytes.Length - 128);
                Debug.LogFormat("{0}", texData[texData.Length - 1]);

                texture.LoadRawTextureData(texData);
                texture.Apply();

                Texture2D flippedTexture = new Texture2D(texture.width, texture.height);
                for (int y = 0; y < flippedTexture.height; y++)
                {
                    for (int x = 0; x < flippedTexture.width; x++)
                    {
                        flippedTexture.SetPixel(x, flippedTexture.height - y, texture.GetPixel(x, y));
                    }
                }
                flippedTexture.Apply();
                return flippedTexture;
            }

        }

        // https://rikoubou.hatenablog.com/entry/2016/02/01/212504
        // https://answers.unity.com/questions/555984/can-you-load-dds-textures-during-runtime.html

        Texture loadImageformBynary(byte[] bytes)
        {
            Texture2D texture = new Texture2D(4, 4);
            texture.LoadImage(bytes);
            return texture;
        }


        AssimpContext importer = new AssimpContext();
        string filePath = "hogehoge"; // 読み込みたいxファイルへのパスを指定
        Scene scene = importer.ImportFile(filePath,
            PostProcessSteps.MakeLeftHanded |
            PostProcessSteps.FlipWindingOrder |
            PostProcessSteps.Triangulate);

        List<UnityEngine.Material> matelialList = new List<UnityEngine.Material>();


        Debug.LogFormat("MaterialCount: {0}", scene.MaterialCount);

        foreach (Assimp.Material mat in scene.Materials)
        {
            UnityEngine.Material unityMatelial = new UnityEngine.Material(Shader.Find("Standard"));

            Debug.LogFormat("ColorDiffuse: {0},{1},{2},{3}", mat.ColorDiffuse[0], mat.ColorDiffuse[1], mat.ColorDiffuse[2], mat.ColorDiffuse[3]);
            Debug.LogFormat("Shininess: {0}", mat.Shininess);
            Debug.LogFormat("ColorSpecular: {0},{1},{2},{3}", mat.ColorSpecular[0], mat.ColorSpecular[1], mat.ColorSpecular[2], mat.ColorSpecular[3]);
            Debug.LogFormat("ColorEmissive: {0},{1},{2},{3}", mat.ColorEmissive[0], mat.ColorEmissive[1], mat.ColorEmissive[2], mat.ColorEmissive[3]);
            if (mat.HasTextureDiffuse)
            {
                Debug.LogFormat("TextureFilePath: {0}", mat.TextureDiffuse.FilePath);
                unityMatelial.mainTexture = readByBinary(readPngFile(Path.Combine(Path.GetDirectoryName(filePath), mat.TextureDiffuse.FilePath)));
                unityMatelial.shader = Shader.Find("Standard");

                unityMatelial.SetOverrideTag("RenderType", "TransparentCutout");
                unityMatelial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                unityMatelial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                unityMatelial.SetInt("_ZWrite", 1);
                unityMatelial.EnableKeyword("_ALPHATEST_ON");
                unityMatelial.DisableKeyword("_ALPHABLEND_ON");
                unityMatelial.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                unityMatelial.renderQueue = 2450;
                //https://programming.sincoston.com/unity-transparent-image/
                //https://qiita.com/polikeiji/items/e56febcfdf886524352c

            }

            unityMatelial.color = new Color(mat.ColorDiffuse[0], mat.ColorDiffuse[1], mat.ColorDiffuse[2]);


            matelialList.Add(unityMatelial);
        }

        UnityEngine.Material mate = new UnityEngine.Material(Shader.Find("Standard"));

        MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>();
        meshRenderer.sharedMaterials = matelialList.ToArray();




        MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();

        submesh(scene, meshFilter);

    }

    // Update is called once per frame
    void Update()
    {

    }

    void submesh(Scene scene, MeshFilter meshFilter)
    {
        List<Vector3> verts_u = new List<Vector3>();
        List<int> tris_u = new List<int>();
        List<Vector3> norms_u = new List<Vector3>();
        List<Vector2> uvs_u = new List<Vector2>();
        UnityEngine.Mesh mesh = new UnityEngine.Mesh();

        List<int> subMeshVertList = new List<int>();
        Dictionary<int, List<int>> subMeshVertDict = new Dictionary<int, List<int>>();
        Dictionary<int, int> subMeshMaterialDict = new Dictionary<int, int>();
        int subMeshVertIndex = 0;
        int subMeshIndex = 0;

        foreach (Assimp.Mesh m in scene.Meshes)
        {


            List<Vector3D> verts = m.Vertices;
            List<Vector3D> norms = (m.HasNormals) ? m.Normals : null;
            List<Vector3D> uvs = m.HasTextureCoords(0) ? m.TextureCoordinateChannels[0] : null;

            List<Face> faces = m.Faces;
            List<int> tris = new List<int>();

            subMeshVertList.Add(subMeshVertIndex);
            subMeshMaterialDict.Add(subMeshIndex, m.MaterialIndex);

            Debug.LogFormat("verts: {0}", verts.Count);
            Debug.LogFormat("norms: {0}", norms.Count);
            Debug.LogFormat("uvs: {0}", uvs.Count);
            Debug.LogFormat("faces: {0}", faces.Count);
            Debug.LogFormat("MaterialIndex: {0}", m.MaterialIndex);

            for (int i = 0; i < verts.Count; i++)
            {
                verts_u.Add(new Vector3(verts[i][0], verts[i][1], verts[i][2]));
            }

            for (int i = 0; i < norms.Count; i++)
            {
                norms_u.Add(new Vector3(norms[i][0], norms[i][1], norms[i][2]));
            }

            for (int i = 0; i < uvs.Count; i++)
            {
                uvs_u.Add(new Vector2(uvs[i][0], uvs[i][1]));
            }


            for (int i = 0; i < faces.Count; i++)
            {
                for (int j = 0; j < faces[i].IndexCount; j++)
                {
                    tris_u.Add(faces[i].Indices[j] + subMeshVertIndex);
                    tris.Add(faces[i].Indices[j] + subMeshVertIndex);
                }
            }


            subMeshVertDict.Add(subMeshIndex, tris);
            subMeshVertIndex += verts.Count;
            subMeshIndex += 1;

        }

        Debug.LogFormat("subMeshVertDict.Count: {0}", subMeshVertDict.Count);

        mesh.SetVertices(verts_u.ToArray());
        mesh.SetNormals(norms_u.ToArray());
        mesh.SetUVs(0, uvs_u.ToArray());

        mesh.subMeshCount = subMeshVertDict.Count;

        foreach (int i in subMeshVertDict.Keys)
        {
            mesh.SetTriangles(subMeshVertDict[i], i);
            Debug.LogFormat("{0}", i);
        }


        meshFilter.mesh = mesh;
    }

    void combineMesh(Scene scene, MeshFilter meshFilter)
    {
        // https://ekulabo.com/unity-mesh-material-combine

        CombineInstance[] combineInstanceAry = new CombineInstance[(int)scene.MeshCount];

        Debug.LogFormat("meshes: {0}", scene.MeshCount);

        int meshnumber = 0;

        foreach (Assimp.Mesh m in scene.Meshes)
        {
            List<Vector3D> verts = m.Vertices;
            List<Vector3D> norms = (m.HasNormals) ? m.Normals : null;
            List<Vector3D> uvs = m.HasTextureCoords(0) ? m.TextureCoordinateChannels[0] : null;

            List<Vector3> verts_u = new List<Vector3>();
            List<int> tris_u = new List<int>();
            List<Vector3> norms_u = new List<Vector3>();
            List<Vector2> uvs_u = new List<Vector2>();

            UnityEngine.Mesh mesh = new UnityEngine.Mesh();

            //Debug.LogFormat("vertslen: {0}", verts.Count);

            for (int i = 0; i < verts.Count; i++)
            {
                verts_u.Add(new Vector3(verts[i][0], verts[i][1], verts[i][2]));
            }

            for (int i = 0; i < norms.Count; i++)
            {
                norms_u.Add(new Vector3(norms[i][0], norms[i][1], norms[i][2]));
            }

            for (int i = 0; i < uvs.Count; i++)
            {
                uvs_u.Add(new Vector2(uvs[i][0], uvs[i][1]));
            }

            List<Face> faces = m.Faces;

            for (int i = 0; i < faces.Count; i++)
            {

                for (int j = 0; j < faces[i].IndexCount; j++)
                {
                    tris_u.Add(faces[i].Indices[j]);
                }
            }


            mesh.vertices = verts_u.ToArray();
            mesh.triangles = tris_u.ToArray();
            mesh.normals = norms_u.ToArray();
            mesh.uv = uvs_u.ToArray();
            mesh.triangles = tris_u.ToArray();



            combineInstanceAry[meshnumber].mesh = mesh;
            combineInstanceAry[meshnumber].transform = UnityEngine.Matrix4x4.Translate(new Vector3(0, 0, 0));
            meshnumber++;
        }

        Debug.LogFormat("{0}", meshnumber);
        var combinedMesh = new UnityEngine.Mesh();
        combinedMesh.name = "test";
        combinedMesh.CombineMeshes(combineInstanceAry);
        meshFilter.mesh = combinedMesh;

        Debug.LogFormat("{0}", combinedMesh.subMeshCount);
    }
}

コメントをどうぞ

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください