✠OpenGL-6-3D模型

构建一个球体

earth.jpg(纹理图)


下面直接给出完整代码:

下面代码的init()函数中:
(x0,y0,z0)、(s0,t0) 对应上图中的位置0
(x1,y1,z1)、(s1,t1) 对应上图中的位置1
(x2,y2,z2)、(s2,t2) 对应上图中的位置2
(x3,y3,z3)、(s3,t3) 对应上图中的位置3

Sphere.h

#include <cmath>
#include <vector>
#include <glm\glm.hpp>
class Sphere {
private:
	int numVertices;
	std::vector<glm::vec3> vertices;
	std::vector<glm::vec2> texCoords;
	void init(float, float);
	float toRadians(float degrees);
public:
	Sphere();
	Sphere(float R, float prec);
	int getNumVertices();
	std::vector<glm::vec3> getVertices();
	std::vector<glm::vec2> getTexCoords();
};

Sphere.cpp

#include <cmath>
#include <vector>
#include <iostream>
#include <glm\glm.hpp>
#include "Sphere.h"
using namespace std;

Sphere::Sphere() {
	init(1.0, 10.0);
}

Sphere::Sphere(float R, float angleSpan) {
	init(R, angleSpan);
}

float Sphere::toRadians(float degrees) {
	return (degrees * 2.0f * 3.14159f) / 360.0f;
}

int Sphere::getNumVertices() { return numVertices; }
std::vector<glm::vec3> Sphere::getVertices() { return vertices; }
std::vector<glm::vec2> Sphere::getTexCoords() { return texCoords; }

/*
* R:球半径,angleSpan:将球进行横向和纵向单位切分的角度
*/
void Sphere::init(float R, float angleSpan) {
	for (float vAngle = -90; vAngle < 90; vAngle += angleSpan) { // 垂直方向:每angleSpan度一份
		for (float hAngle = 0; hAngle < 360; hAngle += angleSpan) { // 水平方向:每angleSpan度一份
			float vAngleRange = 90 + vAngle;
			// hAngle∈[0°,360°),对应纹理图水平方向:左->右∈[0,1)
			// vAngleRange∈[0°,180°),对应纹理图垂直方向:底->顶∈[0,1)
			float x0 = (float)(R * cos(toRadians(vAngle)) * cos(toRadians(hAngle)));
			float y0 = (float)(R * cos(toRadians(vAngle)) * sin(toRadians(hAngle)));
			float z0 = (float)(R * sin(toRadians(vAngle)));

			float s0 = hAngle / 360;
			float t0 = vAngleRange / 180;

			float x1 = (float)(R * cos(toRadians(vAngle)) * cos(toRadians(hAngle + angleSpan)));
			float y1 = (float)(R * cos(toRadians(vAngle)) * sin(toRadians(hAngle + angleSpan)));
			float z1 = (float)(R * sin(toRadians(vAngle)));

			float s1 = (hAngle + angleSpan) / 360;
			float t1 = vAngleRange / 180;

			float x2 = (float)(R * cos(toRadians(vAngle + angleSpan)) * cos(toRadians(hAngle + angleSpan)));
			float y2 = (float)(R * cos(toRadians(vAngle + angleSpan)) * sin(toRadians(hAngle + angleSpan)));
			float z2 = (float)(R * sin(toRadians(vAngle + angleSpan)));

			float s2 = (hAngle + angleSpan) / 360;
			float t2 = (vAngleRange + angleSpan) / 180;

			float x3 = (float)(R * cos(toRadians(vAngle + angleSpan)) * cos(toRadians(hAngle)));
			float y3 = (float)(R * cos(toRadians(vAngle + angleSpan)) * sin(toRadians(hAngle)));
			float z3 = (float)(R * sin(toRadians(vAngle + angleSpan)));

			float s3 = hAngle / 360;
			float t3 = (vAngleRange + angleSpan) / 180;

			// 构建第一个三角形及相应纹理坐标
			vertices.push_back(glm::vec3(x1, y1, z1));
			vertices.push_back(glm::vec3(x3, y3, z3));
			vertices.push_back(glm::vec3(x0, y0, z0));
			texCoords.push_back(glm::vec2(s1, t1));
			texCoords.push_back(glm::vec2(s3, t3));
			texCoords.push_back(glm::vec2(s0, t0));

			// 构建第二个三角形及相应纹理坐标
			vertices.push_back(glm::vec3(x1, y1, z1));
			vertices.push_back(glm::vec3(x2, y2, z2));
			vertices.push_back(glm::vec3(x3, y3, z3));
			texCoords.push_back(glm::vec2(s1, t1));
			texCoords.push_back(glm::vec2(s2, t2));
			texCoords.push_back(glm::vec2(s3, t3));
		}
	}
	numVertices = vertices.size();
}

