OpenGL学习笔记 第二周:模型的显示

模型数据的解析

  • 以下是读取到的数据,从这个数据可以看出来第一部分是顶点数据。这个只是不重复的数据而已。
  • 第二部分叫做绘制指令,一行有三组数据,表示一个三角型的三个顶点。所以有几行就说明这个模型由几个三角型组成。
  • 每一组数据说明了这个顶点的pos、texcoord和normal 在第一部分的索引,就是得到的数字-1。
  • 例如:F中第一组数据是 1/1/1 (pos/texcoord/normal)代表的含义是这个顶点的数据是v、vt、vn的第一行。
  • 因为顶点是会共用的,所以可以看到第二行数据有重复,但是因为绘制模型是按照三角型逐个绘制的,因此这些数据还是必须记录下来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//# This file uses centimeters as units for non - parametric coordinates.
// mtllib Quad.mtl
// g default
// v - 0.500000 - 0.500000 0.000000
// v 0.500000 - 0.500000 0.000000
// v - 0.500000 0.500000 0.000000
// v 0.500000 0.500000 0.000000
// vt 0.000000 0.000000
// vt 1.000000 0.000000
// vt 0.000000 1.000000
// vt 1.000000 1.000000
// vn 0.000000 0.000000 1.000000
// vn 0.000000 0.000000 1.000000
// vn 0.000000 0.000000 1.000000
// vn 0.000000 0.000000 1.000000
// s 1

// g Quad
// usemtl initialShadingGroup
// f 1 / 1 / 1 2 / 2 / 2 3 / 3 / 3
// f 3 / 3 / 3 2 / 2 / 2 4 / 4 / 4
  • 从上面的数据可以得出两个重要的数据集合,第一个是所有顶点的集合,每个点包括位置、纹理坐标和法线。
  • 一个顶点索引集合,它记录了绘制指令部分有多少个指令,同时也记录了每个指令真正对应的包括位置、纹理坐标和法线是什么。

OpenGL数据组织

  • 一个点的数据可以简化为下面的结构体
1
2
3
4
5
6
struct VertexData
{
float positions[3];
float normals[3];
float texcoords[2];
};
  • 还需要一个记录所有点的集合 VertexData* mVertexData和索引的集合unsigned short* mIndexes;

  • 从模型文件中读取数据,(unsigned char*)fileContent是从文件中读到的二进制数据。

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
125
126
127
128
129
130
131
132
133
134
135
136
   // 文件读到字符串流中
std::stringstream ssFileContent((char*)fileContent);
std::string temp; // 用来保存每行的首个字符串 v vt vn f 等
std::vector<FloatData> positions, texcoords, normals;

// 记录F部分所有出现过的顶点数据,不重复。它的个数就是真正模型点的个数。
std::vector<VertexDefine> vertexes;
// 记录每一个出现过的顶点的真实数据在vertexes的索引。indexes的顺序就是绘制三角形带的顶点顺序。
std::vector<int> indexes;

