第七章:3D模型渲染

原文鏈接:


Tutorial 7: 3D Model Rendering
第七章:3D模型渲染

This tutorial will cover how to render 3D models in OpenGL 4.0 using GLSL. The code in this tutorial is based on the code from the diffuse lighting tutorial.
本章將介紹如何在OpenGL 4.0中使用GLSL渲染3D模型。本章的代碼將在漫反射光照教程的基礎上進行修改。

We have already been rendering 3D models in the previous tutorials, however they were composed of a single triangle and were fairly uninteresting. Now that the basics have been covered we'll move forward to render a more complex object. In this case the object will be a cube. Before we get into how to render more complex models we will first talk about model formats.
其實前面的教程已經包含了3D模型的渲染,只是用了簡單的三角形。本章我們將渲染一個更加複雜的模型。本章將使用一個盒子。在介紹渲染模型前,先來討論下模型格式。

There are many tools available that allow users to create 3D models. Maya and 3D Studio Max are two of the more popular 3D modeling programs. There are also many other tools with less features but still can do the basics for what we need.
有很多工具可以用來創建3D模型。Maya和3D Studio Max是兩個非常有名的3D建模工具。當然也有很多其他的工具也可以使用。

Regardless of which tool you choose to use they will all export their models into numerous different formats. My suggestion is that you create your own model format and write a parser to convert their export format into your own format. The reason for this is that the 3D modeling package that you use may change over time and their model format will also change. Also you may be using more than one 3D modeling package so you will have multiple different formats to deal with. So if you have your own format and convert their formats to your own then your code will never need to change. You will only need to change your parser program(s) for changing those formats to your own. As well most 3D modeling packages export a ton of junk that is only useful to that modeling program and you don't need any of it in your model format.
不管你選擇使用哪種工具,他們應該都支持將模型到處爲多種不同的格式。我建議你創建自己的模型格式,然後編寫轉換器將導出的模型轉換爲你自己的格式。原因是3D模型包可能會根據時間變化而變化。也有可能你會使用多種不同格式的3D模型包。如果你有自己的格式和轉換格式的工具,那麼你的(框架)代碼就不需要修改。你只需要修改格式轉換工具就可以了。另外,很多導出的3D模型包包含大量程序中不需要的冗餘數據,在你的模型格式裏可以丟棄這些數據。

The most important part to making your own format is that it covers everything you need it to do and that it is simple for you to use. You can also consider making a couple different formats for different objects as some may have animation data, some may be static, and so forth.
製作你自己格式的重要原則是要方便自己使用。你也可以製作多種不同的格式,有的包含動畫數據,有的是靜態的等等。

The model format I'm going to present is very basic. It will contain a line for each vertex in the model. Each line will match the vertex format used in the code which will be position vector (x, y, z), texture coordinates (tu, tv), and the normal vector (nx, ny, nz). The format will also have the vertex count at the top so you can read the first line and build the memory structures needed before reading in the data. The format will also require that every three lines make a triangle, and that the vertices in the model format are presented in clockwise order. Here is the model file for the cube we are going to render:
我使用的模型格式非常簡單。每個頂點的數據佔一行。每個頂點包含了座標向量(x, y, z),紋理座標(tu, tv)和法線向量(nx, ny, nz)。格式在頭部聲明瞭頂點數量,這樣我們就可以申請對應大小的內存來存放數據。這個格式每3行定義一個三角形,頂點使用了順時針的順序。下面時盒子模型文件的內容:

Cube.txt

Vertex Count: 36

Data:

-1.0  1.0 -1.0 0.0 0.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
 1.0 -1.0 -1.0 1.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 0.0 0.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0  1.0 1.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 0.0 0.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
-1.0 -1.0  1.0 1.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 0.0 0.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0 -1.0 1.0 1.0 -1.0  0.0  0.0
-1.0  1.0  1.0 0.0 0.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
 1.0  1.0 -1.0 1.0 1.0  0.0  1.0  0.0
-1.0 -1.0 -1.0 0.0 0.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
 1.0 -1.0  1.0 1.0 1.0  0.0 -1.0  0.0

So as you can see there are 36 lines of x, y, z, tu, tv, nx, ny, nz data. Every three lines composes its own triangle giving us 12 triangles that will form a cube. The format is very straight forward and can be read directly into our vertex buffers and rendered without any modifications.
你可以看到有36行的x, y, z, tu, tv, nx, ny, nz數據。每3行組成一個三角形,共由12個三角形組成一個盒子。這個格式很直白,不需要任何修改就可以生成我們要用的頂點緩衝區數據並進行渲染。

