OpenGL 入门基础教程 —— 加载obj模型

来源:互联网 发布:单机手机游戏 知乎 编辑:程序博客网 时间:2024/05/29 10:58

参考资料:http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-7-model-loading/


知识点1:obj模型

较为简单的obj模型:

# Blender3D v249 OBJ File: untitled.blend# www.blender3d.orgmtllib cube.mtlv 1.000000 -1.000000 -1.000000v 1.000000 -1.000000 1.000000v -1.000000 -1.000000 1.000000v -1.000000 -1.000000 -1.000000v 1.000000 1.000000 -1.000000v 0.999999 1.000000 1.000001v -1.000000 1.000000 1.000000v -1.000000 1.000000 -1.000000vt 0.748573 0.750412vt 0.749279 0.501284vt 0.999110 0.501077vt 0.999455 0.750380vt 0.250471 0.500702vt 0.249682 0.749677vt 0.001085 0.750380vt 0.001517 0.499994vt 0.499422 0.500239vt 0.500149 0.750166vt 0.748355 0.998230vt 0.500193 0.998728vt 0.498993 0.250415vt 0.748953 0.250920vn 0.000000 0.000000 -1.000000vn -1.000000 -0.000000 -0.000000vn -0.000000 -0.000000 1.000000vn -0.000001 0.000000 1.000000vn 1.000000 -0.000000 0.000000vn 1.000000 0.000000 0.000001vn 0.000000 1.000000 -0.000000vn -0.000000 -1.000000 0.000000usemtl Material_ray.pngs offf 5/1/1 1/2/1 4/3/1f 5/1/1 4/3/1 8/4/1f 3/5/2 7/6/2 8/7/2f 3/5/2 8/7/2 4/8/2f 2/9/3 6/10/3 3/5/3f 6/10/4 7/6/4 3/5/4f 1/2/5 5/1/5 2/9/5f 5/1/6 6/10/6 2/9/6f 5/1/7 8/11/7 6/10/7f 8/11/7 7/12/7 6/10/7f 1/2/8 2/9/8 3/13/8f 1/2/8 3/13/8 4/14/8


较为复杂的obj模型(从网上自己下载的一个):有一万多行的数据,模型较为复杂


其中:

#是注释标记,就像C++中的//
usemtl和mtlib描述了模型的外观。本课用不到。
v代表顶点,后面跟 x,y,z 分量
vt代表顶点的纹理坐标,后面跟 u,v分量
vn代表顶点的法线,后面跟法线的向量 x,y,z
f代表面


f比较麻烦。例如f 8/11/7 7/12/7 6/10/7:

8/11/7描述了三角形的第一个顶点
7/12/7描述了三角形的第二个顶点
6/10/7描述了三角形的第三个顶点
对于第一个顶点:8指向要用的顶点。此例中是-1.000000 1.000000 -1.000000(索引从1开始,和C++中从0开始不同)11指向要用的纹理坐标。此例中是0.748355 0.998230。7指向要用的法线。此例中是0.000000 1.000000 -0.000000。
我们称这些数字为索引。若几个顶点共用同一个坐标,索引就显得很方便,文件中只需保存一个”V”,可以多次引用,节省了存储空间。

其弊端在于我们无法让OpenGL混用顶点、纹理和法线索引。因此本课采用的方法是创建一个标准的、未加索引的模型。


注意点:在网上下载了很多的obj模型,但是能用的只有几个,总是提示数组维度出现错误,后来发现他的obj文件中:

f 54740 54741 54742 54743
f 54744 54747 54746 54745
f 54740 54744 54745 54741
f 54741 54745 54746 54742
f 54742 54746 54747 54743
f 54744 54740 54743 54747


这样表示的方法不适用于此简单的obj加载函数。


加载obj的函数:

#include <vector>#include <stdio.h>#include <string>#include <cstring>#include <glm/glm.hpp>#include "objloader.hpp"// Very, VERY simple OBJ loader.// Here is a short list of features a real function would provide : // - Binary files. Reading a model should be just a few memcpy's away, not parsing a file at runtime. In short : OBJ is not very great.// - Animations & bones (includes bones weights)// - Multiple UVs// - All attributes should be optional, not "forced"// - More stable. Change a line in the OBJ file and it crashes.// - More secure. Change another line and you can inject code.// - Loading from memory, stream, etcbool loadOBJ(const char * path, std::vector<glm::vec3> & out_vertices, std::vector<glm::vec2> & out_uvs,std::vector<glm::vec3> & out_normals){printf("Loading OBJ file %s...\n", path);std::vector<unsigned int> vertexIndices, uvIndices, normalIndices; //临时存储量std::vector<glm::vec3> temp_vertices; std::vector<glm::vec2> temp_uvs;std::vector<glm::vec3> temp_normals;FILE * file = fopen(path, "r"); //打开obj文件if( file == NULL ){printf("Impossible to open the file ! Are you in the right path ? See Tutorial 1 for details\n");getchar();return false;}while( 1 ){ //总是执行char lineHeader[128];// read the first word of the lineint res = fscanf(file, "%s", lineHeader);if (res == EOF)break; // EOF = End Of File. Quit the loop.// else : parse lineHeader//判断每行的首字母是哪种,对应存储到不同的数组中去if ( strcmp( lineHeader, "v" ) == 0 ){glm::vec3 vertex;fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z );temp_vertices.push_back(vertex);}else if ( strcmp( lineHeader, "vt" ) == 0 ){glm::vec2 uv;fscanf(file, "%f %f\n", &uv.x, &uv.y );uv.y = -uv.y; // Invert V coordinate since we will only use DDS texture, which are inverted. Remove if you want to use TGA or BMP loaders.temp_uvs.push_back(uv);}else if ( strcmp( lineHeader, "vn" ) == 0 ){glm::vec3 normal;fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z );temp_normals.push_back(normal);}else if ( strcmp( lineHeader, "f" ) == 0 ){std::string vertex1, vertex2, vertex3;unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] );if (matches != 9){printf("File can't be read by our simple parser :-( Try exporting with other options\n");return false;}vertexIndices.push_back(vertexIndex[0]);vertexIndices.push_back(vertexIndex[1]);vertexIndices.push_back(vertexIndex[2]);uvIndices    .push_back(uvIndex[0]);uvIndices    .push_back(uvIndex[1]);uvIndices    .push_back(uvIndex[2]);normalIndices.push_back(normalIndex[0]);normalIndices.push_back(normalIndex[1]);normalIndices.push_back(normalIndex[2]);}else{// Probably a comment, eat up the rest of the line,其它内容未知放在此处,也可能会造成溢出的情况,因为大小只有1000char stupidBuffer[1000];fgets(stupidBuffer, 1000, file);}}// For each vertex of each triangle,将读入的数据存储到对应的VAO中for( unsigned int i=0; i<vertexIndices.size(); i++ ){// Get the indices of its attributesunsigned int vertexIndex = vertexIndices[i];unsigned int uvIndex = uvIndices[i];unsigned int normalIndex = normalIndices[i];// Get the attributes thanks to the indexglm::vec3 vertex = temp_vertices[ vertexIndex-1 ];glm::vec2 uv = temp_uvs[ uvIndex-1 ];glm::vec3 normal = temp_normals[ normalIndex-1 ];// Put the attributes in buffersout_vertices.push_back(vertex);out_uvs     .push_back(uv);out_normals .push_back(normal);}return true;}#ifdef USE_ASSIMP // don't use this #define, it's only for me (it AssImp fails to compile on your machine, at least all the other tutorials still work)// Include AssImp#include <assimp/Importer.hpp>      // C++ importer interface#include <assimp/scene.h>           // Output data structure#include <assimp/postprocess.h>     // Post processing flagsbool loadAssImp(const char * path, std::vector<unsigned short> & indices,std::vector<glm::vec3> & vertices,std::vector<glm::vec2> & uvs,std::vector<glm::vec3> & normals){Assimp::Importer importer;const aiScene* scene = importer.ReadFile(path, 0/*aiProcess_JoinIdenticalVertices | aiProcess_SortByPType*/);if( !scene) {fprintf( stderr, importer.GetErrorString());getchar();return false;}const aiMesh* mesh = scene->mMeshes[0]; // In this simple example code we always use the 1rst mesh (in OBJ files there is often only one anyway)// Fill vertices positionsvertices.reserve(mesh->mNumVertices);for(unsigned int i=0; i<mesh->mNumVertices; i++){aiVector3D pos = mesh->mVertices[i];vertices.push_back(glm::vec3(pos.x, pos.y, pos.z));}// Fill vertices texture coordinatesuvs.reserve(mesh->mNumVertices);for(unsigned int i=0; i<mesh->mNumVertices; i++){aiVector3D UVW = mesh->mTextureCoords[0][i]; // Assume only 1 set of UV coords; AssImp supports 8 UV sets.uvs.push_back(glm::vec2(UVW.x, UVW.y));}// Fill vertices normalsnormals.reserve(mesh->mNumVertices);for(unsigned int i=0; i<mesh->mNumVertices; i++){aiVector3D n = mesh->mNormals[i];normals.push_back(glm::vec3(n.x, n.y, n.z));}// Fill face indicesindices.reserve(3*mesh->mNumFaces);for (unsigned int i=0; i<mesh->mNumFaces; i++){// Assume the model has only triangles.indices.push_back(mesh->mFaces[i].mIndices[0]);indices.push_back(mesh->mFaces[i].mIndices[1]);indices.push_back(mesh->mFaces[i].mIndices[2]);}// The "scene" pointer will be deleted automatically by "importer"}#endif


C++代码:

1:加载了不同的模型:是一个剑士

2:加载obj模型,理论上就是得到所需的顶点、UV、法线、Face数据,无需再逐个的进行定义数组


