ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 第七课:模型加载 # 第七课:模型加载 目前为止,我们一直在硬编码描述立方体。你一定觉得这样做很笨拙、不方便。 本课将学习从文件中加载3D模型。和加载纹理类似,我们先写一个小的、功能有限的加载器,接着再为大家介绍几个比我们写的更好的、实用的库。 为了让课程尽可能简单,我们将采用简单、常用的OBJ格式。同样也是出于简单原则,我们只处理每个顶点有一个UV坐标和一个法向量的OBJ文件(目前你不需要知道什么是法向量)。 ## 加载OBJ模型 加载函数在common/objloader.hpp中声明,在common/objloader.cpp中实现。函数原型如下: ``` <pre class="calibre16">``` bool <span class="token3">loadOBJ</span><span class="token1">(</span> const char <span class="token">*</span> path<span class="token1">,</span> std<span class="token1">:</span><span class="token1">:</span>vector <span class="token">&</span> out_vertices<span class="token1">,</span> std<span class="token1">:</span><span class="token1">:</span>vector <span class="token">&</span> out_uvs<span class="token1">,</span> std<span class="token1">:</span><span class="token1">:</span>vector <span class="token">&</span> out_normals <span class="token1">)</span> ``` ``` 我们让loadOBJ读取文件路径,把数据写入out\_vertices/out\_uvs/out\_normals。如果出错则返回false。std::vector是C++中的数组,可存放glm::vec3类型的数据,数组大小可任意修改,不过std::vector和数学中的向量(vector)是两码事。其实它只是个数组。最后提一点,符号&意思是这个函数将会直接修改这些数组。 ### OBJ文件示例 OBJ文件看起来大概像这样: ``` <pre class="calibre16">``` # Blender3D v249 OBJ File<span class="token1">:</span> untitled<span class="token1">.</span>blend # www<span class="token1">.</span>blender3d<span class="token1">.</span>org mtllib cube<span class="token1">.</span>mtl v <span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> v <span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> <span class="token6">1.000000</span> v <span class="token">-</span><span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> <span class="token6">1.000000</span> v <span class="token">-</span><span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> v <span class="token6">1.000000</span> <span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> v <span class="token6">0.999999</span> <span class="token6">1.000000</span> <span class="token6">1.000001</span> v <span class="token">-</span><span class="token6">1.000000</span> <span class="token6">1.000000</span> <span class="token6">1.000000</span> v <span class="token">-</span><span class="token6">1.000000</span> <span class="token6">1.000000</span> <span class="token">-</span><span class="token6">1.000000</span> vt <span class="token6">0.748573</span> <span class="token6">0.750412</span> vt <span class="token6">0.749279</span> <span class="token6">0.501284</span> vt <span class="token6">0.999110</span> <span class="token6">0.501077</span> vt <span class="token6">0.999455</span> <span class="token6">0.750380</span> vt <span class="token6">0.250471</span> <span class="token6">0.500702</span> vt <span class="token6">0.249682</span> <span class="token6">0.749677</span> vt <span class="token6">0.001085</span> <span class="token6">0.750380</span> vt <span class="token6">0.001517</span> <span class="token6">0.499994</span> vt <span class="token6">0.499422</span> <span class="token6">0.500239</span> vt <span class="token6">0.500149</span> <span class="token6">0.750166</span> vt <span class="token6">0.748355</span> <span class="token6">0.998230</span> vt <span class="token6">0.500193</span> <span class="token6">0.998728</span> vt <span class="token6">0.498993</span> <span class="token6">0.250415</span> vt <span class="token6">0.748953</span> <span class="token6">0.250920</span> vn <span class="token6">0.000000</span> <span class="token6">0.000000</span> <span class="token">-</span><span class="token6">1.000000</span> vn <span class="token">-</span><span class="token6">1.000000</span> <span class="token">-</span><span class="token6">0.000000</span> <span class="token">-</span><span class="token6">0.000000</span> vn <span class="token">-</span><span class="token6">0.000000</span> <span class="token">-</span><span class="token6">0.000000</span> <span class="token6">1.000000</span> vn <span class="token">-</span><span class="token6">0.000001</span> <span class="token6">0.000000</span> <span class="token6">1.000000</span> vn <span class="token6">1.000000</span> <span class="token">-</span><span class="token6">0.000000</span> <span class="token6">0.000000</span> vn <span class="token6">1.000000</span> <span class="token6">0.000000</span> <span class="token6">0.000001</span> vn <span class="token6">0.000000</span> <span class="token6">1.000000</span> <span class="token">-</span><span class="token6">0.000000</span> vn <span class="token">-</span><span class="token6">0.000000</span> <span class="token">-</span><span class="token6">1.000000</span> <span class="token6">0.000000</span> usemtl Material_ray<span class="token1">.</span>png s off f <span class="token6">5</span><span class="token">/</span><span class="token6">1</span><span class="token">/</span><span class="token6">1</span> <span class="token6">1</span><span class="token">/</span><span class="token6">2</span><span class="token">/</span><span class="token6">1</span> <span class="token6">4</span><span class="token">/</span><span class="token6">3</span><span class="token">/</span><span class="token6">1</span> f <span class="token6">5</span><span class="token">/</span><span class="token6">1</span><span class="token">/</span><span class="token6">1</span> <span class="token6">4</span><span class="token">/</span><span class="token6">3</span><span class="token">/</span><span class="token6">1</span> <span class="token6">8</span><span class="token">/</span><span class="token6">4</span><span class="token">/</span><span class="token6">1</span> f <span class="token6">3</span><span class="token">/</span><span class="token6">5</span><span class="token">/</span><span class="token6">2</span> <span class="token6">7</span><span class="token">/</span><span class="token6">6</span><span class="token">/</span><span class="token6">2</span> <span class="token6">8</span><span class="token">/</span><span class="token6">7</span><span class="token">/</span><span class="token6">2</span> f <span class="token6">3</span><span class="token">/</span><span class="token6">5</span><span class="token">/</span><span class="token6">2</span> <span class="token6">8</span><span class="token">/</span><span class="token6">7</span><span class="token">/</span><span class="token6">2</span> <span class="token6">4</span><span class="token">/</span><span class="token6">8</span><span class="token">/</span><span class="token6">2</span> f <span class="token6">2</span><span class="token">/</span><span class="token6">9</span><span class="token">/</span><span class="token6">3</span> <span class="token6">6</span><span class="token">/</span><span class="token6">10</span><span class="token">/</span><span class="token6">3</span> <span class="token6">3</span><span class="token">/</span><span class="token6">5</span><span class="token">/</span><span class="token6">3</span> f <span class="token6">6</span><span class="token">/</span><span class="token6">10</span><span class="token">/</span><span class="token6">4</span> <span class="token6">7</span><span class="token">/</span><span class="token6">6</span><span class="token">/</span><span class="token6">4</span> <span class="token6">3</span><span class="token">/</span><span class="token6">5</span><span class="token">/</span><span class="token6">4</span> f <span class="token6">1</span><span class="token">/</span><span class="token6">2</span><span class="token">/</span><span class="token6">5</span> <span class="token6">5</span><span class="token">/</span><span class="token6">1</span><span class="token">/</span><span class="token6">5</span> <span class="token6">2</span><span class="token">/</span><span class="token6">9</span><span class="token">/</span><span class="token6">5</span> f <span class="token6">5</span><span class="token">/</span><span class="token6">1</span><span class="token">/</span><span class="token6">6</span> <span class="token6">6</span><span class="token">/</span><span class="token6">10</span><span class="token">/</span><span class="token6">6</span> <span class="token6">2</span><span class="token">/</span><span class="token6">9</span><span class="token">/</span><span class="token6">6</span> f <span class="token6">5</span><span class="token">/</span><span class="token6">1</span><span class="token">/</span><span class="token6">7</span> <span class="token6">8</span><span class="token">/</span><span class="token6">11</span><span class="token">/</span><span class="token6">7</span> <span class="token6">6</span><span class="token">/</span><span class="token6">10</span><span class="token">/</span><span class="token6">7</span> f <span class="token6">8</span><span class="token">/</span><span class="token6">11</span><span class="token">/</span><span class="token6">7</span> <span class="token6">7</span><span class="token">/</span><span class="token6">12</span><span class="token">/</span><span class="token6">7</span> <span class="token6">6</span><span class="token">/</span><span class="token6">10</span><span class="token">/</span><span class="token6">7</span> f <span class="token6">1</span><span class="token">/</span><span class="token6">2</span><span class="token">/</span><span class="token6">8</span> <span class="token6">2</span><span class="token">/</span><span class="token6">9</span><span class="token">/</span><span class="token6">8</span> <span class="token6">3</span><span class="token">/</span><span class="token6">13</span><span class="token">/</span><span class="token6">8</span> f <span class="token6">1</span><span class="token">/</span><span class="token6">2</span><span class="token">/</span><span class="token6">8</span> <span class="token6">3</span><span class="token">/</span><span class="token6">13</span><span class="token">/</span><span class="token6">8</span> <span class="token6">4</span><span class="token">/</span><span class="token6">14</span><span class="token">/</span><span class="token6">8</span> ``` ``` 因此: - # 是注释标记,就像C++中的// - usemtl和mtlib描述了模型的外观。本课用不到。 - v代表顶点 - vt代表顶点的纹理坐标 - vn代表顶点的法向 - f代表面 v vt vn都很好理解。f比较麻烦。例如f 8/11/7 7/12/7 6/10/7: - 8/11/7描述了三角形的第一个顶点 - 7/12/7描述了三角形的第二个顶点 - 6/10/7描述了三角形的第三个顶点 - 对于第一个顶点,8指向要用的顶点。此例中是-1.000000 1.000000 -1.000000(索引从1开始,和C++中从0开始不同) - 11指向要用的纹理坐标。此例中是0.748355 0.998230。 - 7指向要用的法向。此例中是0.000000 1.000000 -0.000000。 我们称这些数字为索引。若几个顶点共用同一个坐标,索引就显得很方便,文件中只需保存一个“V”,可以多次引用,节省了存储空间。 不好的地方在于,我们不能让OpenGL混用顶点、纹理和法向索引。因此本课采用的方法是创建一个标准的、未加索引的模型。等第九课时再讨论索引,届时将会介绍如何解决OpenGL的索引问题。 ### 用Blender创建OBJ文件 我们写的蹩脚加载器功能实在有限,因此在导出模型时得格外小心。下图展示了在Blender中导出模型的情形: ![](https://box.kancloud.cn/2015-11-02_5636f3051fbe2.png) ### 读取OBJ文件 OK,真正开始编码了。需要一些临时变量存储.obj文件的内容。 ``` <pre class="calibre16">``` std<span class="token1">:</span><span class="token1">:</span>vector vertexIndices<span class="token1">,</span> uvIndices<span class="token1">,</span> normalIndices<span class="token1">;</span> std<span class="token1">:</span><span class="token1">:</span>vector temp_vertices<span class="token1">;</span> std<span class="token1">:</span><span class="token1">:</span>vector temp_uvs<span class="token1">;</span> std<span class="token1">:</span><span class="token1">:</span>vector temp_normals<span class="token1">;</span> ``` ``` 学第五课纹理立方体时,你已学会如何打开文件了: ``` <pre class="calibre16">``` FILE <span class="token">*</span> file <span class="token">=</span> <span class="token3">fopen</span><span class="token1">(</span>path<span class="token1">,</span> <span class="token5">"r"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">if</span><span class="token1">(</span> file <span class="token">==</span> NULL <span class="token1">)</span><span class="token1">{</span> <span class="token3">printf</span><span class="token1">(</span><span class="token5">"Impossible to open the file !n"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> <span class="token6">false</span><span class="token1">;</span> <span class="token1">}</span> ``` ``` 读文件直到文件末尾: ``` <pre class="calibre16">``` <span class="token4">while</span><span class="token1">(</span> <span class="token6">1</span> <span class="token1">)</span><span class="token1">{</span> char lineHeader<span class="token1">[</span><span class="token6">128</span><span class="token1">]</span><span class="token1">;</span> <span class="token2">// read the first word of the line</span> int res <span class="token">=</span> <span class="token3">fscanf</span><span class="token1">(</span>file<span class="token1">,</span> <span class="token5">"%s"</span><span class="token1">,</span> lineHeader<span class="token1">)</span><span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span>res <span class="token">==</span> EOF<span class="token1">)</span> <span class="token4">break</span><span class="token1">;</span> <span class="token2">// EOF = End Of File. Quit the loop.</span> <span class="token2">// else : parse lineHeader</span> ``` ``` (注意,我们假设第一行的文字长度不超过128,这样做太愚蠢了。但既然这只是个实验品,就凑合一下吧) 首先处理顶点: ``` <pre class="calibre16">``` <span class="token4">if</span> <span class="token1">(</span> <span class="token3">strcmp</span><span class="token1">(</span> lineHeader<span class="token1">,</span> <span class="token5">"v"</span> <span class="token1">)</span> <span class="token">==</span> <span class="token6">0</span> <span class="token1">)</span><span class="token1">{</span> glm<span class="token1">:</span><span class="token1">:</span>vec3 vertex<span class="token1">;</span> <span class="token3">fscanf</span><span class="token1">(</span>file<span class="token1">,</span> <span class="token5">"%f %f %fn"</span><span class="token1">,</span> <span class="token">&</span>vertex<span class="token1">.</span>x<span class="token1">,</span> <span class="token">&</span>vertex<span class="token1">.</span>y<span class="token1">,</span> <span class="token">&</span>vertex<span class="token1">.</span>z <span class="token1">)</span><span class="token1">;</span> temp_vertices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>vertex<span class="token1">)</span><span class="token1">;</span> ``` ``` 也就是说,若第一个字是“v”,则后面一定是3个float值,于是以这3个值创建一个glm::vec3变量,将其添加到数组。 ``` <pre class="calibre16">``` <span class="token1">}</span><span class="token4">else</span> <span class="token4">if</span> <span class="token1">(</span> <span class="token3">strcmp</span><span class="token1">(</span> lineHeader<span class="token1">,</span> <span class="token5">"vt"</span> <span class="token1">)</span> <span class="token">==</span> <span class="token6">0</span> <span class="token1">)</span><span class="token1">{</span> glm<span class="token1">:</span><span class="token1">:</span>vec2 uv<span class="token1">;</span> <span class="token3">fscanf</span><span class="token1">(</span>file<span class="token1">,</span> <span class="token5">"%f %fn"</span><span class="token1">,</span> <span class="token">&</span>uv<span class="token1">.</span>x<span class="token1">,</span> <span class="token">&</span>uv<span class="token1">.</span>y <span class="token1">)</span><span class="token1">;</span> temp_uvs<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>uv<span class="token1">)</span><span class="token1">;</span> ``` ``` 也就是说,如果不是“v”而是“vt”,那后面一定是2个float值,于是以这2个值创建一个glm::vec2变量,添加到数组。 以同样的方式处理法向: ``` <pre class="calibre16">``` <span class="token1">}</span><span class="token4">else</span> <span class="token4">if</span> <span class="token1">(</span> <span class="token3">strcmp</span><span class="token1">(</span> lineHeader<span class="token1">,</span> <span class="token5">"vn"</span> <span class="token1">)</span> <span class="token">==</span> <span class="token6">0</span> <span class="token1">)</span><span class="token1">{</span> glm<span class="token1">:</span><span class="token1">:</span>vec3 normal<span class="token1">;</span> <span class="token3">fscanf</span><span class="token1">(</span>file<span class="token1">,</span> <span class="token5">"%f %f %fn"</span><span class="token1">,</span> <span class="token">&</span>normal<span class="token1">.</span>x<span class="token1">,</span> <span class="token">&</span>normal<span class="token1">.</span>y<span class="token1">,</span> <span class="token">&</span>normal<span class="token1">.</span>z <span class="token1">)</span><span class="token1">;</span> temp_normals<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>normal<span class="token1">)</span><span class="token1">;</span> ``` ``` 接下来是“f”,略难一些: ``` <pre class="calibre16">``` <span class="token1">}</span><span class="token4">else</span> <span class="token4">if</span> <span class="token1">(</span> <span class="token3">strcmp</span><span class="token1">(</span> lineHeader<span class="token1">,</span> <span class="token5">"f"</span> <span class="token1">)</span> <span class="token">==</span> <span class="token6">0</span> <span class="token1">)</span><span class="token1">{</span> std<span class="token1">:</span><span class="token1">:</span>string vertex1<span class="token1">,</span> vertex2<span class="token1">,</span> vertex3<span class="token1">;</span> unsigned int vertexIndex<span class="token1">[</span><span class="token6">3</span><span class="token1">]</span><span class="token1">,</span> uvIndex<span class="token1">[</span><span class="token6">3</span><span class="token1">]</span><span class="token1">,</span> normalIndex<span class="token1">[</span><span class="token6">3</span><span class="token1">]</span><span class="token1">;</span> int matches <span class="token">=</span> <span class="token3">fscanf</span><span class="token1">(</span>file<span class="token1">,</span> <span class="token5">"%d/%d/%d %d/%d/%d %d/%d/%dn"</span><span class="token1">,</span> <span class="token">&</span>vertexIndex<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>uvIndex<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>normalIndex<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>vertexIndex<span class="token1">[</span><span class="token6">1</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>uvIndex<span class="token1">[</span><span class="token6">1</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>normalIndex<span class="token1">[</span><span class="token6">1</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>vertexIndex<span class="token1">[</span><span class="token6">2</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>uvIndex<span class="token1">[</span><span class="token6">2</span><span class="token1">]</span><span class="token1">,</span> <span class="token">&</span>normalIndex<span class="token1">[</span><span class="token6">2</span><span class="token1">]</span> <span class="token1">)</span><span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span>matches <span class="token">!=</span> <span class="token6">9</span><span class="token1">)</span><span class="token1">{</span> <span class="token3">printf</span><span class="token1">(</span><span class="token5">"File can't be read by our simple parser : ( Try exporting with other optionsn"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> <span class="token6">false</span><span class="token1">;</span> <span class="token1">}</span> vertexIndices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>vertexIndex<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> vertexIndices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>vertexIndex<span class="token1">[</span><span class="token6">1</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> vertexIndices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>vertexIndex<span class="token1">[</span><span class="token6">2</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> uvIndices <span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>uvIndex<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> uvIndices <span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>uvIndex<span class="token1">[</span><span class="token6">1</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> uvIndices <span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>uvIndex<span class="token1">[</span><span class="token6">2</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> normalIndices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>normalIndex<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> normalIndices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>normalIndex<span class="token1">[</span><span class="token6">1</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> normalIndices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>normalIndex<span class="token1">[</span><span class="token6">2</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> ``` ``` 代码与前面的类似,只不过读取的数据多一些。 ### 处理数据 我们只需改变一下数据的形式。读取的是字符串,现在有了一组数组。这还不够,我们得把数据组织成OpenGL要求的形式。也就是去掉索引,只保留顶点坐标数据。这步操作称为索引。 遍历每个三角形(每个“f”行)的每个顶点(每个 v/vt/vn): ``` <pre class="calibre16">``` <span class="token2">// For each vertex of each triangle</span> <span class="token4">for</span><span class="token1">(</span> unsigned int i<span class="token">=</span><span class="token6">0</span><span class="token1">;</span> i ``` ``` 顶点坐标的索引存放到vertexIndices\[i\]: ``` <pre class="calibre16">``` unsigned int vertexIndex <span class="token">=</span> vertexIndices<span class="token1">[</span>i<span class="token1">]</span><span class="token1">;</span> ``` ``` 因此坐标是temp\_vertices\[ vertexIndex-1 \](-1是因为C++的下标从0开始,而OBJ的索引从1开始,还记得吗?): ``` <pre class="calibre16">``` glm<span class="token1">:</span><span class="token1">:</span>vec3 vertex <span class="token">=</span> temp_vertices<span class="token1">[</span> vertexIndex<span class="token">-</span><span class="token6">1</span> <span class="token1">]</span><span class="token1">;</span> ``` ``` 这样就有了一个顶点坐标: ``` <pre class="calibre16">``` out_vertices<span class="token1">.</span><span class="token3">push_back</span><span class="token1">(</span>vertex<span class="token1">)</span><span class="token1">;</span> ``` ``` UV和法向同理,任务完成! ## 使用加载的数据 到这一步,几乎什么变化都没发生。这次我们不再声明一个static const GLfloat g\_vertex\_buffer\_data\[\] = {…},而是创建一个顶点数组(UV和法向同理)。用正确的参数调用loadOBJ: ``` <pre class="calibre16">``` <span class="token2">// Read our .obj file</span> std<span class="token1">:</span><span class="token1">:</span>vector vertices<span class="token1">;</span> std<span class="token1">:</span><span class="token1">:</span>vector uvs<span class="token1">;</span> std<span class="token1">:</span><span class="token1">:</span>vector normals<span class="token1">;</span> <span class="token2">// Won't be used at the moment.</span> bool res <span class="token">=</span> <span class="token3">loadOBJ</span><span class="token1">(</span><span class="token5">"cube.obj"</span><span class="token1">,</span> vertices<span class="token1">,</span> uvs<span class="token1">,</span> normals<span class="token1">)</span><span class="token1">;</span> ``` ``` 把数组传给OpenGL: ``` <pre class="calibre16">``` <span class="token3">glBufferData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> vertices<span class="token1">.</span><span class="token3">size</span><span class="token1">(</span><span class="token1">)</span> <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>glm<span class="token1">:</span><span class="token1">:</span>vec3<span class="token1">)</span><span class="token1">,</span> <span class="token">&</span>vertices<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">,</span> GL_STATIC_DRAW<span class="token1">)</span><span class="token1">;</span> ``` ``` 结束了! ## 结果 不好意思,纹理不好看。我不太擅长美工。欢迎您来提供一些好的纹理。 ## 其他模型格式及加载器 这个小巧的加载器应该比较适合初学,不过别在实际中使用它。参考一下[实用链接和工具](http://www.opengl-tutorial.org/miscellaneous/useful-tools-links/)页面,看看有什么能用的。不过请注意,等到第九课才会真正用到这些工具。