首页 > Unity3D专栏 > 【Unity3D】Unity3D加载obj格式的模型
2017
08-01

【Unity3D】Unity3D加载obj格式的模型

创建两个脚本

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GeometryBuffer {

	private List<ObjectData> objects;
	public List<Vector3> vertices;
	public List<Vector2> uvs;
	public List<Vector3> normals;
	public int unnamedGroupIndex = 1; // naming index for unnamed group. like "Unnamed-1"
	
	private ObjectData current;
	private class ObjectData {
		public string name;
		public List<GroupData> groups;
		public List<FaceIndices> allFaces;
		public int normalCount;
		public ObjectData() {
			groups = new List<GroupData>();
			allFaces = new List<FaceIndices>();
			normalCount = 0;
		}
	}
	
	private GroupData curgr;
	private class GroupData {
		public string name;
		public string materialName;
		public List<FaceIndices> faces;
		public GroupData() {
			faces = new List<FaceIndices>();
		}
		public bool isEmpty { get { return faces.Count == 0; } }
	}
	
	public GeometryBuffer() {
		objects = new List<ObjectData>();
		ObjectData d = new ObjectData();
		d.name = "default";
		objects.Add(d);
		current = d;
		
		GroupData g = new GroupData();
		g.name = "default";
		d.groups.Add(g);
		curgr = g;
		
		vertices = new List<Vector3>();
		uvs = new List<Vector2>();
		normals = new List<Vector3>();
	}
	
	public void PushObject(string name) {
		//Debug.Log("Adding new object " + name + ". Current is empty: " + isEmpty);
		if(isEmpty) objects.Remove(current);
		
		ObjectData n = new ObjectData();
		n.name = name;
		objects.Add(n);
		
		GroupData g = new GroupData();
		g.name = "default";
		n.groups.Add(g);
		
		curgr = g;
		current = n;
	}
	
	public void PushGroup(string name) {
		if(curgr.isEmpty) current.groups.Remove(curgr);
		GroupData g = new GroupData();
		if (name == null) {
			name = "Unnamed-"+unnamedGroupIndex;
			unnamedGroupIndex++;
		}
		g.name = name;
		current.groups.Add(g);
		curgr = g;
	}
	
	public void PushMaterialName(string name) {
		//Debug.Log("Pushing new material " + name + " with curgr.empty=" + curgr.isEmpty);
		if(!curgr.isEmpty) PushGroup(name);
		if(curgr.name == "default") curgr.name = name;
		curgr.materialName = name;
	}
	
	public void PushVertex(Vector3 v) {
		vertices.Add(v);
	}
	
	public void PushUV(Vector2 v) {
		uvs.Add(v);
	}
	
	public void PushNormal(Vector3 v) {
		normals.Add(v);
	}
	
	public void PushFace(FaceIndices f) {
		curgr.faces.Add(f);
		current.allFaces.Add(f);
		if (f.vn >= 0) {
			current.normalCount++;
		}
	}
	
	public void Trace() {
		Debug.Log("OBJ has " + objects.Count + " object(s)");
		Debug.Log("OBJ has " + vertices.Count + " vertice(s)");
		Debug.Log("OBJ has " + uvs.Count + " uv(s)");
		Debug.Log("OBJ has " + normals.Count + " normal(s)");
		foreach(ObjectData od in objects) {
			Debug.Log(od.name + " has " + od.groups.Count + " group(s)");
			foreach(GroupData gd in od.groups) {
				Debug.Log(od.name + "/" + gd.name + " has " + gd.faces.Count + " faces(s)");
			}
		}
		
	}
	
	public int numObjects { get { return objects.Count; } }	
	public bool isEmpty { get { return vertices.Count == 0; } }
	public bool hasUVs { get { return uvs.Count > 0; } }
	public bool hasNormals { get { return normals.Count > 0; } }
	
	public static int MAX_VERTICES_LIMIT_FOR_A_MESH = 64999;
	
	public void PopulateMeshes(GameObject[] gs, Dictionary<string, Material> mats) {
		if(gs.Length != numObjects) return; // Should not happen unless obj file is corrupt...
		Debug.Log("PopulateMeshes GameObjects count:"+gs.Length);
		for(int i = 0; i < gs.Length; i++) {
			ObjectData od = objects[i];
			bool objectHasNormals = (hasNormals && od.normalCount > 0);
			
			if(od.name != "default") gs[i].name = od.name;
			Debug.Log("PopulateMeshes object name:"+od.name);
			
			Vector3[] tvertices = new Vector3[od.allFaces.Count];
			Vector2[] tuvs = new Vector2[od.allFaces.Count];
			Vector3[] tnormals = new Vector3[od.allFaces.Count];
		
			int k = 0;
			foreach(FaceIndices fi in od.allFaces) {
				if (k >= MAX_VERTICES_LIMIT_FOR_A_MESH) {
					Debug.LogWarning("maximum vertex number for a mesh exceeded for object:"  + gs[i].name);
					break;
				}
				tvertices[k] = vertices[fi.vi];
				if(hasUVs) tuvs[k] = uvs[fi.vu];
				if(hasNormals && fi.vn >= 0) tnormals[k] = normals[fi.vn];
				k++;
			}
		
			Mesh m = (gs[i].GetComponent(typeof(MeshFilter)) as MeshFilter).mesh;
			m.vertices = tvertices;
			if(hasUVs) m.uv = tuvs;
			if(objectHasNormals) m.normals = tnormals;
			
			if(od.groups.Count == 1) {
				Debug.Log("PopulateMeshes only one group: "+od.groups[0].name);
				GroupData gd = od.groups[0];
				string matName = (gd.materialName != null) ? gd.materialName : "default"; // MAYBE: "default" may not enough.
				if (mats.ContainsKey(matName)) {
					gs[i].renderer.material = mats[matName];
					Debug.Log("PopulateMeshes mat:"+matName+" set.");
				}
				else {
					Debug.LogWarning("PopulateMeshes mat:"+matName+" not found.");
				}
				int[] triangles = new int[gd.faces.Count];
				for(int j = 0; j < triangles.Length; j++) triangles[j] = j;
				
				m.triangles = triangles;
				
			} else {
				int gl = od.groups.Count;
				Material[] materials = new Material[gl];
				m.subMeshCount = gl;
				int c = 0;
				
				Debug.Log("PopulateMeshes group count:"+gl);
				for(int j = 0; j < gl; j++) {
					string matName = (od.groups[j].materialName != null) ? od.groups[j].materialName : "default"; // MAYBE: "default" may not enough.
					if (mats.ContainsKey(matName)) {
						materials[j] = mats[matName];
						Debug.Log("PopulateMeshes mat:"+matName+" set.");
					}
					else {
						Debug.LogWarning("PopulateMeshes mat:"+matName+" not found.");
					}
					
					int[] triangles = new int[od.groups[j].faces.Count];
					int l = od.groups[j].faces.Count + c;
					int s = 0;
					for(; c < l; c++, s++) triangles[s] = c;
					m.SetTriangles(triangles, j);
				}
				
				gs[i].renderer.materials = materials;
			}
			if (!objectHasNormals) {
				m.RecalculateNormals();
			}
		}
	}
}
using System;