// 临时变量,把读取到的行数据放到这个里面
char szOneLine[256];
// 直到最后一行
while (!ssFileContent.eof())
{
memset(szOneLine, 0, 256);
ssFileContent.getline(szOneLine, 256);
if (strlen(szOneLine) <= 0)
{
continue;
}
// 在文件内容中,v、vt、vn都是顶点相关的信息,分别是:position,texcoord和normal
if (szOneLine[0] == 'v')
{
// 行字符串扔到流里
std::stringstream ssOneLine(szOneLine);

if (szOneLine[1] == 't')
{
FloatData floatData;
ssOneLine >> temp; // 读出"vt"
ssOneLine >> floatData.v[0]; // 读出VT后的第1个float
ssOneLine >> floatData.v[1]; // 读出VT后的第2个float

texcoords.push_back(floatData);

printf("texcoord : %f , %f\n", floatData.v[0], floatData.v[1]);
}
else if (szOneLine[1] == 'n')
{
FloatData floatData;
ssOneLine >> temp; // 读出"vn"
ssOneLine >> floatData.v[0]; // 读出vn后的第1个float
ssOneLine >> floatData.v[1]; // 读出vn后的第2个float
ssOneLine >> floatData.v[2]; // 读出vn后的第3个float

normals.push_back(floatData);

printf("normal : %f , %f , %f\n", floatData.v[0], floatData.v[1], floatData.v[2]);
}
else
{
FloatData floatData;
ssOneLine >> temp; // 读出"v"
ssOneLine >> floatData.v[0]; // 读出v后的第1个float
ssOneLine >> floatData.v[1]; // 读出v后的第2个float
ssOneLine >> floatData.v[2]; // 读出v后的第3个float

positions.push_back(floatData);

printf("position : %f , %f, %f \n", floatData.v[0], floatData.v[1], floatData.v[2]);
}
}
else if (szOneLine[0] == 'f') // f是绘制指令
{
std::stringstream ssOneLine(szOneLine);
ssOneLine >> temp;
std::string vertexStr;
for (int i = 0; i < 3; i++) // 3是因为每一行就是一个三角型的三个顶点数据
{
ssOneLine >> vertexStr; // 1/1/1
size_t pos1 = vertexStr.find_first_of('/'); // pos=1
std::string posIndexStr = vertexStr.substr(0, pos1); // 得到1

size_t pos2 = vertexStr.find_first_of('/', pos1 + 1); // pos=3
std::string texcoordIndexStr = vertexStr.substr(pos1 + 1, pos2 - (pos1 + 1)); // 得到1
std::string normalIndexStr = vertexStr.substr(pos2 + 1, vertexStr.length() - (pos2 + 1)); // 得到1

VertexDefine vd;
vd.posIndex = atoi(posIndexStr.c_str());
vd.texcoordIndex = atoi(texcoordIndexStr.c_str());
vd.nomalIndex = atoi(normalIndexStr.c_str());

// 这个变量最终记录的是每个点所引用的vertexes中的点的下标。
// 这样在最后组织三角片数据时,按照记录的索引直接去vertexes找数据就行了。
int nCurrentVertexIndex = -1;
int nCurrentVertexCount = (int)vertexes.size();

// 循环vertexes当前的集合,如果本次获得的vd是新的则记录下来。
// 最终这里记录的所有出现过的顶点数据,重复的不记录
for (int j = 0; j < nCurrentVertexCount; j++)
{
// 全相等说明是一个重复的数据
if (vertexes[j].posIndex == vd.posIndex &&
vertexes[j].texcoordIndex == vd.texcoordIndex &&
vertexes[j].nomalIndex == vd.nomalIndex)
{
// 告知当前这个点用到了vertexes下标是几的点
nCurrentVertexIndex = j;
break;
}
}

// =-1说明这个点是全新的
if (nCurrentVertexIndex == -1)
{
// 所以这个顶点所引用的vertexes索引就是当前的长度,因为是小标是0开始的。
nCurrentVertexIndex = (int)vertexes.size();
vertexes.push_back(vd);
}

indexes.push_back(nCurrentVertexIndex); // indexes这个是永远增加的
}

}
} // end while

mIndexCount = (int)indexes.size();
mIndexes = new unsigned short[mIndexCount];

for (int i = 0; i < mIndexCount; i++)
{
mIndexes[i] = indexes[i];
}

int vertexCount = (int)vertexes.size(); // 顶点个数
mVertexData = new VertexData[vertexCount];

// 这里最终每个mVertexData都是一个唯一的点,包含了pos、texcoord和normal
for (int i = 0; i < vertexCount; i++)
{
memcpy(mVertexData[i].positions, positions[vertexes[i].posIndex - 1].v, sizeof(float) * 3);
memcpy(mVertexData[i].texcoords, texcoords[vertexes[i].texcoordIndex - 1].v, sizeof(float) * 2);
memcpy(mVertexData[i].normals, normals[vertexes[i].nomalIndex - 1].v, sizeof(float) * 3);

}

绘制模型

  • OpenGL中绘制模型的代码,其中mVertexData是顶点数据的集合。mIndexes为索引的集合。
  • 模型本身也可以设置对于环境光、漫反射和高光的反射率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   glEnable(GL_LIGHTING);
glMaterialfv(GL_FRONT, GL_AMBIENT, mAmbientMaterial);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mDiffuseMaterial);
glMaterialfv(GL_FRONT, GL_SPECULAR, mSpecularMaterial);

glColor4ub(200, 0, 255, 255);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, mTexture);
glEnable(GL_DEPTH_TEST);

glPushMatrix();
glTranslatef(0.0f, 0.0f, -5.0f);
glBegin(GL_TRIANGLES);
// 按照绘制指令中顶点的顺序获取真正的顶点数据,然后传给OpenGL
for (int i = 0; i < mIndexCount; i++)
{
glTexCoord2fv(mVertexData[mIndexes[i]].texcoords);
glNormal3fv(mVertexData[mIndexes[i]].normals);
// 此处注意:先设置顶点的其他属性,最后设置位置,否则效果会不对。
glVertex3fv(mVertexData[mIndexes[i]].positions);
}
glEnd();
glPopMatrix();
# OpenGL
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×