main.cpp

#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <glm\glm.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Sphere.h"
#include "Utils.h"
using namespace std;

#define numVAOs 1
#define numVBOs 2

float cameraX, cameraY, cameraZ;
float sphLocX, sphLocY, sphLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint earthTexture;

// variable allocation for display
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat, rMat;

Sphere mySphere = Sphere();

void setupVertices(void) {
	std::vector<glm::vec3> vert = mySphere.getVertices();
	std::vector<glm::vec2> tex = mySphere.getTexCoords();

	std::vector<float> vertValues;
	std::vector<float> texValues;

	for (int i = 0; i < mySphere.getNumVertices(); i++) {
		vertValues.push_back(vert[i].x);
		vertValues.push_back(vert[i].y);
		vertValues.push_back(vert[i].z);
		texValues.push_back(tex[i].s);
		texValues.push_back(tex[i].t);
	}

	glGenVertexArrays(1, vao);
	glBindVertexArray(vao[0]);
	glGenBuffers(numVBOs, vbo);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	// 注意:sizeof(vector)返回的不是数据块大小(对于数组才是的)。不过可通过vector起始位置指针访问连续向量空间。
	// 前提是vector中存储的是标量类型的数值,所以对于上面 vert ,是无法完成功能的,所以要转换为 vertValues。
	glBufferData(GL_ARRAY_BUFFER, vertValues.size() * sizeof(float), &vertValues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, texValues.size() * sizeof(float), &texValues[0], GL_STATIC_DRAW);
}

void init(GLFWwindow* window) {
	renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
	cameraX = 0.0f; cameraY = 0.0f; cameraZ = 2.0f;
	sphLocX = 0.0f; sphLocY = 0.0f; sphLocZ = -1.0f;

	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);

	setupVertices();
	earthTexture = Utils::loadTexture("earth.jpg");
}

void display(GLFWwindow* window, double currentTime) {
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(renderingProgram);

	mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");

	vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
	mMat = glm::translate(glm::mat4(1.0f), glm::vec3(sphLocX, sphLocY, sphLocZ));
	mvMat = vMat * mMat;

	glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, earthTexture);

	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);

	glDrawArrays(GL_TRIANGLES, 0, mySphere.getNumVertices());
}