Now one thing to watch out for is that some 3D modeling programs export the data in different orders such as left hand or right hand coordinate systems. We have setup OpenGL 4.0 to use a left-handed coordinate system and so the model data needs to match that. Keep an eye out for those differences and ensure your parsing program can handle converting data into the correct format/order.
還有件事要注意,有些3D模型工具導出的數據順序會根據使用不同的座標系統而不同。OpenGL 4.0使用左手座標系統,因此模型數據需要與之對應。留意這些差異,確保你的格式解析程序可以處理正確的格式和順序。

Modelclass.h

For this tutorial all we needed to do was make some minor changes to the ModelClass for it to render 3D models from our text model files.
本章我們只對ModelClass做很小的修改來加載要渲染的3D模型的文本文件。
////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

The fstream library is now included to handle reading from the model text file.
fstream庫用來處理從文本文件讀數據。
//////////////
// INCLUDES //
//////////////
#include <fstream>
using namespace std;

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"

////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
 struct VertexType
 {
  float x, y, z;
  float tu, tv;
  float nx, ny, nz;
 };

The next change is the addition of a new structure to represent the model format. It is called ModelType. It contains position, texture, and normal vectors the same as our file format does.
下面添加一個新的結構ModelType來對應模型的格式。它包含位置、紋理座標和法向量。
 struct ModelType
 {
  float x, y, z;
  float tu, tv;
  float nx, ny, nz;
 };

public:
 ModelClass();
 ModelClass(const ModelClass&);
 ~ModelClass();

The Initialize function will now take as input the character string file name of the model to be loaded.
Initialize方法增加了要加載的模型文件的文件名作爲輸入參數。
bool Initialize(OpenGLClass*, char*, char*, unsigned int, bool);
 void Shutdown(OpenGLClass*);
 void Render(OpenGLClass*);

private:
 bool InitializeBuffers(OpenGLClass*);
 void ShutdownBuffers(OpenGLClass*);
 void RenderBuffers(OpenGLClass*);

 bool LoadTexture(OpenGLClass*, char*, unsigned int, bool);
 void ReleaseTexture();

We also have two new functions to handle loading and unloading the model data from the text file.
下面時兩個新的方法用來加載和釋放模型數據文件。
bool LoadModel(char*);
 void ReleaseModel();

private:
 int m_vertexCount, m_indexCount;
 unsigned int m_vertexArrayId, m_vertexBufferId, m_indexBufferId;
 TextureClass* m_Texture;
The final change is a new private variable called m_model which is going to be an array of the new private structure ModelType. This variable will be used to read in and hold the model data before it is placed in the vertex buffer.
最後一關修改的地方時新增了私有變量m_model,用來指向ModelType結構數組的指針。

ModelType* m_model;
};

#endif

Modelclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"

ModelClass::ModelClass()
{
 m_Texture = 0;

The new model structure is set to null in the class constructor.
在構造方法裏,將m_model設置爲null。
 m_model = 0;
}

ModelClass::ModelClass(const ModelClass& other)
{
}

ModelClass::~ModelClass()
{
}

The Initialize function now takes as input the file name of the model that should be loaded.
Initialize方法增加了模型文件名作爲輸入參數。
bool ModelClass::Initialize(OpenGLClass* OpenGL, char* modelFilename, char* textureFilename, unsigned int textureUnit, bool wrap)
{
 bool result;
In the Initialize function we now call the new LoadModel function first. It will load the model data from the file name we provide into the new m_model array. Once this model array is filled we can then build the vertex and index buffers from it. Since InitializeBuffers now depends on this model data you have to make sure to call the functions in the correct order.
在Initialize方法裏,我們首先調用LoadModel方法。LoadModel方法通過模型文件名加載模型數據到m_model。模型加載好後,我們可以使用它填充頂點和索引緩衝區數據。InitializeBuffers方法現在依賴於加載的模型數據,因此要保證方法以正確的順序進行調用。
// Load in the model data.
 result = LoadModel(modelFilename);
 if(!result)
 {
  return false;
 }

 // Initialize the vertex and index buffers that hold the geometry for the model.
 result = InitializeBuffers(OpenGL);
 if(!result)
 {
  return false;
 }

 // Load the texture for this model.
 result = LoadTexture(OpenGL, textureFilename, textureUnit, wrap);
 if(!result)
 {
  return false;
 }

 return true;
}