public struct FaceIndices
{
	public int vi;
	public int vu;
	public int vn;
}

 

创建完成后,建立我们的测试脚本

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.IO;

public class OBJ : MonoBehaviour {
	
	public string objPath;
	
	/* OBJ file tags */
	private const string O 	= "o";
	private const string G 	= "g";
	private const string V 	= "v";
	private const string VT = "vt";
	private const string VN = "vn";
	private const string F 	= "f";
	private const string MTL = "mtllib";
	private const string UML = "usemtl";

	/* MTL file tags */
	private const string NML = "newmtl";
	private const string NS = "Ns"; // Shininess
	private const string KA = "Ka"; // Ambient component (not supported)
	private const string KD = "Kd"; // Diffuse component
	private const string KS = "Ks"; // Specular component
	private const string D = "d"; 	// Transparency (not supported)
	private const string TR = "Tr";	// Same as 'd'
	private const string ILLUM = "illum"; // Illumination model. 1 - diffuse, 2 - specular
	private const string MAP_KA = "map_Ka"; // Ambient texture
	private const string MAP_KD = "map_Kd"; // Diffuse texture
	private const string MAP_KS = "map_Ks"; // Specular texture
	private const string MAP_KE = "map_Ke"; // Emissive texture
	private const string MAP_BUMP = "map_bump"; // Bump map texture
	private const string BUMP = "bump"; // Bump map texture
	
	private string basepath;
	private string mtllib;
	private GeometryBuffer buffer;

	void Start ()
	{
		buffer = new GeometryBuffer ();
		StartCoroutine (Load (objPath));
	}
	
