原英文版地址:http://www.rastertek.com/dx11tut07.html
本教程将介绍如何使用HLSL在Directx11中渲染三维模型。本教程中的代码基于漫反射照明教程中的代码。
我们已经在之前的教程中渲染了3D模型,但是它们是由一个三角形组成的,非常无趣。既然已经介绍了基础知识,我们将继续渲染更复杂的对象。在这种情况下,对象将是立方体。在我们了解如何渲染更复杂的模型之前,我们将首先讨论模型格式。
有许多工具可供用户创建三维模型。maya和3dstudiomax是两个比较流行的3d建模程序。还有许多其他的工具功能较少,但仍然可以为我们需要的东西做基础。
无论您选择使用哪种工具,它们都会将模型导出为许多不同的格式。我的建议是您创建自己的模型格式,并编写一个解析器来将其导出格式转换为自己的格式。原因是,您使用的三维建模软件包可能会随着时间的推移而改变,它们的模型格式也会改变。此外,您可能会使用多个三维建模包,因此您将有多种不同的格式来处理。因此,如果您有自己的格式并将其转换为自己的格式,那么您的代码将永远不需要更改。您只需要更改解析器程序,就可以将这些格式更改为您自己的格式。而且,大多数3D建模软件包都会导出大量垃圾,这些垃圾只对该建模程序有用,而且您不需要任何模型格式的垃圾。
制作您自己的格式最重要的一点是,它涵盖了您需要它做的所有事情,并且对您来说使用起来很简单。您还可以考虑为不同的对象制作一些不同的格式,因为有些可能有动画数据,有些可能是静态的,等等。
我要介绍的模型格式非常基本。它将包含模型中每个顶点的一条线。每行将匹配代码中使用的顶点格式,即位置向量(x,y,z)、纹理坐标(tu,tv)和法向量(nx,ny,nz)。该格式还将在顶部具有顶点计数,因此您可以在读取数据之前读取第一行并构建所需的内存结构。该格式还要求每三行构成一个三角形,并且模型格式中的顶点按顺时针顺序显示。下面是我们要呈现的多维数据集的模型文件:
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
所以你可以看到有36行x,y,z,tu,tv,nx,ny,nz数据。每三条线组成一个三角形,给我们12个三角形,形成一个立方体。格式是非常直接的,可以直接读取到我们的顶点缓冲区中,并且可以在不做任何修改的情况下进行渲染。
现在要注意的一件事是,一些三维建模程序以不同的顺序导出数据,如左侧或右侧坐标系。请记住,默认情况下,Directx11是左手坐标系,因此模型数据需要与之匹配。注意这些差异,并确保解析程序可以将数据转换成正确的格式/顺序。
Modelclass.h
在本教程中,我们需要做的是对模型类做一些小的改动,以便从我们的文本模型文件中渲染3D模型。
// Filename: modelclass.h
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
//
// INCLUDES //
//
#include <d3d11.h>
#include <d3dx10math.h>
现在包含fstream库来处理从模型文本文件中读取的内容。
#include <fstream>
using namespace std;
///
// MY CLASS INCLUDES //
///
#include "textureclass.h"
// Class name: ModelClass
class ModelClass
{
private:
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR2 texture;
D3DXVECTOR3 normal;
};
下一个变化是添加了一个新的结构来表示模型格式。它被称为modeltype。它包含与文件格式相同的位置、纹理和法向量。
struct ModelType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
};
public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();
初始化函数现在将输入要加载的模型的字符串文件名。
bool Initialize(ID3D11Device*, char*, WCHAR*);
void Shutdown();
void Render(ID3D11DeviceContext*);
int GetIndexCount();
ID3D11ShaderResourceView* GetTexture();
private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);
bool LoadTexture(ID3D11Device*, WCHAR*);
void ReleaseTexture();
我们还有两个新的函数来处理从文本文件加载和卸载模型数据。
bool LoadModel(char*);
void ReleaseModel();
private:
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
int m_vertexCount, m_indexCount;
TextureClass* m_Texture;
最后一个更改是名为m_model的新私有变量,它将成为新私有结构modeltype的数组。在将模型数据放入顶点缓冲区之前,该变量将用于读取和保存模型数据。
ModelType* m_model;
};
#endif
Modelclass.cpp
// Filename: modelclass.cpp
#include "modelclass.h"
ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
m_Texture = 0;
The new model structure is set to null in the class constructor.
m_model = 0;
}
ModelClass::ModelClass(const ModelClass& other)
{
}
ModelClass::~ModelClass()
{
}
初始化函数现在将应该加载的模型的文件名作为输入。
bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename){
bool result;
在初始化函数中,我们现在首先调用新的loadmodel函数。它将把模型数据从我们提供的文件名加载到新的M_模型数组中。一旦这个模型数组被填充,我们就可以从中构建顶点和索引缓冲区。由于initializeBuffers现在依赖于此模型数据,因此必须确保以正确的顺序调用函数。
// Load in the model data,
result = LoadModel(modelFilename);
if(!result)
{
return false;
}
// Initialize the vertex and index buffers.
result = InitializeBuffers(device);
if(!result)
{
return false;
}
// Load the texture for this model.
result = LoadTexture(device, textureFilename);
if(!result)
{
return false;
}
return true;
}
void ModelClass::Shutdown()
{
// Release the model texture.
ReleaseTexture();
//关闭顶点和索引缓冲区。
shutdownbuffers();
在shutdown函数中,我们向releasemodel函数添加一个调用,以便在完成后删除m_模型数组数据。
//释放模型数据。
ReleaseModel();
return;
}
void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(deviceContext);
return;
}
int ModelClass::GetIndexCount()
{
return m_indexCount;
}
ID3D11ShaderResourceView* ModelClass::GetTexture()
{
return m_Texture->GetTexture();
}
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
int i;
请注意,我们将不再在此处手动设置顶点和索引计数。一旦我们到达modelclass::loadmodel函数,您将看到我们在该点读取顶点和索引计数。
//创建顶点数组。
vertices = new VertexType[m_vertexCount];
if(!vertices)
{
return false;
}
// Create the index array.
indices = new unsigned long[m_indexCount];
if(!indices)
{
return false;
}
加载顶点和索引数组有点变化。我们不需要手动设置这些值,而是循环遍历新M_模型数组中的所有元素,并将这些数据从中复制到顶点数组中。索引数组易于构建,因为我们加载的每个顶点的索引号与它加载到的数组中的位置相同。
//用数据加载顶点数组和索引数组。
for(i=0; i<m_vertexCount; i++)
{
vertices[i].position = D3DXVECTOR3(m_model[i].x, m_model[i].y, m_model[i].z);
vertices[i].texture = D3DXVECTOR2(m_model[i].tu, m_model[i].tv);
vertices[i].normal = D3DXVECTOR3(m_model[i].nx, m_model[i].ny, m_model[i].nz);
indices[i] = i;
}
// Set up the description of the static vertex buffer.
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the vertex data.
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;
// Now create the vertex buffer.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
if(FAILED(result))
{
return false;
}
// Set up the description of the static index buffer.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the index data.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;
// Create the index buffer.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
if(FAILED(result))
{
return false;
}
// Release the arrays now that the vertex and index buffers have been created and loaded.
delete [] vertices;
vertices = 0;
delete [] indices;
indices = 0;
return true;
}
void ModelClass::ShutdownBuffers()
{
// Release the index buffer.
if(m_indexBuffer)
{
m_indexBuffer->Release();
m_indexBuffer = 0;
}
// Release the vertex buffer.
if(m_vertexBuffer)
{
m_vertexBuffer->Release();
m_vertexBuffer = 0;
}
return;
}
void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int stride;
unsigned int offset;
// Set vertex buffer stride and offset.
stride = sizeof(VertexType);
offset = 0;
// Set the vertex buffer to active in the input assembler so it can be rendered.
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
// Set the index buffer to active in the input assembler so it can be rendered.
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return;
}
bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
bool result;
// Create the texture object.
m_Texture = new TextureClass;
if(!m_Texture)
{
return false;
}
// Initialize the texture object.
result = m_Texture->Initialize(device, filename);
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;
}
这是一个新的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;
}
releaseModel函数处理删除模型数据数组。
void ModelClass::ReleaseModel()
{
if(m_model)
{
delete [] m_model;
m_model = 0;
}
return;
}
Graphicsclass.h
自从上一个教程以来,graphicsClass的头没有更改。
// Filename: graphicsclass.h
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
///
// MY CLASS INCLUDES //
///
#include "d3dclass.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(int, int, HWND);
void Shutdown();
bool Frame();
private:
bool Render(float);
private:
D3DClass* m_D3D;
CameraClass* m_Camera;
ModelClass* m_Model;
LightShaderClass* m_LightShader;
LightClass* m_Light;
};
#endif
Graphicsclass.cpp
// Filename: graphicsclass.cpp
#include "graphicsclass.h"
GraphicsClass::GraphicsClass()
{
m_D3D = 0;
m_Camera = 0;
m_Model = 0;
m_LightShader = 0;
m_Light = 0;
}
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
GraphicsClass::~GraphicsClass()
{
}
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
bool result;
// Create the Direct3D object.
m_D3D = new D3DClass;
if(!m_D3D)
{
return false;
}
// Initialize the Direct3D object.
result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
return false;
}
// 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;
}
模型初始化现在接收正在加载的模型文件的文件名。在本教程中,我们将使用cube.txt文件,以便将此模型加载到三维立方体对象中进行渲染。
// Initialize the model object.
result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds");
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_D3D->GetDevice(), 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();
delete m_LightShader;
m_LightShader = 0;
}
// Release the model object.
if(m_Model)
{
m_Model->Shutdown();
delete m_Model;
m_Model = 0;
}
// Release the camera object.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}
// Release the D3D object.
if(m_D3D)
{
m_D3D->Shutdown();
delete m_D3D;
m_D3D = 0;
}
return;
}
bool GraphicsClass::Frame()
{
bool result;
static float rotation = 0.0f;
// Update the rotation variable each frame.
rotation += (float)D3DX_PI * 0.01f;
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)
{
D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
bool result;
// Clear the buffers to begin the scene.
m_D3D->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 camera and d3d objects.
m_Camera->GetViewMatrix(viewMatrix);
m_D3D->GetWorldMatrix(worldMatrix);
m_D3D->GetProjectionMatrix(projectionMatrix);
// Rotate the world matrix by the rotation value so that the triangle will spin.
D3DXMatrixRotationY(&worldMatrix, rotation);
// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
m_Model->Render(m_D3D->GetDeviceContext());
// Render the model using the light shader.
result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor());
if(!result)
{
return false;
}
// Present the rendered scene to the screen.
m_D3D->EndScene();
return true;
}
总结
通过对ModelClass的更改,我们现在可以在3D模型中加载并渲染它们。这里使用的格式仅适用于具有照明的基本静态对象,但是它是了解模型格式如何工作的良好开端。
做练习
1。重新编译代码并运行程序。你应该得到一个具有相同海底纹理的旋转立方体。完成后按Escape退出。
2。找到一个像样的三维建模软件包(希望是免费的),创建自己的简单模型并导出它们。开始查看格式。
三。编写一个简单的解析器程序,它将模型导出并将其转换为这里使用的格式。用您的模型替换cube.txt并运行程序。
(如果要看其他课的中文翻译版,请到我博客目录查找,我会抽时间把后续的课目都翻译出来,这取决于我有空闲时间。)
时间仓促,只是粗略翻译,可能有多处失误,请谅解。朋友如有发现哪里有错误,欢迎指正,联·系w新licheng16886