void ModelClass::Shutdown(OpenGLClass* OpenGL)
{
 // Release the texture used for this model.
 ReleaseTexture();

 // Release the vertex and index buffers.
 ShutdownBuffers(OpenGL);

In the Shutdown function we add a call to the ReleaseModel function to delete the m_model array data once we are done.
Shutdown方法我們增加了調用ReleaseModel方法的代碼。

 // Release the model data.
 ReleaseModel();

 return;
}

void ModelClass::Render(OpenGLClass* OpenGL)
{
 // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
 RenderBuffers(OpenGL);

 return;
}

bool ModelClass::InitializeBuffers(OpenGLClass* OpenGL)
{
 VertexType* vertices;
 unsigned int* indices;
 int i;

Take note that we will no longer manually set the vertex and index count here. Once we get to the ModelClass::LoadModel function you will see that we read the vertex and index counts in at that point instead.
這裏我們不需要手動設置頂點和索引數量。ModelClass::LoadModel方法我們將讀取頂點和索引數量。
 // Create the vertex array.
 vertices = new VertexType[m_vertexCount];
 if(!vertices)
 {
  return false;
 }

 // Create the index array.
 indices = new unsigned int[m_indexCount];
 if(!indices)
 {
  return false;
 }

Loading the vertex and index arrays has changed a bit. Instead of setting the values manually we loop through all the elements in the new m_model array and copy that data from there into the vertex array. The index array is easy to build as each vertex we load has the same index number as the position in the array it was loaded into. One final thing to note is that the model format used does need the tv texture coordinate reversed for OpenGL.
加載頂點和索引數組的方式進行了小幅修改。這裏使用循環方式從模型數據數組裏將數據拷貝到頂點數組。索引數組很好創建,因爲頂點的順序就是索引順序。最後注意,模型格式裏的紋理座標tv值與OpenGL中用的相反。
 // Load the vertex array and index array with data.
 for(i=0; i<m_vertexCount; i++)
 {
  vertices[i].x  = m_model[i].x;
  vertices[i].y  = m_model[i].y;
  vertices[i].z  = m_model[i].z;
  vertices[i].tu = m_model[i].tu;
  vertices[i].tv = 1.0f - m_model[i].tv;
  vertices[i].nx = m_model[i].nx;
  vertices[i].ny = m_model[i].ny;
  vertices[i].nz = m_model[i].nz;

  indices[i] = i;
 }

 // Allocate an OpenGL vertex array object.
 OpenGL->glGenVertexArrays(1, &m_vertexArrayId);

 // Bind the vertex array object to store all the buffers and vertex attributes we create here.
 OpenGL->glBindVertexArray(m_vertexArrayId);

 // Generate an ID for the vertex buffer.
 OpenGL->glGenBuffers(1, &m_vertexBufferId);

 // Bind the vertex buffer and load the vertex (position, texture, and normal) data into the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glBufferData(GL_ARRAY_BUFFER, m_vertexCount * sizeof(VertexType), vertices, GL_STATIC_DRAW);

 // Enable the three vertex array attributes.
 OpenGL->glEnableVertexAttribArray(0);  // Vertex position.
 OpenGL->glEnableVertexAttribArray(1);  // Texture coordinates.
 OpenGL->glEnableVertexAttribArray(2);  // Normals.

 // Specify the location and format of the position portion of the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(VertexType), 0);

 // Specify the location and format of the texture coordinate portion of the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (3 * sizeof(float)));

 // Specify the location and format of the normal vector portion of the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
 OpenGL->glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (5 * sizeof(float)));

 // Generate an ID for the index buffer.
 OpenGL->glGenBuffers(1, &m_indexBufferId);

 // Bind the index buffer and load the index data into it.
 OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId);
 OpenGL->glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount* sizeof(unsigned int), indices, GL_STATIC_DRAW);
 
 // Now that the buffers have been loaded we can release the array data.
 delete [] vertices;
 vertices = 0;

 delete [] indices;
 indices = 0;

 return true;
}

void ModelClass::ShutdownBuffers(OpenGLClass* OpenGL)
{
 // Disable the two vertex array attributes.
 OpenGL->glDisableVertexAttribArray(0);
 OpenGL->glDisableVertexAttribArray(1);
 
 // Release the vertex buffer.
 OpenGL->glBindBuffer(GL_ARRAY_BUFFER, 0);
 OpenGL->glDeleteBuffers(1, &m_vertexBufferId);

 // Release the index buffer.
 OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
 OpenGL->glDeleteBuffers(1, &m_indexBufferId);

 // Release the vertex array object.
 OpenGL->glBindVertexArray(0);
 OpenGL->glDeleteVertexArrays(1, &m_vertexArrayId);

 return;
}

