zoukankan      html  css  js  c++  java
  • 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接口的设计思想。

  • 相关阅读:
    CentOS 5.5 安装 Oracle 11gR2 并随系统启动
    使用blockrecover 对有坏块的数据文件进行恢复
    用yum下载安装包
    PInvoke时候StringBuilder的陷阱
    mac:添加环境变量
    win8:metro app UI 设计
    用户体验&UI 实用小技巧
    Win8:To Do List Demo
    JavaScript语言精粹 ——笔记
    win8: 确保 WinJS.xhr 重新发送请求
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-7-OBJ-parser-and-renderer.html
Copyright © 2011-2022 走看看