void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
	aspect = (float)newWidth / (float)newHeight;
	glViewport(0, 0, newWidth, newHeight);
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}
int main(void) {
	if (!glfwInit()) { exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(600, 600, "My Sphere", NULL, NULL);
	glfwMakeContextCurrent(window);
	if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
	glfwSwapInterval(1);
	glfwSetWindowSizeCallback(window, window_size_callback);
	init(window);
	while (!glfwWindowShouldClose(window)) {
		display(window, glfwGetTime());
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

vertShader.glsl

#version 430
layout (location=0) in vec3 pos;
layout (location=1) in vec2 texCoord;
out vec2 tc;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
void main(void) {
	gl_Position = proj_matrix * mv_matrix * vec4(pos, 1.0);
	tc = texCoord;
}

fragShader.glsl

#version 430
in vec2 tc;
out vec4 color;
layout (binding=0) uniform sampler2D samp;
void main(void){
	color = texture(samp, tc);
}

运行效果:

OpenGL中的索引

在 C++/OpenGL 中构建 Torus(环面) 类可以用与 Sphere 类几乎完全相同的方式完成。但是,我们有机会利用 OpenGL 对顶点索引的支持来利用我们在构建环面时创建的索引(我们也可以为球体做到这一点,但我们没有这样做)。对于具有数千个顶点的超大型模型,使用OpenGL 索引可以提高性能索引缓冲对象(Element Buffer Object, EBO)相当于OpenGL中的顶点数组的概念,是为了解决同一个顶点多次重复调用的问题,可以减少内存空间浪费,提高执行效率。当需要使用重复的顶点时,通过顶点的位置索引来调用顶点,而不是对重复的顶点信息重复记录,重复调用。我们使用传统的数组绘制(array drawing)方式绘制一个立方体时,可能每个面使用6个顶点(绘制两个三角形),其中2个顶点是共享的,重复了,那么一共需要制定36个顶点;如果进行压缩的话,实际上只需要8个顶点数据。为了减少这些不必要的数据,我们需要使用索引绘制(indexed drawing)

使用 OpenGL 索引时, 我们还需要将索引本身加载到 VBO 中。 我们生成一个额外的 VBO 用于保存索引。 由于每个索引值只是一个整型引用, 我们首先将索引数组复制到整型的 C++ vector<int>中,然后使用 glBufferData() 将vector<int>加载到新增的 VBO 中,指定 VBO 的类型为GL_ELEMENT_ARRAY_BUFFER,这就是告诉 OpenGL 这个 VBO 包含的是索引。执行此操作的代码可以添加到 setupVertices():

std::vector<int> ind = myTorus.getIndices();// 环面索引的读取函数返回vector<int>类型的索引
...
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);// vbo #3 是新增的VBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * sizeof(int), &ind[0], GL_STATIC_DRAW);
————————————————————————————————————————————————
1.函数glBufferData()的第3个参数[const void* data]——指定指向将复制到数据存储中进行初始化的数据的指针。
2.模板类vector,与数组一样,使用连续的存储位置存储其元素;
  这意味着也可以使用指向其元素的常规指针上的偏移量来访问其元素,其效率与数组中的相同。
3.ind[0]就是vector中的首元素,&ind[0]就是首元素地址,满足glBufferData()的第3个参数的特征标;
  第3个参数特征标前面的const声明表示传进的数据将会是只读的。

在 display()方法中,我们将 glDrawArrays()调用替换为 glDrawElements()调用,它告诉OpenGL 【利用索引 VBO 来[查找]】要绘制的顶点。我们还使用 glBindBuffer()启用包含索引的VBO, 指定哪个 VBO 包含索引并且是 GL_ELEMENT_ARRAY_BUFFER 类型。 代码如下:

numTorusIndices = myTorus.getNumIndices();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glDrawElements(GL_TRIANGLES, numTorusIndices, GL_UNSIGNED_INT, 0);// 利用索引 VBO(vbo[3]) 来查找要绘制的顶点

OpenGL 能够识别 GL_ELEMENT_ARRAY_BUFFER 的存在并利用它(索引缓冲区)来【访问顶点属性】。

通过索引可以在顶点数组中不包含重复的顶点数据,如画一个正方形:

vertices = {-1, 1, 0, //【0】
			 1, 1, 0, //【1】
			 1,-1, 0, //【2】
			-1,-1, 0, //【3】 
			-1, 1, 0, //【0】
			 1,-1, 0} //【2】
glDrawArrays(GL_TRIANGLES, 0, 6);

其中,最后面两个顶点数据是重复的。如果通过索引去画这个正方形,则有如下代码:

vertices = {-1,1,0, 1,1,0, 1,-1,0, -1,-1,0}// 无重复的顶点
vector<int> ind;// 在索引里【以点为单位距离】标明绘制时所用的顶点数据索引值
ind.push_back(0); ind.push_back(1); ind.push_back(2);// 第0、1、2三个顶点绘制第一个三角形
ind.push_back(3); ind.push_back(0); ind.push_back(2);// 第3、0、2三个顶点绘制第二个三角形
...
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glDrawElements(GL_TRIANGLES, ind.size(), GL_UNSIGNED_INT, 0);
构建一个环面



主要算法如下:

/*
* R:环半径; r:内环半径; angleSpan:将环进行环方向上和截面上单位切分的角度
* 默认 R=0.3, r=0.2, angleSpan=10.0°
*/
int cnt = 0;
for (float alpha = 0; alpha < 360; alpha += angleSpan) {
		for (float beta = 0; beta < 360; beta += angleSpan) {
			float x0 = (R + r * cos(toRadians(beta))) * cos(toRadians(alpha));
			float y0 = (R + r * cos(toRadians(beta))) * sin(toRadians(alpha));
			float z0 = -r * sin(toRadians(beta));
			// alpha∈[0,360°),对应纹理图水平方向:左->右∈[0,1)
			// beta∈[0,360°),对应纹理图垂直方向:底->顶∈[0,1)
			float s0 = alpha / 360;
			float t0 = beta / 360;

			float x1 = (R + r * cos(toRadians(beta + angleSpan))) * cos(toRadians(alpha));
			float y1 = (R + r * cos(toRadians(beta + angleSpan))) * sin(toRadians(alpha));
			float z1 = -r * sin(toRadians(beta + angleSpan));

			float s1 = alpha / 360;
			float t1 = (beta + angleSpan) / 360;

			float x2 = (R + r * cos(toRadians(beta + angleSpan))) * cos(toRadians(alpha + angleSpan));
			float y2 = (R + r * cos(toRadians(beta + angleSpan))) * sin(toRadians(alpha + angleSpan));
			float z2 = -r * sin(toRadians(beta + angleSpan));

			float s2 = (alpha + angleSpan) / 360;
			float t2 = (beta + angleSpan) / 360;

			float x3 = (R + r * cos(toRadians(beta))) * cos(toRadians(alpha + angleSpan));
			float y3 = (R + r * cos(toRadians(beta))) * sin(toRadians(alpha + angleSpan));
			float z3 = -r * sin(toRadians(beta));

			float s3 = (alpha + angleSpan) / 360;
			float t3 = beta / 360;

			// 四个点及相应纹理坐标
			vertices.push_back(glm::vec3(x0, y0, z0));
			vertices.push_back(glm::vec3(x1, y1, z1));
			vertices.push_back(glm::vec3(x2, y2, z2));
			vertices.push_back(glm::vec3(x3, y3, z3));

			texCoords.push_back(glm::vec2(s0, t0));
			texCoords.push_back(glm::vec2(s1, t1));
			texCoords.push_back(glm::vec2(s2, t2));
			texCoords.push_back(glm::vec2(s3, t3));

			// 每四个点构建两个三角形,利用索引VBO来查找要绘制的顶点
			indices.push_back(1 + cnt);
			indices.push_back(3 + cnt);
			indices.push_back(0 + cnt);
			indices.push_back(1 + cnt);
			indices.push_back(2 + cnt);
			indices.push_back(3 + cnt);
			cnt += 4;
	}
}

以上代码由于每次构建四个顶点,所以每次会重复两个顶点,实际计算出vertices.size()==5184,indices.size()==7776。

下图显示了实例化环面并使用砖纹理对其进行纹理化的效果:

另一种算法:
使用索引,代码优化,不用每次同时计算出3个顶点,而是每次产生2个就可以了,把关键逻辑放在了构建三角形的索引计算上。计算顶点的顺序如下图(第一次产生点#0, #1;第二次产生#2, #3;第三次产生#4, #5;依次类推)。

/*
* R:环半径; r:内环半径; angleSpan:将环进行环方向上和截面上单位切分的角度
*/
void Sphere::init(float R, float r, float angleSpan) {
	for (float alpha = 0; alpha < 360; alpha += angleSpan) {
		for (float beta = 0; beta < 360; beta += angleSpan) {
			float x0 = (R + r * cos(toRadians(beta))) * cos(toRadians(alpha));
			float y0 = (R + r * cos(toRadians(beta))) * sin(toRadians(alpha));
			float z0 = -r * sin(toRadians(beta));

			float s0 = alpha / 360;
			float t0 = beta / 360;

			float x1 = (R + r * cos(toRadians(beta))) * cos(toRadians(alpha + angleSpan));
			float y1 = (R + r * cos(toRadians(beta))) * sin(toRadians(alpha + angleSpan));
			float z1 = -r * sin(toRadians(beta));

			float s1 = (alpha + angleSpan) / 360;
			float t1 = beta / 360;

			// 两个点及相应纹理坐标
			vertices.push_back(glm::vec3(x0, y0, z0));
			vertices.push_back(glm::vec3(x1, y1, z1));

			texCoords.push_back(glm::vec2(s0, t0));
			texCoords.push_back(glm::vec2(s1, t1));
		}
	}
	// (0)2-1-0, 2-3-1…………总顶点数4
	// (1)4-3-2, 4-5-3…………总顶点数6
	// ...找规律...
	// (n)[2n+2]-[2n+1]-[2n], [2n+2]-[2n+3]-[2n+1]…………总顶点数N=2n+4
	for (int i = 0; i < vertices.size() - 3; i += 2) {
		indices.push_back(i + 2);
		indices.push_back(i + 1);
		indices.push_back(i);
		indices.push_back(i + 2);
		indices.push_back(i + 3);
		indices.push_back(i + 1);
	}
}

实际计算出vertices.size()==2592,indices.size()==7770,即顶点数据少了一半,索引数据少了六个。

以上算法只优化了截面上单位切分的重复点,而环方向上每次也产生重复点,还可进一步优化,让顶点数据再少一半。这里不进一步讨论了(其实原理和上面细分一样,就是原先是两层for循环产生四个顶点,后来产生两个顶点,现在产生一个顶点就行了,由indices记录围成三角形的索引就可以了)。

最终优化方案:环方向上每次也产生重复点,所以可以继续优化算法,每次产生一个顶点就行了。

加载外部构建的模型

OBJ 文件很简单,我们可以相对容易地开发一个基本的导入器。在 OBJ 文件中,通过文本行的形式指定顶点几何数据、纹理坐标、法向量和其他信息。它有一些限制——例如,OBJ 文件无法指定模型动画。

OBJ 文件中的行,以字符标记开头,表示该行上的数据类型。一些常见的标签包括:

  • v-顶点坐标;
  • vt-纹理坐标;
  • vn-顶点法向量;
  • f-面(通常是三角形中的顶点)。

还有其他标签可以用来存储对象名称、使用的材质、曲线、阴影和许多其他细节。我们这里只讨论上面列出的 4 个标签,这些标签足以导入各种复杂模型。


顶部以“ #”开头的行是由 Blender放置的注释,我们的导入器忽略了这些注释。接下来是以“ o”开头的行,给出对象的名称。我们的导入器也可以忽略这一行。之后,有一行以“ s”开头,指定表面不应该被平滑。我们的代码也会忽略以“ s”开头的行。

OBJ 文件中的第一部分有实际内容的行是以“ v”开头的那些(第 4 行~第 8 行)。它们指定金字塔模型的 5 个顶点相对于原点的 X、 Y 和 Z 局部空间坐标。在这里,原点位于金字塔的中心。

蓝色v 开头的是所有三角形顶点坐标(x, y, z);
红色vt 开头的是所有顶点的纹理坐标(纹理坐标列表比顶点列表长的原因是一些顶点参与多个三角形,并且在这些情况下可能使用不同的纹理坐标);
绿色vn 开头的是顶点法向量(该列表通常也比顶点列表长,尽管在该示例中不是这样,同样是因为一些顶点参与多个三角形,并且在那些情况下可能使用不同的法向量);
紫色f 开头的是面。

[面]格式表示的含义:(请注意OBJ 索引从 1 开始)
格式:f 顶点1/纹理1/法向量1 顶点2/纹理2/法向量2 顶点3/纹理3/法向量3
其中:顶点1(纹理1,法向量1) — 顶点2(纹理2,法向量2) — 顶点3(纹理3,法向量3) 构成一个三角形。

例如,第三个面是:f 2/7/3 5/8/3 3/9/3
这表明顶点列表(“v”开头)中的第2、第5 和 第3个顶点组成了一个三角形。
相应的纹理坐标是纹理坐标列表(“vt”开头)中的第7、第8 和 第9项。
所有 3 个顶点都具有相同的法向量,也就是法向量列表(“vn”开头)中的第 3 项。

OBJ 格式的模型并不要求具有法向量, 甚至纹理坐标。
如果模型没有纹理坐标或法向量,则面的数值将仅指定顶点索引:f 2 5 3
如果模型具有纹理坐标,但不具有法向量,则格式为:f 2/7 5/8 3/9
如果模型具有法向量但没有纹理坐标,则格式为:f 2//3 5//3 3//3

一个简单的OBJ文件加载器:
ImportedModel.h

#include <vector>
class ImportedModel {
private:
	int numVertices;
	std::vector<glm::vec3> vertices;
	std::vector<glm::vec2> texCoords;
	std::vector<glm::vec3> normalVecs;
public:
	ImportedModel();
	ImportedModel(const char *filePath);
	int getNumVertices();
	std::vector<glm::vec3> getVertices();
	std::vector<glm::vec2> getTextureCoords();
	std::vector<glm::vec3> getNormals();
};

class ModelImporter {
private:
	std::vector<float> vertVals;
	std::vector<float> triangleVerts;
	std::vector<float> textureCoords;
	std::vector<float> stVals;
	std::vector<float> normals;
	std::vector<float> normVals;
public:
	ModelImporter();
	void parseOBJ(const char *filePath);
	int getNumVertices();
	std::vector<float> getVertices();
	std::vector<float> getTextureCoordinates();
	std::vector<float> getNormals();
};

ImportedModel.cpp

#include <fstream>
#include <sstream>
#include <glm\glm.hpp>
#include "ImportedModel.h"
using namespace std;

ImportedModel::ImportedModel() {}

ImportedModel::ImportedModel(const char *filePath) {
	ModelImporter modelImporter = ModelImporter();
	modelImporter.parseOBJ(filePath);
	numVertices = modelImporter.getNumVertices();
	std::vector<float> verts = modelImporter.getVertices();
	std::vector<float> tcs = modelImporter.getTextureCoordinates();
	std::vector<float> normals = modelImporter.getNormals();

	for (int i = 0; i < numVertices; i++) {
		vertices.push_back(glm::vec3(verts[i*3], verts[i*3+1], verts[i*3+2]));
		texCoords.push_back(glm::vec2(tcs[i*2], tcs[i*2+1]));
		normalVecs.push_back(glm::vec3(normals[i*3], normals[i*3+1], normals[i*3+2]));
	}
}

int ImportedModel::getNumVertices() { return numVertices; }
std::vector<glm::vec3> ImportedModel::getVertices() { return vertices; }
std::vector<glm::vec2> ImportedModel::getTextureCoords() { return texCoords; }
std::vector<glm::vec3> ImportedModel::getNormals() { return normalVecs; }

// ---------------------------------------------------------------

ModelImporter::ModelImporter() {}

void ModelImporter::parseOBJ(const char *filePath) {
	float x, y, z;
	string content;
	ifstream fileStream(filePath, ios::in);
	string line = "";
	while (!fileStream.eof()) {
		getline(fileStream, line);
		if (line.compare(0, 2, "v ") == 0) {
			stringstream ss(line.erase(0, 1));
			ss >> x; ss >> y; ss >> z;
			vertVals.push_back(x);
			vertVals.push_back(y);
			vertVals.push_back(z);
		}
		if (line.compare(0, 2, "vt") == 0) {
			stringstream ss(line.erase(0, 2));
			ss >> x; ss >> y;
			stVals.push_back(x);
			stVals.push_back(y);
		}
		if (line.compare(0, 2, "vn") == 0) {
			stringstream ss(line.erase(0, 2));
			ss >> x; ss >> y; ss >> z;
			normVals.push_back(x);
			normVals.push_back(y);
			normVals.push_back(z);
		}
		if (line.compare(0, 2, "f ") == 0) {
			string oneCorner, v, t, n;
			stringstream ss(line.erase(0, 2));
			for (int i = 0; i < 3; i++) {
				getline(ss, oneCorner, ' ');
				stringstream oneCornerSS(oneCorner);
				getline(oneCornerSS, v, '/');
				getline(oneCornerSS, t, '/');
				getline(oneCornerSS, n, '/');

				int vertRef = (stoi(v) - 1) * 3;
				int tcRef = (stoi(t) - 1) * 2;
				int normRef = (stoi(n) - 1) * 3;

				triangleVerts.push_back(vertVals[vertRef]);
				triangleVerts.push_back(vertVals[vertRef + 1]);
				triangleVerts.push_back(vertVals[vertRef + 2]);

				textureCoords.push_back(stVals[tcRef]);
				textureCoords.push_back(stVals[tcRef + 1]);

				normals.push_back(normVals[normRef]);
				normals.push_back(normVals[normRef + 1]);
				normals.push_back(normVals[normRef + 2]);
			}
		}
	}
}
int ModelImporter::getNumVertices() { return (triangleVerts.size()/3); }
std::vector<float> ModelImporter::getVertices() { return triangleVerts; }
std::vector<float> ModelImporter::getTextureCoordinates() { return textureCoords; }
std::vector<float> ModelImporter::getNormals() { return normals; }

main.cpp

#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <glm\glm.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "ImportedModel.h"
#include "Utils.h"
using namespace std;

#define numVAOs 1
#define numVBOs 3

float cameraX, cameraY, cameraZ;
float objLocX, objLocY, objLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint shuttleTexture;

// variable allocation for display
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;

ImportedModel myModel("shuttle.obj");

float toRadians(float degrees) { return (degrees * 2.0f * 3.14159f) / 360.0f; }

void setupVertices(void) {
 	std::vector<glm::vec3> vert = myModel.getVertices();
	std::vector<glm::vec2> tex = myModel.getTextureCoords();
	std::vector<glm::vec3> norm = myModel.getNormals();

	std::vector<float> pvalues;
	std::vector<float> tvalues;
	std::vector<float> nvalues;

	for (int i = 0; i < myModel.getNumVertices(); i++) {
		pvalues.push_back((vert[i]).x);
		pvalues.push_back((vert[i]).y);
		pvalues.push_back((vert[i]).z);
		tvalues.push_back((tex[i]).s);
		tvalues.push_back((tex[i]).t);
		nvalues.push_back((norm[i]).x);
		nvalues.push_back((norm[i]).y);
		nvalues.push_back((norm[i]).z);
	}

	glGenVertexArrays(1, vao);
	glBindVertexArray(vao[0]);
	glGenBuffers(numVBOs, vbo);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
	glBufferData(GL_ARRAY_BUFFER, nvalues.size() * 4, &nvalues[0], GL_STATIC_DRAW);
}

void init(GLFWwindow* window) {
	renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
	cameraX = 0.0f; cameraY = 0.0f; cameraZ = 1.5f;
	objLocX = 0.0f; objLocY = 0.0f; objLocZ = 0.0f;

	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);

	setupVertices();
	shuttleTexture = Utils::loadTexture("spstob.jpg");
}

void display(GLFWwindow* window, double currentTime) {
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(renderingProgram);

	mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");

	vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
	mMat = glm::translate(glm::mat4(1.0f), glm::vec3(objLocX, objLocY, objLocZ));

	mMat = glm::rotate(mMat, 0.0f, glm::vec3(1.0f, 0.0f, 0.0f));
	mMat = glm::rotate(mMat, toRadians(135.0f), glm::vec3(0.0f, 1.0f, 0.0f));
	mMat = glm::rotate(mMat, toRadians(35.0f), glm::vec3(0.0f, 0.0f, 1.0f));

	mvMat = vMat * mMat;

	glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, shuttleTexture);

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glDrawArrays(GL_TRIANGLES, 0, myModel.getNumVertices());
}

void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
	aspect = (float)newWidth / (float)newHeight;
	glViewport(0, 0, newWidth, newHeight);
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}
int main(void) {
	if (!glfwInit()) { exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter6 - program1", NULL, NULL);
	glfwMakeContextCurrent(window);
	if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
	glfwSwapInterval(1);
	glfwSetWindowSizeCallback(window, window_size_callback);
	init(window);
	while (!glfwWindowShouldClose(window)) {
		display(window, glfwGetTime());
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

spstob.jpg

程序运行效果如下:

补充说明

本章中介绍的 OBJ 导入器是很有限的,并且只能处理 OBJ 格式支持的一部分功能。虽然足以满足我们的需求,但它会在某些 OBJ 文件上失败。在这些情况下,有必要首先将模型加载到 Blender(或 MAYA 等)工具中,然后将其重新导出为符合导入器限制的 OBJ 文件。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值