源代码:
OpenGL大作业OpenCraft-其他文档类资源-优快云下载
目录
1. OBJ文件读取
我们先看部分OBJ文件内容。
# material
mtllib Ground.mtl
usemtl palette
# normals
vn -1 0 0
vn 1 0 0
vn 0 0 1
vn 0 0 -1
vn 0 -1 0
vn 0 1 0
# texcoords
vt 0.00195313 0.5
vt 0.00585938 0.5
vt 0.00976563 0.5
vt 0.0136719 0.5
vt 0.0175781 0.5
vt 0.0214844 0.5
vt 0.0253906 0.5
# verts
v -50 0 50
v -50 0 49
v -50 0 48
v -50 0 47
v -50 0 46
v -50 0 45
v -50 0 44
v -50 0 43
v -50 0 42
v -50 0 41
v -50 0 40
# faces
f 101/20/1 2/20/1 1/20/1
f 102/41/1 3/41/1 2/41/1
f 102/20/1 2/20/1 101/20/1
f 103/1/1 4/1/1 3/1/1
f 103/41/1 3/41/1 102/41/1
f 104/2/1 5/2/1 4/2/1
f 104/1/1 4/1/1 103/1/1
f 105/48/1 6/48/1 5/48/1
f 105/2/1 5/2/1 104/2/1
f 106/12/1 7/12/1 6/12/1
f 106/48/1 6/48/1 105/48/1
我们看到在每一行的前面都有字母,v代表的是每个点的位置,vn代表的是每个点的法向量,vt代表的是纹理图片的坐标,f是每个面片的信息,由三组(或者四组)以斜杠分隔的整数表示该面片第 i 个顶点的 位置索引/纹理坐标索引/法向量索引
读取时我么需要将里面的数据一次放在vertex_positions, vertex_normals, vertex_textures, faces, texture_index, noemal_index 当中。
while (std::getline(fin, line))
{
std::istringstream sin(line);
std::string type;
GLfloat _x, _y, _z;
int a0, b0, c0;
int a1, b1, c1;
int a2, b2, c2;
char slash;
// 读取obj文件,记录里面的这些数据
sin >> type;
glm::vec3 tmp_node;
if (type == "v")
{
sin >> tmp_node.x >> tmp_node.y >> tmp_node.z;
vertex_positions.push_back(tmp_node);
if (MinPosition > tmp_node.z)
MinPosition = tmp_node.z;
}
if (type == "vn")
{
sin >> tmp_node.x >> tmp_node.y >> tmp_node.z;
vertex_normals.push_back(tmp_node);
vertex_colors.push_back(tmp_node);
}
if (type == "vt")
{
float x, y, z;
sin >> x >> y >> z;
vertex_textures.push_back(glm::vec2(x, y));
}
if (type == "f")
{
sin >> a0 >> slash >> b0 >> slash >> c0;
sin >> a1 >> slash >> b1 >> slash >> c1;
sin >> a2 >> slash >> b2 >> slash >> c2;
faces.push_back(vec3i(a0 - 1, a1 - 1, a2 - 1));
texture_index.push_back(vec3i(b0 - 1, b1 - 1, b2 - 1));
color_index.push_back(vec3i(c0 - 1, c1 - 1, c2 - 1));
normal_index.push_back(vec3i(c0 - 1, c1 - 1, c2 - 1));
}
// 其中vertex_color和color_index可以用法向量的数值赋值
}
此时已经将所有的点的信息传递给了数组当中。
接下来将根据面片顶点坐标,依次加入GPU points等容器中。
在此之前,先将物体的大小进行了归一化处理,也就是让所有的物体尺寸处于同一大小。这个可以通过setNormalize函数进行控制,我们看看若没有进行归一化是怎样的效果。
可以看到wawa的模型十分大,直接包围了table模型。
所以说归一化的操作是十分有必要的,让两个物体大小相差不大。
归一化代码如下:
if (do_normalize_size)
{
// 记录物体包围盒大小,可以用于大小的归一化
// 先获得包围盒的对角顶点
float max_x = -FLT_MAX;
float max_y = -FLT_MAX;
float max_z = -FLT_MAX;
float min_x = FLT_MAX;
float min_y = FLT_MAX;
float min_z = FLT_MAX;
for (int i = 0; i < vertex_positions.size(); i++)
{
auto &position = vertex_positions[i];
if (position.x > max_x)
max_x = position.x;
if (position.y > max_y)
max_y = position.y;
if (position.z > max_z)
max_z = position.z;
if (position.x < min_x)
min_x = position.x;
if (position.y < min_y)
min_y = position.y;
if (position.z < min_z)
min_z = position.z;
}
up_corner = glm::vec3(max_x, max_y, max_z);
down_corner = glm::vec3(min_x, min_y, min_z);
center = glm::vec3((min_x + max_x) / 2.0, (min_y + max_y) / 2.0, (min_z + max_z) / 2.0);
diagonal_length = length(up_corner - down_corner);
minz = FLT_MAX; //找到最低的点
minx = FLT_MAX;
miny = FLT_MAX;
for (int i = 0; i < vertex_positions.size(); i++)
{
vertex_positions[i] = (vertex_positions[i] - center) / diagonal_length;
if (minz > vertex_positions[i].z)
minz = vertex_positions[i].z;
if (miny > vertex_positions[i].y)
miny = vertex_positions[i].y;
if (minx > vertex_positions[i].x)
minx = vertex_positions[i].x;
}
}
然后将点信息放进GPU存储器当中。
for (int i = 0; i < faces.size(); i++)
{
// 坐标
points.push_back(vertex_positions[faces[i].x]);
points.push_back(vertex_positions[faces[i].y]);
points.push_back(vertex_positions[faces[i].z]);
// 颜色
colors.push_back(vertex_colors[color_index[i].x]);
colors.push_back(vertex_colors[color_index[i].y]);
colors.push_back(vertex_colors[color_index[i].z]);
// 法向量
normals.push_back(vertex_normals[normal_index[i].x]);
normals.push_back(vertex_normals[normal_index[i].y]);
normals.push_back(vertex_normals[normal_index[i].z]);
// 纹理
textures.push_back(vertex_textures[texture_index[i].x]);
textures.push_back(vertex_textures[texture_index[i].y]);
textures.push_back(vertex_textures[texture_index[i].z]);
}
这样子所有的模型存储在GPU当中,准备进行建模。
2. 物体渲染与纹理着色。
在main函数中,我编写了一个addMeshes函数,调用这个函数就可以将物体加入到painter当中,使用着色器进行进一步的渲染。
void addMeshes(glm::vec3 Translation, glm::vec3 Rotation, bool setNormalize, std::string Name,
std::string OBJLocation, std::string TextureLocation, glm::vec3 Scall = glm::vec3(1.0, 1.0, 1.0))
{
std::string vshader, fshader;
// 读取着色器并使用
// for Windows
vshader = "shaders/vshader_win.glsl";
fshader = "shaders/fshader_win.glsl";
TriMesh *TMesh = new TriMesh();
TMesh->setNormalize(true);
TMesh->readObj(OBJLocation);
// 设置物体的旋转位移
TMesh->setRotation(Rotation);
TMesh->setScale(Scall);
TMesh->setTranslation(Translation + glm::vec3(0.0, -(TMesh->miny * Scall.y), 0.0)); //使物体始终处于地面上方
TMesh->setAmbient(glm::vec4(0.2, 0.2, 0.2, 1.0)); // 环境光
TMesh->setDiffuse(glm::vec4(0.7, 0.7, 0.7, 1.0)); // 漫反射
TMesh->setSpecular(glm::vec4(0.2, 0.2, 0.2, 1.0)); // 镜面反射
TMesh->setShininess(1.0); //高光系数
// 加到painter中
painter->addMesh(TMesh, Name, TextureLocation, vshader, fshader); // 指定纹理与着色器
}
该函数当中,只需要输入物体初始状态的位置、角度、比例、物体名称、是否需要将物体归一化和物体文件地址和纹理地址。在函数中,调用TriMesh创建一个物体,设置其位置大小比例,再设置其光反射系数。最后将设置好的物体加入到painter当中,调用painter的addMesh函数,准备开始着色。
void MeshPainter::addMesh(TriMesh *mesh, const std::string &name, const std::string &texture_image,
const std::string &vshader, const std::string &fshader)
{
mesh_names.push_back(name);
meshes.push_back(mesh);
openGLObject object;
bindObjectAndData(mesh, object, texture_image, vshader, fshader);
opengl_objects.push_back(object);
};
在addMesh函数当中,首先将物体存入到meshes数组当中,所有产生的单一物体都会存放在meshes函数当中,以便之后的渲染,接着调用bindObjectAndData函数,将物体调用到着色器当中,准备渲染。
接下来,若要增加物体,直接在main文件当中调用addMeshes函数即可。
最后在display函数中,调用painter的drawMeshes函数便可实现物体在窗口当中显示。
void MeshPainter::drawMeshes(Light *light, Camera *camera)
{
drawMesh(meshes[0], opengl_objects[0], light, camera, meshes[0]->getModelMatrix(), 0);
//地面不需要阴影
for (int i = 1; i < meshes.size(); i++)
{
drawMesh(meshes[i], opengl_objects[i], light, camera, meshes[i]->getModelMatrix());
}
};
void MeshPainter::drawMesh(TriMesh *mesh, openGLObject &object,
Light *light, Camera *camera, glm::mat4 modelMatrix)
{
// 相机矩阵计算
camera->updateCamera();
camera->viewMatrix = camera->getViewMatrix();
camera->projMatrix = camera->getProjectionMatrix(true);
#ifdef __APPLE__ // for MacOS
glBindVertexArrayAPPLE(object.vao);
#else
glBindVertexArray(object.vao);
#endif
glUseProgram(object.program);
// 物体的变换矩阵
// 传递矩阵
glUniformMatrix4fv(object.modelLocation, 1, GL_FALSE, &modelMatrix[0][0]);
glUniformMatrix4fv(object.viewLocation, 1, GL_TRUE, &camera->viewMatrix[0][0]);
glUniformMatrix4fv(object.projectionLocation, 1, GL_TRUE, &camera->projMatrix[0][0]);
// 将着色器 isShadow 设置为0,表示正常绘制的颜色,如果是1着表示阴影
glUniform1i(object.shadowLocation, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, object.texture); // 该语句必须,否则将只使用同一个纹理进行绘制
// 传递纹理数据 将生成的纹理传给shader
glUniform1i(glGetUniformLocation(object.program, "texture"), 0);
// 将材质和光源数据传递给着色器
bindLightAndMaterial(mesh, object, light, camera);
// 绘制
glDrawArrays(GL_TRIANGLES, 0, mesh->getPoints().size());
#ifdef __APPLE__ // for MacOS
glBindVertexArrayAPPLE(0);
#else
glBindVertexArray(0);
#endif
glUseProgram(0);
}
经过上述操作,便可实现物体在窗口当中显示。