	public IEnumerator Load(string path) {
		basepath = (path.IndexOf("/") == -1) ? "" : path.Substring(0, path.LastIndexOf("/") + 1);
		
		WWW loader = new WWW(path);
		yield return loader;
		SetGeometryData(loader.text);
		
		if(hasMaterials) {
			loader = new WWW(basepath + mtllib);
			Debug.Log("base path = "+basepath);
			Debug.Log("MTL path = "+(basepath + mtllib));
			yield return loader;
			if (loader.error != null) {
				Debug.LogError(loader.error);
			}
			else {
				SetMaterialData(loader.text);
			}
			
			foreach(MaterialData m in materialData) {
				if(m.diffuseTexPath != null) {
					WWW texloader = GetTextureLoader(m, m.diffuseTexPath);
					yield return texloader;
					if (texloader.error != null) {
						Debug.LogError(texloader.error);
					} else {
						m.diffuseTex = texloader.texture;
					}
				}
				if(m.bumpTexPath != null) {
					WWW texloader = GetTextureLoader(m, m.bumpTexPath);
					yield return texloader;
					if (texloader.error != null) {
						Debug.LogError(texloader.error);
					} else {
						m.bumpTex = texloader.texture;
					}
				}
			}
		}
		
		Build();

	}
	
	private WWW GetTextureLoader(MaterialData m, string texpath) {
		char[] separators = {'/', '\\'};
		string[] components = texpath.Split(separators);
		string filename = components[components.Length-1];
		string ext = Path.GetExtension(filename).ToLower();
		if (ext != ".png" && ext != ".jpg") {
			Debug.LogWarning("maybe unsupported texture format:"+ext);
		}
		WWW texloader = new WWW(basepath + filename);
		Debug.Log("texture path for material("+m.name+") = "+(basepath + filename));
		return texloader;
	}
	
	private void GetFaceIndicesByOneFaceLine(FaceIndices[] faces, string[] p, bool isFaceIndexPlus) {
		if (isFaceIndexPlus) {
			for(int j = 1; j < p.Length; j++) {
				string[] c = p[j].Trim().Split("/".ToCharArray());
				FaceIndices fi = new FaceIndices();
				// vertex
				int vi = ci(c[0]);
				fi.vi = vi-1;
				// uv
				if(c.Length > 1 && c[1] != "") {
					int vu = ci(c[1]);
					fi.vu = vu-1;
				}
				// normal
				if(c.Length > 2 && c[2] != "") {
					int vn = ci(c[2]);
					fi.vn = vn-1;
				}
				else { 
					fi.vn = -1;
				}
				faces[j-1] = fi;
			}
		}
		else { // for minus index
			int vertexCount = buffer.vertices.Count;
			int uvCount = buffer.uvs.Count;
			for(int j = 1; j < p.Length; j++) {
				string[] c = p[j].Trim().Split("/".ToCharArray());
				FaceIndices fi = new FaceIndices();
				// vertex
				int vi = ci(c[0]);
				fi.vi = vertexCount + vi;
				// uv
				if(c.Length > 1 && c[1] != "") {
					int vu = ci(c[1]);
					fi.vu = uvCount + vu;
				}
				// normal
				if(c.Length > 2 && c[2] != "") {
					int vn = ci(c[2]);
					fi.vn = vertexCount + vn;
				}
				else {
					fi.vn = -1;
				}
				faces[j-1] = fi;
			}
		}
	}

	private void SetGeometryData(string data) {
		string[] lines = data.Split("\n".ToCharArray());
		Regex regexWhitespaces = new Regex(@"\s+");
		bool isFirstInGroup = true;
		bool isFaceIndexPlus = true;
		for(int i = 0; i < lines.Length; i++) {
			string l = lines[i].Trim();
			
			if(l.IndexOf("#") != -1) { // comment line
				continue;
			}
			string[] p = regexWhitespaces.Split(l);
			switch(p[0]) {
				case O:
					buffer.PushObject(p[1].Trim());
					isFirstInGroup = true;
					break;
				case G:
					string groupName = null;
					if (p.Length >= 2) {
						groupName = p[1].Trim();
					}
					isFirstInGroup = true;
					buffer.PushGroup(groupName);
					break;
				case V:
					buffer.PushVertex( new Vector3( cf(p[1]), cf(p[2]), cf(p[3]) ) );
					break;
				case VT:
					buffer.PushUV(new Vector2( cf(p[1]), cf(p[2]) ));
					break;
				case VN:
					buffer.PushNormal(new Vector3( cf(p[1]), cf(p[2]), cf(p[3]) ));
					break;
				case F:
					FaceIndices[] faces = new FaceIndices[p.Length-1];
					if (isFirstInGroup) {
						isFirstInGroup = false;
						string[] c = p[1].Trim().Split("/".ToCharArray());
						isFaceIndexPlus = (ci(c[0]) >= 0);
					}
					GetFaceIndicesByOneFaceLine(faces, p, isFaceIndexPlus);
					if (p.Length == 4) {
						buffer.PushFace(faces[0]);
						buffer.PushFace(faces[1]);
						buffer.PushFace(faces[2]);
					}
					else if (p.Length == 5) {
						buffer.PushFace(faces[0]);
						buffer.PushFace(faces[1]);
						buffer.PushFace(faces[3]);
						buffer.PushFace(faces[3]);
						buffer.PushFace(faces[1]);
						buffer.PushFace(faces[2]);
					}
					else {
						Debug.LogWarning("face vertex count :"+(p.Length-1)+" larger than 4:");
					}
					break;
				case MTL:
					mtllib = l.Substring(p[0].Length+1).Trim();
					break;
				case UML:
					buffer.PushMaterialName(p[1].Trim());
					break;
			}
		}
		
		// buffer.Trace();
	}

