文章出處

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接口的設計思想。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()