CSharpGL(9)解析OBJ文件并用CSharpGL渲染

由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了。CSharpGL源码中包含10多个独立的Demo,更适合入门参考。

为了尽可能提升渲染效率,CSharpGL是面向Shader的,因此稍有难度。

 

最近研究shader,需要一些典型的模型来显示效果。我自己做了几个。

CSharpGL(9)解析OBJ文件并用CSharpGL渲染

 

CSharpGL(9)解析OBJ文件并用CSharpGL渲染

但是都不如这个茶壶更典型。

CSharpGL(9)解析OBJ文件并用CSharpGL渲染

我搜罗半天,找到几个用*.obj格式存储的茶壶模型,于是不得不写个OBJ格式文件的解析器来读取和渲染这个茶壶了。

 

 

下载

这个OBJ解析器是CSharpGL的一部分,CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

OBJ文件格式

OBJ文件格式是非常简单的。这种文件以纯文本的形式存储了模型的顶点、法线和纹理坐标和材质使用信息。OBJ文件的每一行,都有极其相似的格式。在OBJ文件中,每行的格式如下:

 

前缀 参数1 参数2 参数3 ...

其中,前缀标识了这一行所存储的信息类型。参数则是具体的数据。OBJ文件常见的的前缀有

 

v 表示本行指定一个顶点。 前缀后跟着3个单精度浮点数,分别表示该定点的X、Y、Z坐标值

vt 表示本行指定一个纹理坐标。此前缀后跟着两个单精度浮点数。分别表示此纹理坐标的U、V值

vn 表示本行指定一个法线向量。此前缀后跟着3个单精度浮点数,分别表示该法向量的X、Y、Z坐标值

f 表示本行指定一个表面(Face)。一个表面实际上就是一个三角形图元。此前缀行的参数格式后面将详细介绍。

usemtl 此前缀后只跟着一个参数。该参数指定了从此行之后到下一个以usemtl开头的行之间的所有表面所使用的材质名称。该材质可以在此OBJ文件所附属的MTL文件中找到具体信息。

mtllib 此前缀后只跟着一个参数。该参数指定了此OBJ文件所使用的材质库文件(*.mtl)的文件路径

现在,我们再来看一下OBJ文件的结构。在一个OBJ文件中,首先有一些以v、vt或vn前缀开头的行指定了所有的顶点、纹理坐标、法线的坐标。然后再由一些以f开头的行指定每一个三角形所对应的顶点、纹理坐标和法线的索引。在顶点、纹理坐标和法线的索引之间,使用符号"/"隔开的。一个f行可以以下面几种格式出现:

 

f 1 2 3 这样的行表示以第1、2、3号顶点组成一个三角形。

f 1/3 2/5 3/4 这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,第二个顶点的纹理坐标的索引值为5,第三个顶点的纹理坐标的索引值为4。

f 1/3/4 2/5/6 3/4/2 这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,其法线的索引值是4;第二个顶点的纹理坐标的索引值为5,其法线的索引值是6;第三个顶点的纹理坐标的索引值为6,其法线的索引值是2。

f 1//4 2//6 3//2这样的行表示以第1、2、3号顶点组成一个三角形,且忽略纹理坐标。其中第一个顶点的法线的索引值是4;第二个顶点的法线的索引值是6;第三个顶点的法线的索引值是2。

值得注意的是文件中的索引值是以1作为起点的,这一点与C语言中以0作为起点有很大的不同。在渲染的时候应注意将从文件中读取的坐标值减去1。

另外,一个OBJ文件里可能有多个模型,每个模型都是由(若干顶点属性信息+若干面信息)这样的顺序描述的。

解析器设计思路