	private float cf(string v) {
		try {
			return float.Parse(v);
		}
		catch(Exception e) {
			print(e);
			return 0;
		}
	}
	
	private int ci(string v) {
		try {
			return int.Parse(v);
		}
		catch(Exception e) {
			print(e);
			return 0;
		}
	}
	
	private bool hasMaterials {
		get {
			return mtllib != null;
		}
	}
	
	/* ############## MATERIALS */
	private List<MaterialData> materialData;
	private class MaterialData {
		public string name;
		public Color ambient;
		public Color diffuse;
		public Color specular;
		public float shininess;
		public float alpha;
		public int illumType;
		public string diffuseTexPath;
		public string bumpTexPath;
		public Texture2D diffuseTex;
		public Texture2D bumpTex;
	}
	
	private void SetMaterialData(string data) {
		string[] lines = data.Split("\n".ToCharArray());
		
		materialData = new List<MaterialData>();
		MaterialData current = new MaterialData();
		Regex regexWhitespaces = new Regex(@"\s+");
		
		for(int i = 0; i < lines.Length; i++) {
			string l = lines[i].Trim();
			
			if(l.IndexOf("#") != -1) l = l.Substring(0, l.IndexOf("#"));
			string[] p = regexWhitespaces.Split(l);
			if (p[0].Trim() == "") continue;

			switch(p[0]) {
				case NML:
					current = new MaterialData();
					current.name = p[1].Trim();
					materialData.Add(current);
					break;
				case KA:
					current.ambient = gc(p);
					break;
				case KD:
					current.diffuse = gc(p);
					break;
				case KS:
					current.specular = gc(p);
					break;
				case NS:
					current.shininess = cf(p[1]) / 1000;
					break;
				case D:
				case TR:
					current.alpha = cf(p[1]);
					break;
				case MAP_KD:
					current.diffuseTexPath = p[p.Length-1].Trim();
					break;
				case MAP_BUMP:
				case BUMP:
					BumpParameter(current, p);
					break;
				case ILLUM:
					current.illumType = ci(p[1]);
					break;
				default:
					Debug.Log("this line was not processed :" +l );
					break;
			}
		}	
	}
	
	private Material GetMaterial(MaterialData md) {
		Material m;
		
		if(md.illumType == 2) {
			string shaderName = (md.bumpTex != null)? "Bumped Specular" : "Specular";
			m =  new Material(Shader.Find(shaderName));
			m.SetColor("_SpecColor", md.specular);
			m.SetFloat("_Shininess", md.shininess);
		} else {
			string shaderName = (md.bumpTex != null)? "Bumped Diffuse" : "Diffuse";
			m =  new Material(Shader.Find(shaderName));
		}

		if(md.diffuseTex != null) {
			m.SetTexture("_MainTex", md.diffuseTex);
		}
		else {
			m.SetColor("_Color", md.diffuse);
		}
		if(md.bumpTex != null) m.SetTexture("_BumpMap", md.bumpTex);
		
		m.name = md.name;
		
		return m;
	}
	
	private class BumpParamDef {
		public string optionName;
		public string valueType;
		public int valueNumMin;
		public int valueNumMax;
		public BumpParamDef(string name, string type, int numMin, int numMax) {
			this.optionName = name;
			this.valueType = type;
			this.valueNumMin = numMin;
			this.valueNumMax = numMax;
		}
	}

