とても手っ取り早く、既存のモデルをロードすることで、手製で作成するかわりにしたいと思います。 
Blenderからモデルをインポートしてみましょう。 
どうやら直接.blenderファイルをロードする簡単​​な方法はないようですが、次のいずれかの方法で行えます： 
OBJローダーを書くことは良いエクササイズになると思うので、その方法で始めましょう。 
thumb|Blender screenshot
スザンヌは、Blenderのテストモデルです。  これは500ポリゴンを持っていて、私たちのテストにも適しています。 
それを作成するため、Blenderを実行し（バージョン2.58を使用しています）、その後： 
Blenderは2つのファイル、suzanne.objとsuzanne.mtlを作成します： 
今からメッシュをロードしていきます。 
.objファイルをテキストエディタで調べてみましょう。   フォーマットは非常にシンプルであることがわかります： 
C言語の配列をいくつか移入する必要があります： 
このフォーマットは他にも特徴を持っていますが、今のところそれらは脇に残しておきます。 
ここにあるのは最初の、荒削りな実装ですが、私たちのオブジェクトには役目を果たしてくれるはずです。 
私たちのパーサには制限がありますが（複数のオブジェクトのサポートもなく、別の頂点フォーマット、ポリゴンなども）、私たちの当面のニーズのためには十分です。 
void load_obj(const char* filename, vector &vertices, vector &normals, vector &elements) {
  ifstream in(filename, ios::in);
  if (!in) { cerr << "Cannot open " << filename << endl; exit(1); }
  string line;
  while (getline(in, line)) {
    if (line.substr(0,2) == "v ") {
      istringstream s(line.substr(2));
      glm::vec4 v; s >> v.x; s >> v.y; s >> v.z; v.w = 1.0f;
      vertices.push_back(v);
    }  else if (line.substr(0,2) == "f ") {
      istringstream s(line.substr(2));
      GLushort a,b,c;
      s >> a; s >> b; s >> c;
      a--; b--; c--;
      elements.push_back(a); elements.push_back(b); elements.push_back(c);
    }
    else if (line[0] == '#') { /* ignoring this line */ }
    else { /* ignoring this line */ }
  }
  normals.resize(mesh->vertices.size(), glm::vec3(0.0, 0.0, 0.0));
  for (int i = 0; i < elements.size(); i+=3) {
    GLushort ia = elements[i];
    GLushort ib = elements[i+1];
    GLushort ic = elements[i+2];
    glm::vec3 normal = glm::normalize(glm::cross(
      glm::vec3(vertices[ib]) - glm::vec3(vertices[ia]),
      glm::vec3(vertices[ic]) - glm::vec3(vertices[ia])));
    normals[ia] = normals[ib] = normals[ic] = normal;
  }
}
C++のベクターを用いて、メモリ管理をシンプルにしています。 
引数は参照で渡していて、その主な理由はベクターへのポインタにアクセスするための構文が恐ろしいことになるからです  ((*elements)[i])
.objファイルはこのような方法でロードすることができます: 
  vector suzanne_vertices;
  vector suzanne_normals;
  vector suzanne_elements;
  [...]
  load_obj("suzanne.obj", suzanne_vertices, suzanne_normals, suzanne_elements);
