Exercise 8:三维模型的载入
前言
第八次作业了,胜利就在眼前!
一、ply文件
先上一下我这次作业用的ply模型的部分
ply
format ascii 1.0
comment - RG built normal additions
element vertex 35947
property float x
property float y
property float z
property float nx
property float ny
property float nz
element face 69451
property list uchar int vertex_indices
end_header
-0.037830 0.127940 0.004475 0.196148 0.972595 -0.124835
-0.044779 0.128887 0.001905 0.210776 0.951005 -0.226191
-0.068010 0.151244 0.037195 0.052961 0.802618 0.594137
-0.002287 0.130150 0.023220 -0.053027 0.970576 0.234886
...
...
3 34930 34929 34800
3 34801 34930 34800
3 35059 35058 34929
3 34930 35059 34929
3 35188 35187 35058
3 35059 35188 35058
3 22321 22225 22322
3 34780 34909 17277
3 17346 34909 17279
3 34909 17346 17277
3 17277 17346 17345
从第一行到end_header为止,都是ply文件的头部部分,其中较为重要的是element后面的部分。其中vertex 35947表示该模型一共有35947个顶点,face 69451表示该模型共有69451个面。property里的属性用于说明接下来的数据部分都含有什么数据、是什么格式,比如这里的vertex数据每6个float格式一行,前三个为顶点的位置,后三个则为对应的法线方向。face数据是对应的顶点索引,用于描述哪几个顶点绘制成一个面。下面是对应的读取文件。
void LoadPly(const char* path)
{
fstream fopen;
fopen.open(path, ios::in);
string filein;
while (!fopen.eof())
{
fopen >> filein;
if (filein == "ply");
else if (filein == "comment")
{
getline(fopen, filein, '\n');
}
else if (filein == "forment")
{
getline(fopen, filein, '\n');
}
else if (filein == "element")
{
fopen >> filein;
if (filein == "vertex")
{
fopen >> vertexNum;
vertexNum *= 6;
vertex = new GLfloat[vertexNum];
getline(fopen, filein, '\n');
}
else if (filein == "face")
{
fopen >> faceNum;
faceNum *= 3;
faces = new GLuint[faceNum];
getline(fopen, filein, '\n');
}
else
{
getline(fopen, filein, '\n');
}
}
else if (filein == "property")
{
getline(fopen, filein, '\n');
}
else if (filein == "end_header")
{
getline(fopen, filein, '\n');
for (int i = 0,j = 0; i < vertexNum;)
{
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
}
int count = 3;
for (int i = 0; i < faceNum;)
{
fopen >> count;
fopen >> faces[i++];
fopen >> faces[i++];
fopen >> faces[i++];
}
break;
}
}
fopen.close();
}
二、Vertex Buffer Object的使用
读取到了ply文件的数据,接下来要做的就是将它绘制在屏幕上,这里采用VBO的方法绘制
1.VBO(Vertex Buffer Object)
VBO可以将顶点数组复制到一块顶点缓冲中,供OpenGL使用
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId); //绑定该VBO,对应格式为GL_ARRAY_BUFFER
glBufferData(GL_ARRAY_BUFFER, sizeof(arr), arr, GL_STATIC_DRAW); //将数组复制到顶点缓冲中
在完成复制之后,还要告诉OpenGL该如何应用该数组中的顶点数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), 0);
glNormalPointer(GL_FLOAT, 6 * sizeof(GLfloat), (GLubyte*)NULL + 3 * sizeof(GLfloat));
glEnableClientState(GL_VERTEX_ARRAY); //启用GL_VERTEX_ARRAY
glEnableClientState(GL_NORMAL_ARRAY); //启用GL_NORMAL_ARRAY
glVertexAttribPointer()函数告诉OpenGL数组中的顶点属性,对应参数如下:
1.指定顶点属性位置,和顶点着色器相关
2.指定顶点的属性大小,这里有xyz三个属性
3.顶点的数据类型
4.是否要进行归一化
5.顶点属性之间的间隔(如果数据为紧密分布,则可填默认值0)
6.初始数据的偏移量,这里为0
glNormalPointer()函数告诉OpenGL数组中的法线属性,对应参数如下:
1.数据类型
2.法线属性之间的间隔
3.初始数据的偏移量
2.EBO(Element Buffer Object)
EBO用于复制并储存顶点位置的索引
glGenBuffers(1, &eboId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*faceNum, faces, GL_STATIC_DRAW);
3.VAO (Vertex Arrary Object)
VAO相当于多个VBO的引用,便于绘制图形时的使用
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
注意VAO的绑定要放在VBO前,在解除该VAO的绑定之前所绑定的VBO都视为可被该VAO引用
4.绘制的使用
这里的绘制就非常的简便,如下所示
glBindVertexArray(vaoId);
glDrawElements(GL_TRIANGLES, faceNum, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
总结
惯例放出这次的全部代码及生成图
#include<GL/glew.h>
#include<GL/freeglut.h>
#include<cstdio>
#include<cstdlib>
#include <fstream>
#include <string>
using namespace std;
GLint Height = 600;
GLint Width = 800;
GLfloat LightPosition0[4] = { 0,2,2,0 };
int oldX, oldY;
float rotateX, rotateY;
bool rotated = false;
GLuint shaderProgram;
GLuint vboId;
GLuint vaoId;
GLuint eboId;
int vertexNum;
int faceNum;
GLfloat* vertex;
GLfloat* normal;
GLuint* faces;
struct camer {
float eyePos[3] = { 0,0,0.2f };
float lookAt[3] = { 0,0,-1 };
float upDir[3] = { 0,1,0 };
};
camer ca;
float aspect;
void Lighting();
void createVBO();
float offsetz;
void LoadShader(const char* vertName, const char* fragName, GLuint& shader);
void init()
{
glClearColor(0.5, 0.5, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); //开启深度测试
createVBO();
Lighting();
LoadShader("ppixel.vert", "ppixel.frag", shaderProgram);
}
const char* getFileData(const char* path)
{
FILE* infile;
fopen_s(&infile, path, "rb");
if (!infile)
{
return NULL;
}
fseek(infile, 0, SEEK_END);
int len = ftell(infile);
fseek(infile, 0, SEEK_SET);
char* source = new char[len+1];
fread(source, 1, len, infile);
source[len] = 0;
fclose(infile);
return source;
}
void LoadShader(const char* vertName, const char* fragName, GLuint& shader)
{
const char* vertShaderSoucre = getFileData(vertName);
const char* fragShaderSource = getFileData(fragName);
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vertShader, 1, &vertShaderSoucre, NULL);
glShaderSource(fragShader, 1, &fragShaderSource, NULL);
glCompileShader(vertShader);
GLint ret;
glGetShaderiv(vertShader, GL_COMPILE_STATUS, &ret);
if (ret == 0)
{
GLchar log[1024];
glGetShaderInfoLog(vertShader, sizeof(log), NULL, log);
printf( "shader编译失败:%s\n", log);
}
glCompileShader(fragShader);
glGetShaderiv(fragShader, GL_COMPILE_STATUS, &ret);
if (ret == 0)
{
GLchar log[1024];
glGetShaderInfoLog(fragShader, sizeof(log), NULL, log);
printf("shader编译失败:%s\n", log);
}
shader = glCreateProgram();
glAttachShader(shader, vertShader);
glAttachShader(shader, fragShader);
glLinkProgram(shader);
}
void createVBO()
{
//创建VAO对象
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
//创建VBO对象,把顶点数组复制到一个顶点缓冲中,供OpenGL使用
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*vertexNum, vertex, GL_STATIC_DRAW);
//创建EBO对象
glGenBuffers(1, &eboId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
//传入EBO数据
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*faceNum, faces, GL_STATIC_DRAW);
//解释顶点数据方式
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), 0);
glNormalPointer(GL_FLOAT, 6 * sizeof(GLfloat), (GLubyte*)NULL + 3 * sizeof(GLfloat));
glEnableVertexAttribArray(0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
//解绑VAO
glBindVertexArray(0);
//解绑EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
//解绑VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void LoadPly(const char* path)
{
fstream fopen;
fopen.open(path, ios::in);
string filein;
while (!fopen.eof())
{
fopen >> filein;
if (filein == "ply");
else if (filein == "comment")
{
getline(fopen, filein, '\n');
}
else if (filein == "forment")
{
getline(fopen, filein, '\n');
}
else if (filein == "element")
{
fopen >> filein;
if (filein == "vertex")
{
fopen >> vertexNum;
vertexNum *= 6;
vertex = new GLfloat[vertexNum];
getline(fopen, filein, '\n');
}
else if (filein == "face")
{
fopen >> faceNum;
faceNum *= 3;
faces = new GLuint[faceNum];
getline(fopen, filein, '\n');
}
else
{
getline(fopen, filein, '\n');
}
}
else if (filein == "property")
{
getline(fopen, filein, '\n');
}
else if (filein == "end_header")
{
getline(fopen, filein, '\n');
for (int i = 0,j = 0; i < vertexNum;)
{
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
fopen >> vertex[i++];
}
int count = 3;
for (int i = 0; i < faceNum;)
{
fopen >> count;
fopen >> faces[i++];
fopen >> faces[i++];
fopen >> faces[i++];
}
break;
}
}
fopen.close();
}
void DrawScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAt(ca.eyePos[0], ca.eyePos[1], ca.eyePos[2], ca.lookAt[0], ca.lookAt[1], ca.lookAt[2], ca.upDir[0], ca.upDir[1], ca.upDir[2]);
glLightfv(GL_LIGHT0, GL_POSITION, LightPosition0);
glPushMatrix();
glTranslatef(-0.1f, 0, -3.0f);
glRotatef(rotateX, 0, 1, 0);
glRotatef(rotateY, 1, 0, 0);
glBindVertexArray(vaoId);
glUseProgram(0);
glDrawElements(GL_TRIANGLES, faceNum, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glPopMatrix();
glPushMatrix();
glTranslatef(0.1f, 0, -3.0f);
glRotatef(rotateX, 0, 1, 0);
glRotatef(rotateY, 1, 0, 0);
glBindVertexArray(vaoId);
glUseProgram(shaderProgram);
glDrawElements(GL_TRIANGLES, faceNum, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glPopMatrix();
}
void display()
{
DrawScene();
glutSwapBuffers();
}
void Lighting()
{
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
GLfloat AmbientLight0[4] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT, AmbientLight0);
GLfloat DiffuseLight0[4] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight0);
GLfloat SpecularLight0[4] = { 1.0,1.0,1.0,1.0 };
glLightfv(GL_LIGHT0, GL_SPECULAR, SpecularLight0);
//glEnable(GL_LIGHT1);
GLfloat AmbientLight1[4] = { 0.0, 1.0, 0.0, 1.0 };
glLightfv(GL_LIGHT1, GL_AMBIENT, AmbientLight1);
GLfloat DiffuseLight1[4] = { 0.0, 1.0, 0.0, 1.0 };
glLightfv(GL_LIGHT1, GL_DIFFUSE, DiffuseLight1);
GLfloat SpecularLight1[4] = { 0.0,1.0,0.0,1.0 };
glLightfv(GL_LIGHT1, GL_SPECULAR, SpecularLight1);
//glEnable(GL_LIGHT2);
GLfloat AmbientLight2[4] = { 1.0, 0.0, 0.0, 1.0 };
glLightfv(GL_LIGHT2, GL_AMBIENT, AmbientLight2);
GLfloat DiffuseLight2[4] = { 1.0, 0.0, 0.0, 1.0 };
glLightfv(GL_LIGHT2, GL_DIFFUSE, DiffuseLight2);
GLfloat SpecularLight2[4] = { 1.0,0.0,0.0,1.0 };
glLightfv(GL_LIGHT2, GL_SPECULAR, SpecularLight2);
}
void Idle()
{
glutPostRedisplay();
}
void reshape(int w, int h)
{
aspect = (float)w / ((h) ? h : 1);//平截头体的纵横比,也就是宽度除以高度,(h)?h:1意思是若h=0,则h=1
Height = h;
Width = w;
glViewport(0, 0, w, h);
//进行投影变换前调用下面两个函数,接下来的变换函数将影响投影矩阵
//在窗口改变函数reshape中先用glMatrixMode(GL_PROJECTION)定义视锥,再用glMatrixMode(GL_MODELVIEW)改为模型变换
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(10, aspect, 1.0f, 100.0f);//
glViewport(0, 0, w, h);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void MouseFunc(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
rotated = true;
oldX = x;
oldY = y;
}
else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
{
rotated = false;
}
}
void MouseMove(int x, int y)
{
if (rotated)
{
rotateX += x - oldX;
rotateY += y - oldY;
oldX = x;
oldY = y;
}
}
void KeyBoard(unsigned char button, int x, int y)
{
}
int main(int argc, char** argv)
{
LoadPly("tetrahedron.ply");
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
glutInitWindowPosition(100, 100);
glutInitWindowSize(Width, Height);
glutCreateWindow("Test");
glewInit(); //如果使用FBO的话,此句不能缺少
init();
glutDisplayFunc(display); //窗口绘制
glutIdleFunc(Idle);
glutReshapeFunc(reshape);
glutMouseFunc(MouseFunc);
glutMotionFunc(MouseMove);
glutKeyboardFunc(KeyBoard);
glutMainLoop();
return 0;
}