参考资料:http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-7-model-loading/
知识点1:obj模型
较为简单的obj模型:
# Blender3D v249 OBJ File: untitled.blend # www.blender3d.org mtllib cube.mtl v 1.000000 -1.000000 -1.000000 v 1.000000 -1.000000 1.000000 v -1.000000 -1.000000 1.000000 v -1.000000 -1.000000 -1.000000 v 1.000000 1.000000 -1.000000 v 0.999999 1.000000 1.000001 v -1.000000 1.000000 1.000000 v -1.000000 1.000000 -1.000000 vt 0.748573 0.750412 vt 0.749279 0.501284 vt 0.999110 0.501077 vt 0.999455 0.750380 vt 0.250471 0.500702 vt 0.249682 0.749677 vt 0.001085 0.750380 vt 0.001517 0.499994 vt 0.499422 0.500239 vt 0.500149 0.750166 vt 0.748355 0.998230 vt 0.500193 0.998728 vt 0.498993 0.250415 vt 0.748953 0.250920 vn 0.000000 0.000000 -1.000000 vn -1.000000 -0.000000 -0.000000 vn -0.000000 -0.000000 1.000000 vn -0.000001 0.000000 1.000000 vn 1.000000 -0.000000 0.000000 vn 1.000000 0.000000 0.000001 vn 0.000000 1.000000 -0.000000 vn -0.000000 -1.000000 0.000000 usemtl Material_ray.png s off f 5/1/1 1/2/1 4/3/1 f 5/1/1 4/3/1 8/4/1 f 3/5/2 7/6/2 8/7/2 f 3/5/2 8/7/2 4/8/2 f 2/9/3 6/10/3 3/5/3 f 6/10/4 7/6/4 3/5/4 f 1/2/5 5/1/5 2/9/5 f 5/1/6 6/10/6 2/9/6 f 5/1/7 8/11/7 6/10/7 f 8/11/7 7/12/7 6/10/7 f 1/2/8 2/9/8 3/13/8 f 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, etc bool 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 line int 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,其它内容未知放在此处,也可能会造成溢出的情况,因为大小只有1000 char 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 attributes unsigned int vertexIndex = vertexIndices[i]; unsigned int uvIndex = uvIndices[i]; unsigned int normalIndex = normalIndices[i]; // Get the attributes thanks to the index glm::vec3 vertex = temp_vertices[ vertexIndex-1 ]; glm::vec2 uv = temp_uvs[ uvIndex-1 ]; glm::vec3 normal = temp_normals[ normalIndex-1 ]; // Put the attributes in buffers out_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 flags bool 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 positions vertices.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 coordinates uvs.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 normals normals.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 indices indices.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" } #endifC++代码:
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 GLFW if( !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 needed glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Open a window and create its OpenGL context window = 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 GLEW glewExperimental = true; // Needed for core profile if (glewInit() != GLEW_OK) { fprintf(stderr, "Failed to initialize GLEW\n"); getchar(); glfwTerminate(); return -1; } // Ensure we can capture the escape key being pressed below glfwSetInputMode(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 background glClearColor(0.0f, 0.0f, 0.4f, 0.0f); // Enable depth test glEnable(GL_DEPTH_TEST); // Accept fragment if it closer to the camera than the former one glDepthFunc(GL_LESS); // Cull triangles which normal is not towards the camera glEnable(GL_CULL_FACE); GLuint VertexArrayID; glGenVertexArrays(1, &VertexArrayID); glBindVertexArray(VertexArrayID); // Create and compile our GLSL program from the shaders GLuint programID = LoadShaders( "TransformVertexShader.vertexshader", "TextureFragmentShader.fragmentshader" ); // Get a handle for our "MVP" uniform GLuint 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" uniform GLuint TextureID = glGetUniformLocation(programID, "myTextureSampler"); // Read our .obj file std::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 VBO GLuint 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 screen glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Use our shader glUseProgram(programID); // Compute the MVP matrix from keyboard and mouse input computeMatricesFromInputs(); 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" uniform glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]); // Bind our texture in Texture Unit 0 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture); // Set our "myTextureSampler" sampler to user Texture Unit 0 glUniform1i(TextureID, 0); // 1rst attribute buffer : vertices glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer( 0, // attribute 3, // size GL_FLOAT, // type GL_FALSE, // normalized? 0, // stride (void*)0 // array buffer offset ); // 2nd attribute buffer : UVs glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, uvbuffer); glVertexAttribPointer( 1, // attribute 2, // size GL_FLOAT, // type GL_FALSE, // normalized? 0, // stride (void*)0 // array buffer offset ); // Draw the triangle ! glDrawArrays(GL_TRIANGLES, 0, vertices.size() ); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); // Swap buffers glfwSwapBuffers(window); glfwPollEvents(); } // Check if the ESC key was pressed or the window was closed while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 ); // Cleanup VBO and shader glDeleteBuffers(1, &vertexbuffer); glDeleteBuffers(1, &uvbuffer); glDeleteProgram(programID); glDeleteTextures(1, &TextureID); glDeleteVertexArrays(1, &VertexArrayID); // Close OpenGL window and terminate GLFW glfwTerminate(); return 0; }