DirectX 10 教程7:绘制3D模型
原文地址:Tutorial 7: 3D Model Rendering(http://www.rastertek.com/dx10tut07.htm)。
源代码:dx10tut07.zip。
本教程介绍在DirectX 10中使用HLSL绘制3D模型,代码基于上一个漫反射光照的教程。
在上一个教程中我们绘制了一个3D模型,只是一个简单的三角形很容易理解。本教程我们要绘制一个更复杂的对象——立方体,但在这之前需要讨论一下模型格式。
有很多工具可以创建3D模型,Maya和3D Studio Max是两个较为流行的建模工具,其他的工具功能弱一点,但都能完成基本的工作。
无论使用的是何种工具,你总要将做好的模型导出为不同的格式。建议你定义自己的模型格式,然后编写一个转换程序将输出格式转换为自己的格式。因为你有可能改变使用的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个三角形构成一个立方体。数据格式非常简单,可以不加修改被读取到顶点缓存。
值得注意的是,有些3D建模工具以不同的顺序输出数据,例如顺时针方向和逆时针方向坐标系统,你需要记住DirectX 10默认为左手坐标系,所以模型数据也必须加以匹配。留心这些区别,确保你的转换程序将数据转换为正确的格式/顺序。
Modelclass.h
本教程中我们需要对ModelClass做一点小的修改使之可以绘制来自于文本文件中的3D模型。
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_
需要包含fstream库处理模型文本文件的读取。
////////////// // INCLUDES // ////////////// #includeusing 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();
Initialize方法的一个参数为要加载的模型的文件名称。
bool Initialize(ID3D10Device*, char*, WCHAR*); void Shutdown(); void Render(ID3D10Device*); int GetIndexCount(); ID3D10ShaderResourceView* GetTexture(); private: bool InitializeBuffers(ID3D10Device*); void ShutdownBuffers(); void RenderBuffers(ID3D10Device*); bool LoadTexture(ID3D10Device*, WCHAR*); void ReleaseTexture();
还添加了两个新方法加载和卸载来自于文本文件的模型数据。
bool LoadModel(char*); void ReleaseModel(); private: ID3D10Buffer *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;
在构造函数中将模型结构体设置为null。
m_model = 0; } ModelClass::ModelClass(const ModelClass& other) { } ModelClass::~ModelClass() { }
Initialize方法的一个参数为模型文件的名称。
bool ModelClass::Initialize(ID3D10Device* device, char* modelFilename, WCHAR* textureFilename) { bool result;
在Initialize方法中我们首先调用LoadModel方法,这个方法将模型数据加载到m_model数组中。填充了这个数组后,我们就可以基于它创建顶点和索引缓存了。因为InitializeBuffers方法需要使用模型数据,所以你需要确保以正确的顺序调用这个方法。
// Load in the model data. result = LoadModel(modelFilename); if(!result) { return false; } // Initialize the vertex and index buffer that hold the geometry for the triangle. 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(); // Release the vertex and index buffers. ShutdownBuffers();
在Shutdown方法中,我们添加了调用ReleaseModel方法,用来清除m_model数组数据。
ID3D10ShaderResourceView* ModelClass::GetTexture() { return m_Texture->GetTexture(); } bool ModelClass::InitializeBuffers(ID3D10Device* device) { VertexType* vertices; unsigned long* indices; D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D10_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i;
注意我们现在不需要手动设置顶点和索引数量,我们可以在ModelClass::LoadModel方法中读取顶点和索引数量。
// Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; }
加载顶点和索引数组有一点变化。现在不需要手动设置,而是遍历m_model数组中的数据,将它们复制到顶点数组中。索引数组很容易创建,因为每个顶点的索引值与数组中的索引值相同。
// Load the vertex array and index array with data. 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 vertex buffer. vertexBufferDesc.Usage = D3D10_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; // Now finally create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the index buffer. indexBufferDesc.Usage = D3D10_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; // 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(ID3D10Device* device) { 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. device->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. device->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. device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; } bool ModelClass::LoadTexture(ID3D10Device* 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; }
本教程中我将光线颜色变为白色。
// 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; // 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->GetDevice()); // Render the model using the light shader. m_LightShader->Render(m_D3D->GetDevice(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor()); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
总结
通过修改ModelClass,我们现在可以加载3D模型并进行绘制了。虽然本教程中使用的格式只能用于含有基本光照的静态对象,但这是一个理解模型格式非常好的一个入门教程。
练习
1.编译代码并运行程序,你可以看到一个带有纹理的旋转立方体,按escape键退出。
2.找一个不错的3D建模工具(希望是免费的)并创建一个简单的模型,导出后看看它们的格式。
3.编写一个简单的转换程序,将导出的模型转换为本教程使用的格式,替换掉cube.txt看看运行效果。
文件下载(已下载 1154 次)发布时间:2012/7/17 下午12:00:44 阅读次数:9362