0%

【光栅化渲染器】(七)加载模型

上一节我们实现了各种剔除和裁剪算法,目的是在处理复杂模型和场景的时候也能够保证效率,避免无用计算。这一节我们来向场景中加载模型。

1 模型、对象、网格的关系

在实现加载模型的功能之前,我们首先要了解模型由什么组成。一个模型(Model)包含多个对象(Object),每个对象拥有网格(Mesh)和材质(Material),网格存储了多边形的绘制信息,包括顶点位置、顶点法向、顶点纹理坐标,顶点索引;材质存储了光照和贴图信息,比如漫反射光颜色、镜面反射光颜色、镜面反射光泽度、纹理贴图信息等。所有这些数据都提供给 Shader 来使用,因此一个 Shader 需要绑定一个材质才能发挥作用。

2 材质与对象类

了解了这些关系后,我们开始实现相应的类。首先是材质类,最简单的材质包括漫反射颜色、镜面反射颜色、Glossy 扰动和一张主纹理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
* 材质类
*/
#pragma once
#ifndef MATERIAL_H
#define MATERIAL_H

#include "Global.h"
#include "Texture.h"
#include "Shader.h"

//标准光照材质包括
//漫反射颜色 镜面反射颜色 镜面反射强度
//纹理一张
class Material {
public:
glm::vec4 Color;
glm::vec4 Specular;
int Gloss;
Texture* MainTex;
Shader* shader;

Material() :
Color(glm::vec4(1.0, 1.0, 1.0, 1.0)),
Specular(glm::vec4(1.0, 1.0, 1.0, 1.0)),
Gloss(32),
MainTex(nullptr)
{}
Material(const glm::vec4& color, const glm::vec4& specular, const int& gloss) :
Color(color),
Specular(specular),
Gloss(gloss),
MainTex(nullptr)
{}

~Material() = default;

void SetShader(Shader* s) {
shader = s;
}
void SetTexture(Texture* t) {
MainTex = t;
}
};

#endif

然后是 Object 类,一个 Object 包含一个 Mesh 和一个 Material:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* Object类
*/
#pragma once
#ifndef OBJECT_H
#define OBJECT_H

#include "Mesh.h"
#include "Material.h"

class Object {

public:
Mesh mesh;
Material material;

Object() = default;
~Object() = default;
Object(const Object& obj) {
mesh = obj.mesh;
material = obj.material;
}
Object(const Mesh& m, const Material& mat) {
mesh = m;
material = mat;
}
Object& operator= (const Object& obj) {
if (&obj == this)
return *this;
mesh = obj.mesh;
material = obj.material;
return *this;
}
};

#endif

3 模型类

最后是模型类,模型类需要能够加载 obj 模型并解析,之前在图形学中我们了解过 obj 文件的格式,这里再复习一遍。

一个典型的 obj 文件使用记事本打开可以看到如下格式,这里使用我们项目中用到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 04.08.2011 15:18:00

#
# object Object01
#

v -7.0063 72.8042 6.8872
v -6.9854 72.8711 8.2541
v -7.8984 74.1601 6.7762
......
# 65 vertices

vn -0.8222 -0.5678 0.0404
vn -0.7979 -0.5926 0.1101
vn -0.8362 -0.5469 0.0396
......
# 70 vertex normals

vt 0.4465 -0.7212 0.0000
vt 0.4014 -0.7905 0.0000
vt 0.4932 -0.8096 0.0000
......
# 36 texture coords

g Object01
s 1
f 1/1/1 2/2/2 3/3/3
s 2
f 3/3/4 2/2/2 4/4/5
s 3
f 5/5/6 2/2/2 1/1/1
......
# 0 polygons - 104 triangles

#
# object Object02
#

......

每一个 Object 下面都有 v、vn、vt 和 g、s、f 开头的数据,它们分别代表:

  • v(vertex) :后面记录了一个顶点坐标
  • vn(vertex normal):后面记录了一个法线
  • vt(vertex texcood):后面记录了一个纹理坐标
  • g(geometry):代表下面将开始几何信息
  • s:后面跟图元编号
  • f(face):后面后面记录了一个面的三个顶点所使用的顶点坐标、法线和纹理坐标(也可以是四个或者多个组成多边形)
  • 5/5/6:表示该顶点使用第 5 个 v,第 5 个 vn,第 6 个 vt,这个索引是全局的

于是我们可以实现模型 Model 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
* Model类
*/
#pragma once
#ifndef MODEL_H
#define MODEL_H

#include "Object.h"
#include "Global.h"

