assimpライブラリを使用して、x形式のオブジェクトをunity中へ読み込んでみたテストです。試してから1年以上放置していましたが、もったいないので公開します。
内房線アドオン同梱のTc208-2100f_Sotobo.x
を表示させた例を次図に示します。
- 実行環境
- unity
- 2022.2.14f1
- assimp
- 4.1.0
- https://intelligide.github.io/assimp-unity/installation を参考にしてインストール
- unity
使い方は下記のとおりです。
- 下記コードを適当なC#スクリプトとして保存する
start
関数中のfilepath
を読み込みたいxファイルのパスへ書き換える
- 空のオブジェクトを作成して、1で作成したスクリプトファイルをアタッチする
- 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);
}
}