第七章: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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的。 要用 3dmax 做一个雷达的 fbx 模型,你可以这样做: 1. 打开 3dmax,然后创建一个新的场景。 2. 使用 3dmax 的建模工具来创建雷达的几何形状。你可以使用基本的图形,如立方体、圆柱体、球体等,来构建雷达的各个部分。 3. 在雷达的几何形状上应用材质,以便让它看起来更真实。你可以使用 3dmax 内置的材质或者导入第三方材质。 4. 使用 3dmax 的动画工具来制作雷达的动画效果。你可以设置雷达的转速、旋转方向等。 5. 当你完成了所有的建模和动画工作之后,就可以导出雷达模型为 fbx 格式了。在 3dmax 中,你可以通过「输出」菜单中的「输出」命令来导出 fbx 模型。 希望这些信息能帮到你。 ### 回答2: 要使用3DMax制作一个雷达的FBX模型,可以按照以下步骤进行操作。 首先,打开3DMax软件。在界面顶部的工具栏中,选择"文件",然后点击"新建"来创建一个新的项目。 接下来,在工具栏中找到"创建"选项,点击它并选择"几何体"。在几何体选项中,选择"圆柱体"。然后在视图窗口中点击并拖动鼠标以确定圆柱体的大小。 然后,为雷达的圆柱体选择适当的尺寸和参数。可以设置半径、高度、段数等。根据需要,可以为圆柱体添加纹理,以便后续渲染时具有更真实的外观。 接下来,在3DMax的工具栏中选择"编辑"选项,然后点击"变换"。这将允许你在视图窗口中移动和旋转圆柱体,以便调整它的位置和方向。 在制作雷达的模型时,可以根据需要为其添加更多的细节和特征。例如,可以添加雷达的天线和支架等部件。可以使用3DMax提供的工具和功能,如绘制线条和创建多边形,来制作这些部件。 完成模型后,可以进行渲染和导出。在3DMax的工具栏中选择"渲染"选项,并根据需要设置灯光、材质和纹理等属性,以获得最终想要的效果。最后,可以将模型导出为FBX格式,以便在其他软件中使用或与他人分享。 通过按照以上步骤,你可以使用3DMax制作一个雷达的FBX模型。记得保存项目以便以后修改和使用。同时,尽量多尝试并学习使用3DMax的各种功能和工具,以便发挥更多创造力和实现更复杂的设计。 ### 回答3: 使用3Dmax制作一个雷达的fbx模型需要经过以下步骤: 1. 收集参考资料:寻找雷达设备的照片或图纸,以便了解其外观和细节。 2. 创建基本几何体:使用3Dmax中的基本几何体工具(如立方体或圆柱体)来创建雷达的基本形状。 3. 调整形状和尺寸:根据参考资料中的尺寸和比例,调整基本几何体的形状和尺寸,使其更接近实际雷达的外观。 4. 细化模型细节:使用3Dmax中的编辑工具(如边角调整、顶点编辑等),对模型进行细化处理,加入更多的细节、边缘和曲线,以增强真实感。 5. 创建材质和纹理:使用3Dmax中的材质编辑器,创建适当的材质和纹理,并将其应用到雷达模型上,以使其外观更加逼真。 6. 添加细节和装饰物:根据参考资料,添加任何额外的细节和装饰物,如按钮、指示灯等,以增添模型的真实感。 7. 灯光和渲染设置:针对雷达模型选择合适的灯光设置,并使用3Dmax的渲染器进行渲染,以生成高质量的图像或动画。 8. 导出为fbx格式:完成模型的设计和渲染后,使用3Dmax中的导出功能,将模型保存为fbx格式,以便在其他软件或平台上使用。 总结而言,使用3Dmax制作雷达的fbx模型需要依次进行形状建模、细节处理、材质纹理、灯光设置和渲染等环节,并最终导出为fbx格式。这个过程需要参考资料、艺术感和技术技巧的结合,以实现准确度和真实感的平衡。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值