void ModelClass::RenderBuffers(OpenGLClass* OpenGL)
{
 // Bind the vertex array object that stored all the information about the vertex and index buffers.
 OpenGL->glBindVertexArray(m_vertexArrayId);

 // Render the vertex buffer using the index buffer.
 glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, 0);

 return;
}

bool ModelClass::LoadTexture(OpenGLClass* OpenGL, char* textureFilename, unsigned int textureUnit, bool wrap)
{
 bool result;

 // Create the texture object.
 m_Texture = new TextureClass;
 if(!m_Texture)
 {
  return false;
 }

 // Initialize the texture object.
 result = m_Texture->Initialize(OpenGL, textureFilename, textureUnit, wrap);
 if(!result)
 {
  return false;
 }

 return true;
}

void ModelClass::ReleaseTexture()
{
 // Release the texture object.
 if(m_Texture)
 {
  m_Texture->Shutdown();
  delete m_Texture;
  m_Texture = 0;
 }

 return;
}

This is the new LoadModel function which handles loading the model data from the text file into the m_model array variable. It opens the text file and reads in the vertex count first. After reading the vertex count it creates the ModelType array and then reads each line into the array. Both the vertex count and index count are now set in this function.
下面時新增的LoadModel方法,用來從模型文件加載模型數據到m_model數組變量。它先打開文件,並讀取頂點數量。然後根據頂點數量創建ModelType數組並將每行頂點數據讀入數組。頂點數量和頂點數組都已經設置好了。
bool ModelClass::LoadModel(char* filename)
{
 ifstream fin;
 char input;
 int i;

 // Open the model file.
 fin.open(filename);
 
 // If it could not open the file then exit.
 if(fin.fail())
 {
  return false;
 }

 // Read up to the value of vertex count.
 fin.get(input);
 while(input != ':')
 {
  fin.get(input);
 }

 // Read in the vertex count.
 fin >> m_vertexCount;

 // Set the number of indices to be the same as the vertex count.
 m_indexCount = m_vertexCount;

 // Create the model using the vertex count that was read in.
 m_model = new ModelType[m_vertexCount];
 if(!m_model)
 {
  return false;
 }

 // Read up to the beginning of the data.
 fin.get(input);
 while(input != ':')
 {
  fin.get(input);
 }
 fin.get(input);
 fin.get(input);

 // Read in the vertex data.
 for(i=0; i<m_vertexCount; i++)
 {
  fin >> m_model[i].x >> m_model[i].y >> m_model[i].z;
  fin >> m_model[i].tu >> m_model[i].tv;
  fin >> m_model[i].nx >> m_model[i].ny >> m_model[i].nz;
 }

 // Close the model file.
 fin.close();

 return true;
}

The ReleaseModel function handles deleting the model data array.
ReleaseModel方法刪除模型數據數組。
void ModelClass::ReleaseModel()
{
 if(m_model)
 {
  delete [] m_model;
  m_model = 0;
 }

 return;
}

Graphicsclass.h

The header for the GraphicsClass has not changed since the previous tutorial.
GraphicsClass的頭部有修改。
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "openglclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"

/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
 GraphicsClass();
 GraphicsClass(const GraphicsClass&);
 ~GraphicsClass();

 bool Initialize(OpenGLClass*, HWND);
 void Shutdown();
 bool Frame();

private:
 bool Render(float);

private:
 OpenGLClass* m_OpenGL;
 CameraClass* m_Camera;
 ModelClass* m_Model;
 LightShaderClass* m_LightShader;
 LightClass* m_Light;
};

#endif

Graphicsclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