	private void BumpParameter(MaterialData m, string[] p) {
		Regex regexNumber = new Regex(@"^[-+]?[0-9]*\.?[0-9]+$");
		
		var bumpParams = new Dictionary<String, BumpParamDef>();
		bumpParams.Add("bm",new BumpParamDef("bm","string", 1, 1));
		bumpParams.Add("clamp",new BumpParamDef("clamp", "string", 1,1));
		bumpParams.Add("blendu",new BumpParamDef("blendu", "string", 1,1));
		bumpParams.Add("blendv",new BumpParamDef("blendv", "string", 1,1));
		bumpParams.Add("imfchan",new BumpParamDef("imfchan", "string", 1,1));
		bumpParams.Add("mm",new BumpParamDef("mm", "string", 1,1));
		bumpParams.Add("o",new BumpParamDef("o", "number", 1,3));
		bumpParams.Add("s",new BumpParamDef("s", "number", 1,3));
		bumpParams.Add("t",new BumpParamDef("t", "number", 1,3));
		bumpParams.Add("texres",new BumpParamDef("texres", "string", 1,1));
		int pos = 1;
		string filename = null;
		while (pos < p.Length) {
			if (!p[pos].StartsWith("-")) {
				filename = p[pos];
				pos++;
				continue;
			}
			// option processing
			string optionName = p[pos].Substring(1);
			pos++;
			if (!bumpParams.ContainsKey(optionName)) {
				continue;
			}
			BumpParamDef def = bumpParams[optionName];
			ArrayList args = new ArrayList();
			int i=0;
			bool isOptionNotEnough = false;
			for (;i<def.valueNumMin ; i++, pos++) {
				if (pos >= p.Length) {
					isOptionNotEnough = true;
					break;
				}
				if (def.valueType == "number") {
					Match match = regexNumber.Match(p[pos]);
					if (!match.Success) {
						isOptionNotEnough = true;
						break;
					}
				}
				args.Add(p[pos]);
			}
			if (isOptionNotEnough) {
				Debug.Log("bump variable value not enough for option:"+optionName+" of material:"+m.name);
				continue;
			}
			for (;i<def.valueNumMax && pos < p.Length ; i++, pos++) {
				if (def.valueType == "number") {
					Match match = regexNumber.Match(p[pos]);
					if (!match.Success) {
						break;
					}
				}
				args.Add(p[pos]);
			}
			// TODO: some processing of options
			Debug.Log("found option: "+optionName+" of material: "+m.name+" args: "+String.Concat(args.ToArray()));
		}
		if (filename != null) {
			m.bumpTexPath = filename;
		}
	}
	
	private Color gc(string[] p) {
		return new Color( cf(p[1]), cf(p[2]), cf(p[3]) );
	}

	private void Build() {
		Dictionary<string, Material> materials = new Dictionary<string, Material>();
		
		if(hasMaterials) {
			foreach(MaterialData md in materialData) {
				if (materials.ContainsKey(md.name)) {
					Debug.LogWarning("duplicate material found: "+ md.name+ ". ignored repeated occurences");
					continue;
				}
				materials.Add(md.name, GetMaterial(md));
			}
		} else {
			materials.Add("default", new Material(Shader.Find("VertexLit")));
		}
		
		GameObject[] ms = new GameObject[buffer.numObjects];
		
		if(buffer.numObjects == 1) {
			gameObject.AddComponent(typeof(MeshFilter));
			gameObject.AddComponent(typeof(MeshRenderer));
			ms[0] = gameObject;
		} else if(buffer.numObjects > 1) {
			for(int i = 0; i < buffer.numObjects; i++) {
				GameObject go = new GameObject();
				go.transform.parent = gameObject.transform;
				go.AddComponent(typeof(MeshFilter));
				go.AddComponent(typeof(MeshRenderer));
				ms[i] = go;
			}
		}
		
		buffer.PopulateMeshes(ms, materials);
	}
}

把这个脚本挂在任意物体上后,给objpath地址即可。

最后,如果在移动端上显示的话,会报错。需要Edit-Graphics-添加个”Specular “即可。



虚拟世界提醒您:如果您觉得本文不错,快快将这篇文章分享出去吧 ,感谢您的支持和关注,谢谢!
最后编辑:
作者:虚拟世界
专注互联网技术及资讯 技术研究中心|有相关Unity3D问题或者作事宜请联系我哦!
捐 赠如果您愿意花10块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝或者微信直接向我捐款,在此非常感谢您对虚拟世界的捐赠。

留下一个回复

你的email不会被公开。