class Model
{

public:
std::vector<Object> objects;
Model() = default;

~Model() = default;
Model(const std::string& path) {
LoadObj(path);
}

Model(const Model& model) {
objects = model.objects;
}

Model& operator=(const Model& model) {
if (&model == this)
return *this;
objects = model.objects;
return *this;
}

void SetMaterial(const int& id, const Material& m) {
objects[id].material = m;
}

void LoadObj(const std::string& path) {
std::ifstream in(path);
if (!in) {
std::cout << "Open Obj File Error !" << std::endl;
return;
}

std::vector<glm::vec3> vertexs;
std::vector<glm::vec3> normals;
std::vector<glm::vec2> texcoords;

std::string line;

int currentObjectNums = -1;
bool flag = false;
while (!in.eof()) {
std::getline(in, line);
// 顶点数据
if (!line.compare(0, 2, "v "))
{
if (!flag) {
currentObjectNums++;
Object o;
objects.push_back(o);
flag = true;
}
line = line.substr(2);
std::istringstream iss(line);
glm::vec3 v;
iss >> v.x;
iss >> v.y;
iss >> v.z;
vertexs.push_back(v);
continue;
}
// 法线数据
if (!line.compare(0, 3, "vn "))
{
line = line.substr(3);
std::istringstream iss(line);
glm::vec3 vn;
iss >> vn.x;
iss >> vn.y;
iss >> vn.z;
normals.push_back(vn);
continue;
}
// 纹理坐标
if (!line.compare(0, 3, "vt "))
{
line = line.substr(3);
std::istringstream iss(line);
glm::vec3 vt;
iss >> vt.x;
iss >> vt.y;
vt.y = 1 - vt.y;
// 纹理坐标z为0
iss >> vt.z;
texcoords.push_back(glm::vec2(vt.x, vt.y));
continue;
}
// 图元数据
if (!line.compare(0, 2, "f "))
{
if (flag)
flag = false;
line = line.substr(2);
std::istringstream iss(line);
char bar;
int vIndex, vtIndex, vnIndex;
//解析每个顶点数据 eg:1/1/1
int offset = objects[currentObjectNums].mesh.VBO.size();
for (int i = 0; i < 3; i++) {
iss >> vIndex >> bar >> vtIndex >> bar >> vnIndex;
Vertex vertex(vertexs[vIndex - 1], glm::vec4(1, 1, 1, 1), texcoords[vtIndex - 1], normals[vnIndex - 1]);
objects[currentObjectNums].mesh.VBO.push_back(vertex);
objects[currentObjectNums].mesh.EBO.push_back(offset + i);
}
continue;
}
}
in.close();
}

};

#endif

4 绘制模型

接下来在我们原来的 DarwMesh 函数基础上,稍作修改实现绘制对象和绘制模型的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 绘制一个模型
void DrawModel(Model& model) {
for (int i = 0; i < model.objects.size(); i++) {
DrawObject(model.objects[i]);
}
}

// 绘制一个对象
void DrawObject(Object& obj) {
if (obj.mesh.EBO.empty()) {
return;
}
currentMat = &obj.material;
currentMat->shader->texture = currentMat->MainTex;
for (int i = 0; i < obj.mesh.EBO.size(); i += 3) {
Vertex p1, p2, p3;
p1 = obj.mesh.VBO[obj.mesh.EBO[i]];
p2 = obj.mesh.VBO[obj.mesh.EBO[i + 1]];
p3 = obj.mesh.VBO[obj.mesh.EBO[i + 2]];

// 顶点着色器变换到裁剪空间
V2F v1, v2, v3;
v1 = currentMat->shader->VertexShader(p1);
v2 = currentMat->shader->VertexShader(p2);
v3 = currentMat->shader->VertexShader(p3);

// 视锥体剔除
UpdateViewPlanes();
if (FrustumCull && !WorldFrustumCull(ViewPlanes, v1.worldPos / v1.w, v2.worldPos / v2.w, v3.worldPos / v3.w))
{
continue;
}

// 正面/背面剔除
if (FaceCull && WorldFaceCull(CullMode, v1.worldPos, v2.worldPos, v3.worldPos))
{
continue;
}

// 裁剪空间剔除
if (!ClipSpaceCull(v1.windowPos, v2.windowPos, v3.windowPos)) {
continue;
}
// 裁剪
std::vector<V2F> clipingVertexs = SutherlandHodgeman(v1, v2, v3);

// 做透视除法,变换到NDC
for (int i = 0; i < clipingVertexs.size(); ++i) {
PerspectiveDivision(clipingVertexs[i]);
}

// 渲染
int n = clipingVertexs.size() - 3 + 1;
for (int i = 0; i < n; ++i) {
V2F tempv1 = clipingVertexs[0];
V2F tempv2 = clipingVertexs[i + 1];
V2F tempv3 = clipingVertexs[i + 2];

// 视口变换,变换到屏幕空间
tempv1.windowPos = ViewPortMatrix * tempv1.windowPos;
tempv2.windowPos = ViewPortMatrix * tempv2.windowPos;
tempv3.windowPos = ViewPortMatrix * tempv3.windowPos;

// 画线
if (renderMode == Line) {
DrawLine(tempv1, tempv2);
DrawLine(tempv2, tempv3);
DrawLine(tempv3, tempv1);
}
// 光栅化
else {
ScanLineTriangle(tempv1, tempv2, tempv3);
}
}
}
}