GraphicsClass::GraphicsClass()
{
 m_OpenGL = 0;
 m_Camera = 0;
 m_Model = 0;
 m_LightShader = 0;
 m_Light = 0;
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(OpenGLClass* OpenGL, HWND hwnd)
{
 bool result;

 // Store a pointer to the OpenGL class object.
 m_OpenGL = OpenGL;

 // Create the camera object.
 m_Camera = new CameraClass;
 if(!m_Camera)
 {
  return false;
 }

 // Set the initial position of the camera.
 m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

 // Create the model object.
 m_Model = new ModelClass;
 if(!m_Model)
 {
  return false;
 }

The model initialization now takes in the filename of the model file it is loading. In this tutorial we will use the cube.txt file so this model loads in a 3D cube object for rendering. Also note that we are using a new texture called opengl.tga instead of the stone texture from the previous tutorial.
模型初始化現在增加了模型文件名作爲參數。本章我們使用cube.txt文件,所以將加載3D盒子進行渲染。注意,我們使用了新的紋理opengl.tga替換前面教程的紋理。
 // Initialize the model object.
 result = m_Model->Initialize(m_OpenGL,  "../Engine/data/cube.txt", "../Engine/data/opengl.tga", 0, true);
 if(!result)
 {
  MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
  return false;
 }
 
 // Create the light shader object.
 m_LightShader = new LightShaderClass;
 if(!m_LightShader)
 {
  return false;
 }

 // Initialize the light shader object.
 result = m_LightShader->Initialize(m_OpenGL, hwnd);
 if(!result)
 {
  MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
  return false;
 }

 // Create the light object.
 m_Light = new LightClass;
 if(!m_Light)
 {
  return false;
 }

I have changed the diffuse light color to white for this tutorial.
我將漫反射光照的顏色修改爲白色。
 // Initialize the light object.
 m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
 m_Light->SetDirection(0.0f, 0.0f, 1.0f);

 return true;
}

void GraphicsClass::Shutdown()
{
 // Release the light object.
 if(m_Light)
 {
  delete m_Light;
  m_Light = 0;
 }

 // Release the light shader object.
 if(m_LightShader)
 {
  m_LightShader->Shutdown(m_OpenGL);
  delete m_LightShader;
  m_LightShader = 0;
 }

 // Release the model object.
 if(m_Model)
 {
  m_Model->Shutdown(m_OpenGL);
  delete m_Model;
  m_Model = 0;
 }

 // Release the camera object.
 if(m_Camera)
 {
  delete m_Camera;
  m_Camera = 0;
 }

 // Release the pointer to the OpenGL class object.
 m_OpenGL = 0;

 return;
}

bool GraphicsClass::Frame()
{
 bool result;
 static float rotation = 0.0f;

 // Update the rotation variable each frame.
 rotation += 0.0174532925f * 2.0f;
 if(rotation > 360.0f)
 {
  rotation -= 360.0f;
 }

 // Render the graphics scene.
 result = Render(rotation);
 if(!result)
 {
  return false;
 }

 return true;
}

bool GraphicsClass::Render(float rotation)
{
 float worldMatrix[16];
 float viewMatrix[16];
 float projectionMatrix[16];
 float lightDirection[3];
 float diffuseLightColor[4];

 // Clear the buffers to begin the scene.
 m_OpenGL->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

 // Generate the view matrix based on the camera's position.
 m_Camera->Render();

 // Get the world, view, and projection matrices from the opengl and camera objects.
 m_OpenGL->GetWorldMatrix(worldMatrix);
 m_Camera->GetViewMatrix(viewMatrix);
 m_OpenGL->GetProjectionMatrix(projectionMatrix);

 // Get the light properties.
 m_Light->GetDirection(lightDirection);
 m_Light->GetDiffuseColor(diffuseLightColor);

 // Rotate the world matrix by the rotation value so that the triangle will spin.
 m_OpenGL->MatrixRotationY(worldMatrix, rotation);

 // Set the light shader as the current shader program and set the matrices that it will use for rendering.
 m_LightShader->SetShader(m_OpenGL);
 m_LightShader->SetShaderParameters(m_OpenGL, worldMatrix, viewMatrix, projectionMatrix, 0, lightDirection, diffuseLightColor);

 // Render the model using the light shader.
 m_Model->Render(m_OpenGL);
 
 // Present the rendered scene to the screen.
 m_OpenGL->EndScene();

 return true;
}

Summary
總結

With the changes to the ModelClass we can now load in 3D models and render them. The format used here is just for basic static objects with lighting, however it is a good start to understanding how model formats work.
修改ModelClass後,我們可以加載3D模型並進行渲染。這裏使用了靜態模型格式。但作爲了解模型文件格式本章是個不錯的開端。

To Do Exercises
練習

1. Recompile the code and run the program. You should get a rotating cube with the opengl.tga texture on it. Press escape to quit once done.
1. 重新編譯並運行代碼。可以看到一個帶opengl.tga紋理的旋轉的盒子。按ESC鍵退出程序。

2. Find a decent 3D modeling package (hopefully something free) and create your own simple models and export them. Start looking at the format.
2. 找一個適當的3D模型包(最好是免費的),創建你自己的模型格式並將它們導出。

3. Write a simple parser program that takes the model exports and converts it to the format used here. Replace cube.txt with your model and run the program.
3. 編寫簡單的解析模型格式工具。用你自己的模型文件替換cube.txt。並運行程序。


Source Code
源代碼

http://www.rastertek.com/gl40src07.zip

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章