そして以下を使用してOpenGLに渡します： 
  glEnableVertexAttribArray(attribute_v_coord);
  // Describe our vertices array to OpenGL (it can't guess its format automatically)
  glBindBuffer(GL_ARRAY_BUFFER, vbo_mesh_vertices);
  glVertexAttribPointer(
    attribute_v_coord,  // attribute
    4,                  // number of elements per vertex, here (x,y,z,w)
    GL_FLOAT,           // the type of each element
    GL_FALSE,           // take our values as-is
    0,                  // no extra data between each position
    0                   // offset of first element
  );
  glBindBuffer(GL_ARRAY_BUFFER, vbo_mesh_normals);
  glVertexAttribPointer(
    attribute_v_normal, // attribute
    3,                  // number of elements per vertex, here (x,y,z)
    GL_FLOAT,           // the type of each element
    GL_FALSE,           // take our values as-is
    0,                  // no extra data between each position
    0                   // offset of first element
  );
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_mesh_elements);
  int size;  glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);  
  glDrawElements(GL_TRIANGLES, size/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
thumb|Suzanne, now in our application
最後に、適宜見え方を調整し、Y-is-top座標系にして、カメラがスザンヌ向くようにします： 
  glm::mat4 view = glm::lookAt(
    glm::vec3(0.0,  2.0, 4.0),   // eye
    glm::vec3(0.0,  0.0, 0.0),   // direction
    glm::vec3(0.0,  1.0, 0.0));  // up
  glm::mat4 projection = glm::perspective(45.0f, 1.0f*screen_width/screen_height, 0.1f, 100.0f);
私は少し抜けがけをしてグロー照明モデルを実装しました。  これはこの先で少し取り上げます。 
thumb|Suzanne with flat-shading
texturing tutorialで説明したように 、時には同じ頂点が使用される面に応じて異なる値になることがあります。   これが当てはまるのは、法線を共有したくない場合です（そして上記したように、頂点の法線を計算するときに任意の面を選択する）。   その場合は、別の法線と一緒に使用されるごとに頂点を複製し、そのあと要素配列を再生成する必要があります。  こうするとロードにかかる時間は多くなりますが、OpenGLの長期的な処理では速くなります。   OpenGLに送信される頂点は少ないほうがベターです。  または、先に述べたように、この例では、それらが表示される時間の頂点だけを複製することもできます。次からは要素の配列なしで作業を進めることができます。 
  for (int i = 0; i < elements.size(); i++) {
    vertices.push_back(shared_vertices[elements[i]]);
    if ((i % 3) == 2) {
      GLushort ia = elements[i-2];
      GLushort ib = elements[i-1];
      GLushort ic = elements[i];
      glm::vec3 normal = glm::normalize(glm::cross(
        shared_vertices[ic] - shared_vertices[ia],
	shared_vertices[ib] - shared_vertices[ia]));
      for (int n = 0; n < 3; n++)
	normals.push_back(normal);
    }
  }
  glDrawArrays(GL_TRIANGLES, 0, suzanne_vertices.size());
このセットアップをつかえば、フラットシェーディングを得ることができます： varying変数は実際にはフラグメントシェーダ内の頂点の間で変化することはなく、その理由は法線がすべての三角形の3つの頂点で同じになるからです。 
私たちのアルゴリズムは機能していますが、二つの面が同じベクトルを参照する場合、最後の面がその頂点の法線を上書きしてしまいます。   これは、面の順番に応じてオブジェクトが全く異なって見えることを意味します。 
この問題の解決法は、2つ​​の面の間の法線を平均化することです。   2つのベクトルを平均化するには、最初のベ​​クトルの半分に2番目のベクトルの半分を加えたものを求めます。  ここではnb_seenを使用してベクトル係数を格納するので、ベクトルのリストが満杯まで格納されなければ、新しいベクトルを何度でも平均することができます： 
thumb|Normals averaging
  mesh->normals.resize(mesh->vertices.size(), glm::vec3(0.0, 0.0, 0.0));
  nb_seen.resize(mesh->vertices.size(), 0);
  for (int i = 0; i < mesh->elements.size(); i+=3) {
    GLushort ia = mesh->elements[i];
    GLushort ib = mesh->elements[i+1];
    GLushort ic = mesh->elements[i+2];
    glm::vec3 normal = glm::normalize(glm::cross(
      glm::vec3(mesh->vertices[ib]) - glm::vec3(mesh->vertices[ia]),
      glm::vec3(mesh->vertices[ic]) - glm::vec3(mesh->vertices[ia])));
    int v[3];  v[0] = ia;  v[1] = ib;  v[2] = ic;
    for (int j = 0; j < 3; j++) {
      GLushort cur_v = v[j];
      nb_seen[cur_v]++;
      if (nb_seen[cur_v] == 1) {
	mesh->normals[cur_v] = normal;
      } else {
	// average
	mesh->normals[cur_v].x = mesh->normals[cur_v].x * (1.0 - 1.0/nb_seen[cur_v]) + normal.x * 1.0/nb_seen[cur_v];
	mesh->normals[cur_v].y = mesh->normals[cur_v].y * (1.0 - 1.0/nb_seen[cur_v]) + normal.y * 1.0/nb_seen[cur_v];
	mesh->normals[cur_v].z = mesh->normals[cur_v].z * (1.0 - 1.0/nb_seen[cur_v]) + normal.z * 1.0/nb_seen[cur_v];
	mesh->normals[cur_v] = glm::normalize(mesh->normals[cur_v]);
      }
    }
  }
TODO: improve the parser to support .obj normals
Obj形式は事前計算法線をサポートしています。   興味深いことにそれらは面に指定されているので、頂点がいくつかの面に存在している場合、別の法線になってしまうことがあるので、上述の頂点重複のテクニックを使用しなければならないことを意味します。 
例えば、基本的なエクスポートのスザンヌが、2つの異なる法線＃1と＃7といっしょに＃1の頂点を参照するとき： 
 v 0.437500 0.164063 0.765625
 ...
 vn 0.664993 -0.200752 0.719363
 ...
 f 47//1 1//1 3//1
 ...
 f 1//7 11//7 9//7
 f 1//7 9//7 3//7
比較すると、MD2/MD3フォーマット（とりわけQuakeなどで使用される）は事前計算法線を含みますが、それらは頂点ではなく面に接続されています。 
OpenGL Programming/Modern OpenGL Tutorial Load OBJ