代码并不复杂。

  1     public class ObjFile
  2     {
  3         private List<ObjModel> models = new List<ObjModel>();
  4 
  5         public List<ObjModel> Models
  6         {
  7             get { return models; }
  8             //set { models = value; }
  9         }
 10 
 11         public static ObjFile Load(string filename)
 12         {
 13             ObjFile file = new ObjFile();
 14 
 15             LoadModels(filename, file);
 16             GenNormals(file);
 17             OrganizeModels(file);
 18 
 19             return file;
 20         }
 21 
 22         private static void OrganizeModels(ObjFile file)
 23         {
 24             List<ObjModel> models = new List<ObjModel>();
 25             foreach (var model in file.models)
 26             {
 27                 var newModel = OrganizeModels(model);
 28                 models.Add(newModel);
 29             }
 30 
 31             file.models.Clear();
 32             file.models.AddRange(models);
 33         }
 34 
 35         private static ObjModel OrganizeModels(ObjModel model)
 36         {
 37             ObjModel result = new ObjModel();
 38             result.positionList = model.positionList;
 39 
 40             result.normalList.AddRange(model.normalList);
 41 
 42             bool hasUV = model.uvList.Count > 0;
 43             if (hasUV)
 44             {
 45                 result.uvList.AddRange(model.uvList);
 46             }
 47 
 48             for (int i = 0; i < model.innerFaceList.Count; i++)
 49             {
 50                 var face = model.innerFaceList[i];
 51                 var tuple = new Tuple<int, int, int>(face.vertex0.position, face.vertex1.position, face.vertex2.position);
 52                 result.faceList.Add(tuple);
 53                 if (face.vertex0.normal > 0)
 54                     result.normalList[face.vertex0.position - 1] = model.normalList[face.vertex0.normal - 1];
 55                 if (face.vertex1.normal > 0)
 56                     result.normalList[face.vertex1.position - 1] = model.normalList[face.vertex1.normal - 1];
 57                 if (face.vertex2.normal > 0)
 58                     result.normalList[face.vertex2.position - 1] = model.normalList[face.vertex2.normal - 1];
 59 
 60                 if (hasUV)
 61                 {
 62                     if (face.vertex0.uv > 0)
 63                         result.uvList[face.vertex0.position - 1] = model.uvList[face.vertex0.uv - 1];
 64                     if (face.vertex1.uv > 0)
 65                         result.uvList[face.vertex1.position - 1] = model.uvList[face.vertex1.uv - 1];
 66                     if (face.vertex2.uv > 0)
 67                         result.uvList[face.vertex2.position - 1] = model.uvList[face.vertex2.uv - 1];
 68                 }
 69 
 70                 result.faceList.Add(new Tuple<int, int, int>(face.vertex0.position, face.vertex1.position, face.vertex2.position));
 71                 //result.faceList[i] = new Tuple<int, int, int>(face.vertex0.position, face.vertex1.position, face.vertex2.position);
 72             }
 73 
 74             //model.innerFaceList.Clear();
 75 
 76             return result;
 77         }
 78 
 79         private static void GenNormals(ObjFile file)
 80         {
 81             foreach (var model in file.models)
 82             {
 83                 GenNormals(model);
 84             }
 85         }
 86 
 87         private static void GenNormals(ObjModel model)
 88         {
 89             if (model.normalList.Count > 0) { return; }
 90 
 91             var faceNormals = new vec3[model.innerFaceList.Count];
 92             model.normalList.AddRange(new vec3[model.positionList.Count]);
 93 
 94             for (int i = 0; i < model.innerFaceList.Count; i++)
 95             {
 96                 var face = model.innerFaceList[i];
 97                 vec3 vertex0 = model.positionList[face.vertex0.position - 1];
 98                 vec3 vertex1 = model.positionList[face.vertex1.position - 1];
 99                 vec3 vertex2 = model.positionList[face.vertex2.position - 1];
100                 vec3 v1 = vertex0 - vertex2;
101                 vec3 v2 = vertex2 - vertex1;
102                 faceNormals[i] = v1.cross(v2);
103             }
104 
105             for (int i = 0; i < model.positionList.Count; i++)
106             {
107                 vec3 sum = new vec3();
108                 int shared = 0;
109                 for (int j = 0; j < model.innerFaceList.Count; j++)
110                 {
111                     var face = model.innerFaceList[j];
112                     if (face.vertex0.position - 1 == i || face.vertex1.position - 1 == i || face.vertex2.position - 1 == i)
113                     {
114                         sum = sum + faceNormals[i];
115                         shared++;
116                     }
117                 }
118                 if (shared > 0)
119                 {
120                     sum = sum / shared;
121                     sum.Normalize();
122                 }
123                 model.normalList[i] = sum;
124             }
125 
126         }
127 
128         private static void LoadModels(string filename, ObjFile file)
129         {
130             using (var sr = new StreamReader(filename))
131             {
132                 var model = new ObjModel();
133 
134                 while (!sr.EndOfStream)
135                 {
136                     string line = sr.ReadLine();
137                     string[] parts = line.Split(separator, StringSplitOptions.RemoveEmptyEntries);
138                     if (parts[0] == ("v"))
139                     {
140                         if (model.innerFaceList.Count > 0)
141                         {
142                             file.models.Add(model);
143                             model = new ObjModel();
144                         }
145 
146                         vec3 position = new vec3(float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3]));
147                         model.positionList.Add(position);
148                     }
149                     else if (parts[0] == ("vt"))
150                     {
151                         vec2 uv = new vec2(float.Parse(parts[1]), float.Parse(parts[2]));
152                         model.uvList.Add(uv);
153                     }
154                     else if (parts[0] == ("vn"))
155                     {
156                         vec3 normal = new vec3(float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3]));
157                         model.normalList.Add(normal);
158                     }
159                     else if (parts[0] == ("f"))
160                     {
161                         Triangle triangle = ParseFace(parts);
162                         model.innerFaceList.Add(triangle);
163                     }
164                 }
165 
166                 file.models.Add(model);
167             }
168         }
169 
170         private static Triangle ParseFace(string[] parts)
171         {
172             Triangle result = new Triangle();
173             if (parts[1].Contains("//"))
174             {
175                 for (int i = 1; i < 4; i++)
176                 {
177                     string[] indexes = parts[i].Split('/');
178                     int position = int.Parse(indexes[0]); int normal = int.Parse(indexes[1]);
179                     result[i - 1] = new VertexInfo() { position = position, normal = normal, uv = -1 };
180                 }
181             }
182             else if (parts[1].Contains("/"))
183             {
184                 int components = parts[1].Split('/').Length;
185                 if (components == 2)
186                 {
187                     for (int i = 1; i < 4; i++)
188                     {
189                         string[] indexes = parts[i].Split('/');
190                         int position = int.Parse(indexes[0]); int uv = int.Parse(indexes[1]);
191                         result[i - 1] = new VertexInfo() { position = position, normal = -1, uv = uv };
192                     }
193                 }
194                 else if (components == 3)
195                 {
196                     for (int i = 1; i < 4; i++)
197                     {
198                         string[] indexes = parts[i].Split('/');
199                         int position = int.Parse(indexes[0]); int uv = int.Parse(indexes[1]); int normal = int.Parse(indexes[2]);
200                         result[i - 1] = new VertexInfo() { position = position, normal = normal, uv = uv, };
201                     }
202                 }
203             }
204             else
205             {
206                 for (int i = 1; i < 4; i++)
207                 {
208                     int position = int.Parse(parts[i]);
209                     result[i - 1] = new VertexInfo() { position = position, normal = -1, uv = -1, };
210                 }
211             }
212 
213             return result;
214         }
215 
216         static readonly char[] separator = new char[] { ' ' };
217         static readonly char[] separator1 = new char[] { '/' };
218     }
219 
220     class VertexInfo
221     {
222         public int position;
223         public int normal;
224         public int uv;
225     }
226     class Triangle
227     {
228         public VertexInfo vertex0;
229         public VertexInfo vertex1;
230         public VertexInfo vertex2;
231 
232         public VertexInfo this[int index]
233         {
234             set
235             {
236                 if (index == 0)
237                 {
238                     this.vertex0 = value;
239                 }
240                 else if (index == 1)
241                 {
242                     this.vertex1 = value;
243                 }
244                 else if (index == 2)
245                 {
246                     this.vertex2 = value;
247                 }
248                 else
249                 {
250                     throw new ArgumentException();
251                 }
252             }
253         }
254     }
Parser

相关文章:

  • 2021-09-23
  • 2022-02-08
  • 2022-02-08
  • 2021-07-16
  • 2021-07-22
  • 2022-12-23
猜你喜欢
  • 2021-06-29
  • 2021-10-12
  • 2021-08-13
  • 2021-05-30
  • 2021-12-25
  • 2021-06-12
  • 2021-12-15
相关资源
相似解决方案