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

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

2016-08-13

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

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

 

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

 

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

我搜罗半天,找到几个用*.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

 

用CSharpGL渲染OBJ模型文件

IModel接口

我发现一个shader可以渲染多个模型,一个模型也可以用多种shader来渲染。为了保证这种多对多关系,CSharpGL创建了一个IModel接口,用于将模型数据转换为OpenGL需要的Vertex Buffer Object。

 

public interface IModel

{

    BufferRenderer GetPositionBufferRenderer(string varNameInShader);

    BufferRenderer GetColorBufferRenderer(string varNameInShader);

    BufferRenderer GetNormalBufferRenderer(string varNameInShader);

    BufferRenderer GetIndexes();

}

 

从模型到VBO

为了保证Obj解析器项目的纯净,我们不直接让ObjModel实现IModel接口,而是另建一个Adapter类(可能不是这个名字,原谅我没有细学设计模式)。

  1 class ObjModelAdpater : IModel
  2     {
  3         private ObjModel model;
  4         public ObjModelAdpater(ObjModel model)
  5         {
  6             this.model = model;
  7         }
  8 
  9 
 10         CSharpGL.Objects.VertexBuffers.BufferRenderer IModel.GetPositionBufferRenderer(string varNameInShader)
 11         {
 12             using (var buffer = new ObjModelPositionBuffer(varNameInShader))
 13             {
 14                 buffer.Alloc(model.positionList.Count);
 15                 unsafe
 16                 {
 17                     vec3* array = (vec3*)buffer.FirstElement();
 18                     for (int i = 0; i < model.positionList.Count; i++)
 19                     {
 20                         array[i] = model.positionList[i];
 21                     }
 22                 }
 23 
 24                 return buffer.GetRenderer();
 25             }
 26 
 27         }
 28 
 29         CSharpGL.Objects.VertexBuffers.BufferRenderer IModel.GetColorBufferRenderer(string varNameInShader)
 30         {
 31             if (model.uvList.Count == 0) { return null; }
 32 
 33             using (var buffer = new ObjModelColorBuffer(varNameInShader))
 34             {
 35                 buffer.Alloc(model.uvList.Count);
 36                 unsafe
 37                 {
 38                     vec2* array = (vec2*)buffer.FirstElement();
 39                     for (int i = 0; i < model.uvList.Count; i++)
 40                     {
 41                         array[i] = model.uvList[i];
 42                     }
 43                 }
 44 
 45                 return buffer.GetRenderer();
 46             }
 47 
 48         }
 49 
 50         CSharpGL.Objects.VertexBuffers.BufferRenderer IModel.GetNormalBufferRenderer(string varNameInShader)
 51         {
 52             using (var buffer = new ObjModelNormalBuffer(varNameInShader))
 53             {
 54                 buffer.Alloc(model.normalList.Count);
 55                 unsafe
 56                 {
 57                     vec3* array = (vec3*)buffer.FirstElement();
 58                     for (int i = 0; i < model.normalList.Count; i++)
 59                     {
 60                         array[i] = model.normalList[i];
 61                     }
 62                 }
 63 
 64                 return buffer.GetRenderer();
 65             }
 66 
 67         }
 68 
 69         CSharpGL.Objects.VertexBuffers.BufferRenderer IModel.GetIndexes()
 70         {
 71             using (var buffer = new IndexBuffer<uint>(DrawMode.Triangles, IndexElementType.UnsignedInt, BufferUsage.StaticDraw))
 72             {
 73                 buffer.Alloc(model.faceList.Count * 3);
 74                 unsafe
 75                 {
 76                     uint* array = (uint*)buffer.FirstElement();
 77                     for (int i = 0; i < model.faceList.Count; i++)
 78                     {
 79                         array[i * 3 + 0] = (uint)(model.faceList[i].Item1 - 1);
 80                         array[i * 3 + 1] = (uint)(model.faceList[i].Item2 - 1);
 81                         array[i * 3 + 2] = (uint)(model.faceList[i].Item3 - 1);
 82                     }
 83                 }
 84 
 85                 return buffer.GetRenderer();
 86             }
 87         }
 88     }
 89 
 90 
 91     class ObjModelPositionBuffer : PropertyBuffer<vec3>
 92     {
 93         public ObjModelPositionBuffer(string varNameInShader)
 94             : base(varNameInShader, 3, GL.GL_FLOAT, BufferUsage.StaticDraw)
 95         {
 96 
 97         }
 98     }
 99 
100     class ObjModelColorBuffer : PropertyBuffer<vec2>
101     {
102         public ObjModelColorBuffer(string varNameInShader)
103             : base(varNameInShader, 3, GL.GL_FLOAT, BufferUsage.StaticDraw)
104         {
105 
106         }
107     }
108 
109     class ObjModelNormalBuffer : PropertyBuffer<vec3>
110     {
111         public ObjModelNormalBuffer(string varNameInShader)
112             : base(varNameInShader, 3, GL.GL_FLOAT, BufferUsage.StaticDraw)
113         {
114 
115         }
116     }
Adapter

渲染