// Include standard headers#include <stdio.h>#include <stdlib.h>#include <vector>// Include GLEW#include <GL/glew.h>// Include GLFW#include <glfw3.h>GLFWwindow* window;// Include GLM#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtx/transform.hpp>using namespace glm;#include <common/shader.hpp>#include <common/texture.hpp>#include <common/controls.hpp>#include <common/objloader.hpp>int main( void ){// Initialise GLFWif( !glfwInit() ){fprintf( stderr, "Failed to initialize GLFW\n" );getchar();return -1;}glfwWindowHint(GLFW_SAMPLES, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be neededglfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// Open a window and create its OpenGL contextwindow = glfwCreateWindow( 1024, 768, "Tutorial 07 - Model Loading", NULL, NULL);if( window == NULL ){fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );getchar();glfwTerminate();return -1;}glfwMakeContextCurrent(window);// Initialize GLEWglewExperimental = true; // Needed for core profileif (glewInit() != GLEW_OK) {fprintf(stderr, "Failed to initialize GLEW\n");getchar();glfwTerminate();return -1;}// Ensure we can capture the escape key being pressed belowglfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);    // Hide the mouse and enable unlimited mouvement    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);        // Set the mouse at the center of the screen    glfwPollEvents();    glfwSetCursorPos(window, 1024/2, 768/2);// Dark blue backgroundglClearColor(0.0f, 0.0f, 0.4f, 0.0f);// Enable depth testglEnable(GL_DEPTH_TEST);// Accept fragment if it closer to the camera than the former oneglDepthFunc(GL_LESS); // Cull triangles which normal is not towards the cameraglEnable(GL_CULL_FACE);GLuint VertexArrayID;glGenVertexArrays(1, &VertexArrayID);glBindVertexArray(VertexArrayID);// Create and compile our GLSL program from the shadersGLuint programID = LoadShaders( "TransformVertexShader.vertexshader", "TextureFragmentShader.fragmentshader" );// Get a handle for our "MVP" uniformGLuint MatrixID = glGetUniformLocation(programID, "MVP");// Load the texture//GLuint Texture = loadBMP_custom("47.bmp");GLuint Texture = loadDDS("avatar_a_t00_ori.DDS");// Get a handle for our "myTextureSampler" uniformGLuint TextureID  = glGetUniformLocation(programID, "myTextureSampler");// Read our .obj filestd::vector<glm::vec3> vertices;std::vector<glm::vec2> uvs;std::vector<glm::vec3> normals; // Won't be used at the moment.bool res = loadOBJ("56.obj", vertices, uvs, normals);// Load it into a VBOGLuint vertexbuffer; //绑定对应的顶点数组glGenBuffers(1, &vertexbuffer);glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);GLuint uvbuffer;glGenBuffers(1, &uvbuffer);//绑定对应的uv数组glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(glm::vec2), &uvs[0], GL_STATIC_DRAW);do{// Clear the screenglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Use our shaderglUseProgram(programID);// Compute the MVP matrix from keyboard and mouse inputcomputeMatricesFromInputs();glm::mat4 ProjectionMatrix = getProjectionMatrix();glm::mat4 ViewMatrix = getViewMatrix();glm::mat4 ModelMatrix = glm::mat4(0.1);glm::mat4 myScalingMatrix = glm::scale(glm::vec3(0.1f, 0.1f ,0.1f));//缩放矩阵glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix*myScalingMatrix;// Send our transformation to the currently bound shader, // in the "MVP" uniformglUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);// Bind our texture in Texture Unit 0glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, Texture);// Set our "myTextureSampler" sampler to user Texture Unit 0glUniform1i(TextureID, 0);// 1rst attribute buffer : verticesglEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);glVertexAttribPointer(0,                  // attribute3,                  // sizeGL_FLOAT,           // typeGL_FALSE,           // normalized?0,                  // stride(void*)0            // array buffer offset);// 2nd attribute buffer : UVsglEnableVertexAttribArray(1);glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);glVertexAttribPointer(1,                                // attribute2,                                // sizeGL_FLOAT,                         // typeGL_FALSE,                         // normalized?0,                                // stride(void*)0                          // array buffer offset);// Draw the triangle !glDrawArrays(GL_TRIANGLES, 0, vertices.size() );glDisableVertexAttribArray(0);glDisableVertexAttribArray(1);// Swap buffersglfwSwapBuffers(window);glfwPollEvents();} // Check if the ESC key was pressed or the window was closedwhile( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&   glfwWindowShouldClose(window) == 0 );// Cleanup VBO and shaderglDeleteBuffers(1, &vertexbuffer);glDeleteBuffers(1, &uvbuffer);glDeleteProgram(programID);glDeleteTextures(1, &TextureID);glDeleteVertexArrays(1, &VertexArrayID);// Close OpenGL window and terminate GLFWglfwTerminate();return 0;}


阅读全文
1 0
原创粉丝点击