5 测试

修改主函数,加载模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include "Global.h"
#include "Draw.h"
#include "Camera.h"
#include "Material.h"

unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const std::string TEXTURE_PATH = "D:\\TechStack\\ComputerGraphics\\RenderEngine\\Assets\\Textures\\box.png";
const std::string OUT_PATH = "D:\\TechStack\\ComputerGraphics\\RenderEngine\\Results\\";
const std::string FILE_NAME = "Model.png";

int main()
{
// 使用标准Shader
Shader shader;
// 加载模型
Material bodyMat;
bodyMat.SetShader(&shader);
Texture bodyTexture("D:\\TechStack\\ComputerGraphics\\RenderEngine\\Assets\\neptune\\Texf_body02.jpg");
bodyMat.SetTexture(&bodyTexture);

Material faceMat;
faceMat.SetShader(&shader);
Texture faceTexture("D:\\TechStack\\ComputerGraphics\\RenderEngine\\Assets\\neptune\\Tex002f_body01.jpg");
faceMat.SetTexture(&faceTexture);

Material mouseMat;
mouseMat.SetShader(&shader);
Texture mouseTexture("D:\\TechStack\\ComputerGraphics\\RenderEngine\\Assets\\neptune\\Texf_mouse.jpg");
mouseMat.SetTexture(&mouseTexture);

Material eyeMat;
eyeMat.SetShader(&shader);
Texture eyeTexture("D:\\TechStack\\ComputerGraphics\\RenderEngine\\Assets\\neptune\\Tex001f_eye.jpg");
eyeMat.SetTexture(&eyeTexture);

Model model("D:\\TechStack\\ComputerGraphics\\RenderEngine\\Assets\\neptune\\neptune.obj");
model.SetMaterial(0, mouseMat);
model.SetMaterial(1, faceMat);
model.SetMaterial(2, bodyMat);
model.SetMaterial(3, eyeMat);

// 创建箱子
Mesh box = CreateBox(glm::vec3(0.0, 0.0, 0.0), 0.5);
Material mat;
mat.SetShader(&shader);
Texture boxt(TEXTURE_PATH);
mat.SetTexture(&boxt);
Object obj(box, mat);

//初始化相机
camera = new Camera(
glm::vec3(0.0f, 0.0f, 5.0f), // 相机位置
glm::vec3(0.0f, 1.0f, 0.0f), // 世界空间的up方向
glm::vec3(0.0f, 0.0f, 4.0f), // 相机lookat
60.0f, // 垂直视场
SCR_WIDTH, // 宽
SCR_HEIGHT, // 高
0.3f, // near
100 // far
);

// 初始化渲染器
dw = new Draw(SCR_WIDTH, SCR_HEIGHT, TEXTURE_PATH);
dw->Init();
dw->ClearBuffer(glm::vec4(0, 0, 0, 1));

// 开启视锥体剔除,实际上默认已经开启
dw->EnableFrustumCull();
// 开启背面剔除,背面剔除默认关闭,需要手动开启并指定剔除模式
Face CullMode = Back;
dw->EnableFaceCull(CullMode);
// 改变渲染模式,默认为纹理填充,改变后为只绘制边框
//dw->ChangeRenderMode();

// 设置观察矩阵
ViewMatrix = camera->ViewMatrix();
// 设置投影矩阵
ProjectMatrix = camera->PerspectiveMatrix();

// 将箱子左移两单位
ModelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(2, 0, 0));
// 让箱子在原点绕着(1,1,0)旋转angle度
float angle = 0.0;
ModelMatrix *= glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::vec3(1.0, 1.0, 0.0));
// 旋转后要更新法线
UpdateNormalMatrix();
// 绘制箱子
dw->DrawObject(obj);

// 因为模型太大,需要缩小100倍
ModelMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(0.01, 0.01, 0.01));
// 绘制模型
dw->DrawModel(model);

// 写出图片
std::string filepath = OUT_PATH + FILE_NAME;
dw->show(filepath);

return 0;
}

看下效果:

Model1

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。