剩下的就简单了,把其他Element的框架抄来就差不多了。

 

  1     class ObjModelElement : SceneElementBase
  2     {
  3         ShaderProgram shaderProgram;
  4 
  5         #region VAO/VBO renderers
  6 
  7         VertexArrayObject vertexArrayObject;
  8 
  9         const string strin_Position = "in_Position";
 10         BufferRenderer positionBufferRenderer;
 11 
 12         //const string strin_Color = "in_Color";
 13         //BufferRenderer colorBufferRenderer;
 14 
 15         const string strin_Normal = "in_Normal";
 16         BufferRenderer normalBufferRenderer;
 17 
 18         BufferRenderer indexBufferRenderer;
 19 
 20         #endregion
 21 
 22         #region uniforms
 23 
 24 
 25         const string strmodelMatrix = "modelMatrix";
 26         public mat4 modelMatrix;
 27 
 28         const string strviewMatrix = "viewMatrix";
 29         public mat4 viewMatrix;
 30 
 31         const string strprojectionMatrix = "projectionMatrix";
 32         public mat4 projectionMatrix;
 33 
 34         #endregion
 35 
 36 
 37         public PolygonModes polygonMode = PolygonModes.Filled;
 38 
 39         private int indexCount;
 40 
 41         private ObjModelAdpater objModelAdapter;
 42 
 43         public ObjModelElement(ObjModel objModel)
 44         {
 45             this.objModelAdapter = new ObjModelAdpater(objModel);
 46         }
 47 
 48         protected void InitializeShader(out ShaderProgram shaderProgram)
 49         {
 50             var vertexShaderSource = ManifestResourceLoader.LoadTextFile(@"ObjModelElement.vert");
 51             var fragmentShaderSource = ManifestResourceLoader.LoadTextFile(@"ObjModelElement.frag");
 52 
 53             shaderProgram = new ShaderProgram();
 54             shaderProgram.Create(vertexShaderSource, fragmentShaderSource, null);
 55 
 56         }
 57 
 58         protected void InitializeVAO()
 59         {
 60             IModel model = this.objModelAdapter;
 61 
 62             this.positionBufferRenderer = model.GetPositionBufferRenderer(strin_Position);
 63             //this.colorBufferRenderer = model.GetColorBufferRenderer(strin_Color);
 64             this.normalBufferRenderer = model.GetNormalBufferRenderer(strin_Normal);
 65             this.indexBufferRenderer = model.GetIndexes();
 66 
 67             IndexBufferRenderer renderer = this.indexBufferRenderer as IndexBufferRenderer;
 68             if (renderer != null)
 69             {
 70                 this.indexCount = renderer.ElementCount;
 71             }
 72         }
 73 
 74         protected override void DoInitialize()
 75         {
 76             InitializeShader(out shaderProgram);
 77 
 78             InitializeVAO();
 79         }
 80 
 81         protected override void DoRender(RenderEventArgs e)
 82         {
 83             if (this.vertexArrayObject == null)
 84             {
 85                 var vao = new VertexArrayObject(
 86                     this.positionBufferRenderer,
 87                     //this.colorBufferRenderer,
 88                     this.normalBufferRenderer,
 89                     this.indexBufferRenderer);
 90                 vao.Create(e, this.shaderProgram);
 91 
 92                 this.vertexArrayObject = vao;
 93             }
 94 
 95             ShaderProgram program = this.shaderProgram;
 96             // 绑定shader
 97             program.Bind();
 98 
 99             program.SetUniformMatrix4(strprojectionMatrix, projectionMatrix.to_array());
100             program.SetUniformMatrix4(strviewMatrix, viewMatrix.to_array());
101             program.SetUniformMatrix4(strmodelMatrix, modelMatrix.to_array());
102 
103             int[] originalPolygonMode = new int[1];
104             GL.GetInteger(GetTarget.PolygonMode, originalPolygonMode);
105 
106             GL.PolygonMode(PolygonModeFaces.FrontAndBack, this.polygonMode);
107             this.vertexArrayObject.Render(e, this.shaderProgram);
108             GL.PolygonMode(PolygonModeFaces.FrontAndBack, (PolygonModes)(originalPolygonMode[0]));
109 
110             // 解绑shader
111             program.Unbind();
112         }
113 
114 
115 
116         protected override void CleanUnmanagedRes()
117         {
118             if (this.vertexArrayObject != null)
119             {
120                 this.vertexArrayObject.Dispose();
121             }
122 
123             base.CleanUnmanagedRes();
124         }
125 
126         public void DecreaseVertexCount()
127         {
128             IndexBufferRenderer renderer = this.indexBufferRenderer as IndexBufferRenderer;
129             if (renderer != null)
130             {
131                 if (renderer.ElementCount > 0)
132                     renderer.ElementCount--;
133             }
134         }
135 
136         public void IncreaseVertexCount()
137         {
138             IndexBufferRenderer renderer = this.indexBufferRenderer as IndexBufferRenderer;
139             if (renderer != null)
140             {
141                 if (renderer.ElementCount < this.indexCount)
142                     renderer.ElementCount++;
143             }
144         }
145 
146 
147     }
ObjModelElement

 

结果如图所示。我用normal值来表示颜色,就成了这个样子。

 

总结

本篇介绍了一个OBJ文件解析器、渲染器和IModel接口的设计思想。

posted @ 2016-01-18 14:40  BIT祝威  阅读(2964)  评论(4编辑  收藏  举报
canvas start.

canvas end.