Skip to content

Instantly share code, notes, and snippets.

@Lesliehdezg
Forked from supachailllpay/ UnityObjectLoader
Created February 20, 2018 17:10
Show Gist options
  • Select an option

  • Save Lesliehdezg/0a0ec2f0f9eef2914cfea19894230826 to your computer and use it in GitHub Desktop.

Select an option

Save Lesliehdezg/0a0ec2f0f9eef2914cfea19894230826 to your computer and use it in GitHub Desktop.
Load .obj and .mtl in Unity at runtime
Load .obj and .mtl in Unity at runtime
using UnityEngine;
using System.Collections;
public class Manager : MonoBehaviour {
public GameObject model;
void Start () {
ObjectLoader loader = model.AddComponent <ObjectLoader> ();
loader.Load (@"/model/path/", "model.obj");
}
}
/*
* The .obj and .mtl files must follow Wavefront OBJ Specifications.
*
* OBJ Supported List
*
* Vertex Data
* - v Geometric vertices (not support w)
* - vt Texture vertices (not support w)
* - vn Vertex normals
*
* Elements
* - f Face (only support triangulate faces)
*
* Grouping
* - o Object name
*
* Display
* - mtllib Material library (not support multiple files)
* - usemtl Material name
*
* MTL Supported List
*
* Material Name
* - newmtl Material name
*
* Texture Map
* - map_Kd Texture file is linked to the diffuse (not support options)
*/
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FileReader {
public struct ObjectFile {
public string o;
public string mtllib;
public List<string> usemtl;
public List<Vector3> v;
public List<Vector3> vn;
public List<Vector2> vt;
public List<List<int[]>> f;
}
public struct MaterialFile {
public List<string> newmtl;
public List<string> mapKd;
}
public static ObjectFile ReadObjectFile (string path) {
ObjectFile obj = new ObjectFile ();
string[] lines = File.ReadAllLines (path);
obj.usemtl = new List<string> ();
obj.v = new List<Vector3> ();
obj.vn = new List<Vector3> ();
obj.vt = new List<Vector2> ();
obj.f = new List<List<int[]>> ();
foreach (string line in lines) {
if (line == "" || line.StartsWith ("#"))
continue;
string[] token = line.Split (' ');
switch (token [0]) {
case ("o"):
obj.o = token [1];
break;
case ("mtllib"):
obj.mtllib = token [1];
break;
case ("usemtl"):
obj.usemtl.Add (token [1]);
obj.f.Add (new List<int[]> ());
break;
case ("v"):
obj.v.Add (new Vector3 (
float.Parse (token [1]),
float.Parse (token [2]),
float.Parse (token [3])));
break;
case ("vn"):
obj.vn.Add (new Vector3 (
float.Parse (token [1]),
float.Parse (token [2]),
float.Parse (token [3])));
break;
case ("vt"):
obj.vt.Add (new Vector3 (
float.Parse (token [1]),
float.Parse (token [2])));
break;
case ("f"):
for (int i = 1; i < 4; i += 1) {
int[] triplet = Array.ConvertAll (token [i].Split ('/'), x => {
if (String.IsNullOrEmpty (x))
return 0;
return int.Parse (x);
});
obj.f [obj.f.Count - 1].Add (triplet);
}
break;
}
}
return obj;
}
public static MaterialFile ReadMaterialFile (string path) {
MaterialFile mtl = new MaterialFile ();
string[] lines = File.ReadAllLines (path);
mtl.newmtl = new List<string> ();
mtl.mapKd = new List<string> ();
foreach (string line in lines) {
if (line == "" || line.StartsWith ("#"))
continue;
string[] token = line.Split (' ');
switch (token [0]) {
case ("newmtl"):
mtl.newmtl.Add (token [1]);
break;
case ("map_Kd"):
mtl.mapKd.Add (token [1]);
break;
}
}
return mtl;
}
}
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectLoader : MonoBehaviour {
public string directoryPath;
public bool isLoaded;
void Awake () {
isLoaded = true;
}
public void Load (string path, string filename) {
if (!isLoaded)
return;
directoryPath = path;
StartCoroutine (ConstructModel (filename));
}
IEnumerator ConstructModel (string filename) {
isLoaded = false;
FileReader.ObjectFile obj = FileReader.ReadObjectFile (directoryPath + filename);
FileReader.MaterialFile mtl = FileReader.ReadMaterialFile (directoryPath + obj.mtllib);
MeshFilter filter = gameObject.AddComponent<MeshFilter> ();
MeshRenderer renderer = gameObject.AddComponent<MeshRenderer> ();
filter.mesh = PopulateMesh (obj);
renderer.materials = DefineMaterial (obj, mtl);
isLoaded = true;
yield return null;
}
Mesh PopulateMesh (FileReader.ObjectFile obj) {
Mesh mesh = new Mesh ();
List<int[]> triplets = new List<int[]> ();
List<int> submeshes = new List<int> ();
for (int i = 0; i < obj.f.Count; i += 1) {
for (int j = 0; j < obj.f [i].Count; j += 1) {
triplets.Add (obj.f [i] [j]);
}
submeshes.Add (obj.f [i].Count);
}
Vector3[] vertices = new Vector3[triplets.Count];
Vector3[] normals = new Vector3[triplets.Count];
Vector2[] uvs = new Vector2[triplets.Count];
for (int i = 0; i < triplets.Count; i += 1) {
vertices [i] = obj.v [triplets [i] [0] - 1];
normals [i] = obj.vn [triplets [i] [2] - 1];
if (triplets [i] [1] > 0)
uvs [i] = obj.vt [triplets [i] [1] - 1];
}
mesh.name = obj.o;
mesh.vertices = vertices;
mesh.normals = normals;
mesh.uv = uvs;
mesh.subMeshCount = submeshes.Count;
int vertex = 0;
for (int i = 0; i < submeshes.Count; i += 1) {
int[] triangles = new int[submeshes [i]];
for (int j = 0; j < submeshes [i]; j += 1) {
triangles [j] = vertex;
vertex += 1;
}
mesh.SetTriangles (triangles, i);
}
mesh.RecalculateBounds ();
mesh.Optimize ();
return mesh;
}
Material[] DefineMaterial (FileReader.ObjectFile obj, FileReader.MaterialFile mtl) {
Material[] materials = new Material[obj.usemtl.Count];
for (int i = 0; i < obj.usemtl.Count; i += 1) {
int index = mtl.newmtl.IndexOf (obj.usemtl [i]);
Texture2D texture = new Texture2D (1, 1);
texture.LoadImage (File.ReadAllBytes (directoryPath + mtl.mapKd [index]));
materials [i] = new Material (Shader.Find ("Diffuse"));
materials [i].name = mtl.newmtl [index];
materials [i].mainTexture = texture;
}
return materials;
}
}
@Lesliehdezg

Copy link
Copy Markdown
Author

Hi, excellent work, but how works it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment