构建一个环
先在xy平面上画圆,以原点为圆心,外径为半径画圆,(可以看作将一个点从原点沿x轴方向平移外径长度的距离,然后绕Z轴旋转一周,得到了圆),然后,将这个圆沿x轴方向再平移内径的距离,如上左图,是环的切面,这样得到了环的第一个组成部分。
将这个圆各个顶点再沿Y轴旋转一周,就得到了环。如上右图,是圆绕Y轴旋转过程中形成的不同环面。
在构建这些顶点时,为每个顶点计算纹理坐标和法向量。还会额外为每个顶点生成与环面表面相切的向量(称为切向量)。
在顶点创建之后,逐个环地遍历所有顶点,并且对于每个顶点生成两个三角形。两个三角形的六个索引表条目的生成方式和之前的球体类似。
我们为剩余的环选择纹理坐标的策略,是将它们排列成使得纹理图像的S轴环绕环面的水平周边的一半,然后再对另一半重复。当我们绕Y轴旋转生成环时,我们指定一个从1开始并增加到指定精度的变量环(再次称为“prec”)。然后我们将S纹理坐标值设置为ring*2.0/prec,使S的取值范围介于0.0和2.0之间,然后每当纹理坐标大于1.0时减去1.0。这种方法的动机是避免纹理图像在水平方向上过度“拉伸”。反之,如果我们确实希望纹理完全围绕环面拉伸,我们只须从纹理坐标计算中删除“*2.0”乘数即可。
这里使用OpenGL索引,我们需要将索引本身加载到VBO中。指定VBO的类型为GL_ELEMENT_ARRAY_BUFFER(这会告诉OpenGL这个VBO包含索引)。
这里将glDrawArrays()调用替换为glDrawElements()调用,它告诉OpenGL利用索引VBO来查找要绘制的顶点。我们还使用glBindBuffer()启用包含索引的VBO,指定哪个VBO包含索引并且是GL_ELEMENT_ARRAY_BUFFER类型。
prec变量具有与球体类似的作用,对顶点数量和索引数量进行类似的计算。
计算了两个切向量(sTangent和tTangent,尽管通常称为“切向量(tangent)”和“副切向量(bitangent)”),它们的叉乘积形成法向量。
代码如下:
环面类 Torus
1 #pragma once
2 #include <vector>
3 #include <glm\glm.hpp>
4 #include <cmath>
5 class Torus
6 {
7 private:
8 int numVertices;
9 int numIndices;
10 int prec;
11 float inner;
12 float outer;
13 std::vector<int> indices;
14 std::vector<glm::vec3> vertices;
15 std::vector<glm::vec2> texCoords;
16 std::vector<glm::vec3> normals;
17 std::vector<glm::vec3> sTangents;
18 std::vector<glm::vec3> tTangents;
19 void init();
20 float toRadians(float degrees);
21
22 public:
23 Torus();
24 Torus(float innerRadius, float outerRadius, int prec);
25 ~Torus();
26 int getNumVertices();
27 int getNumIndices();
28 std::vector<int> getIndices();
29 std::vector<glm::vec3> getVertices();
30 std::vector<glm::vec2> getTexCoords();
31 std::vector<glm::vec3> getNormals();
32 std::vector<glm::vec3> getStangents();
33 std::vector<glm::vec3> getTtangents();
34 };
35
36 #include "Torus.h"
37 #include <iostream>
38 using namespace std;
39 #include "glm/gtc/matrix_transform.hpp"
40 void Torus::init()
41 {
42 numVertices = (prec + 1) * (prec + 1);
43 numIndices = prec * prec * 6;
44 for (int i = 0; i < numVertices; i++)
45 {
46 vertices.push_back(glm::vec3());
47 texCoords.push_back(glm::vec2());
48 normals.push_back(glm::vec3());
49 sTangents.push_back(glm::vec3());
50 tTangents.push_back(glm::vec3());
51 }
52 for (int i = 0; i < numIndices; i++)
53 {
54 indices.push_back(0);
55 }
56
57 // 计算第一个环
58 for (int i = 0; i < prec + 1; i++)
59 {
60 float amt = toRadians(i * 360.0f / prec);
61 // 绕原点旋转点,形成环,然后将它们向外移动
62 glm::mat4 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 0.0f, 1.0f));
63 glm::vec3 initPos(rMat * glm::vec4(outer, 0.0f, 0.0f, 1.0f));
64 vertices[i] = glm::vec3(initPos + glm::vec3(inner, 0.0f, 0.0f));
65
66 // 为环上的每个顶点计算纹理坐标
67 texCoords[i] = glm::vec2(0.0f, ((float)i / (float)prec));
68
69 // 计算切向量和法向量,第一个切向量是绕Z轴旋转的Y轴
70 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 0.0f, 1.0f));
71 tTangents[i] = glm::vec3(rMat * glm::vec4(0.0f, -1.0f, 0.0f, 1.0f));
72 sTangents[i] = glm::vec3(glm::vec3(0.0f, 0.0f, -1.0f));
73 // 第二个切向量是-Z轴
74 normals[i] = glm::cross(tTangents[i], sTangents[i]);
75 // 它们的叉乘积就是法向量
76
77 // 绕Y轴旋转最初的那个环,形成其他的环
78 for (int ring = 1; ring < prec + 1; ring++)
79 {
80 for (int vert = 0; vert < prec + 1; vert++)
81 {
82 // 绕Y轴旋转最初那个环的顶点坐标
83 float amt = (float)(toRadians(ring * 360.0f / prec));
84 glm::mat4 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
85 vertices[ring * (prec + 1) + i] = glm::vec3(rMat * glm::vec4(vertices[i], 1.0f));
86
87 // 计算新环顶点的纹理坐标
88 texCoords[ring * (prec + 1) + vert] =
89 glm::vec2((float)ring/** 2.0f*//(float)prec, texCoords[vert].t);
90 if (texCoords[ring * (prec + 1) + i].s > 1.0)
91 {
92 texCoords[ring * (prec + 1) + i].s -= 1.0f;
93 }
94
95 // 绕Y轴旋转切向量和副切向量
96 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
97 sTangents[ring * (prec + 1) + i] = glm::vec3(rMat * glm::vec4(sTangents[i], 1.0f));
98 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
99 tTangents[ring * (prec + 1) + i] = glm::vec3(rMat * glm::vec4(tTangents[i], 1.0f));
100
101 // 绕Y轴旋转法向量
102 rMat = glm::rotate(glm::mat4(1.0f), amt, glm::vec3(0.0f, 1.0f, 0.0f));
103 normals[ring * (prec + 1) + i] = glm::vec3(rMat * glm::vec4(normals[i], 1.0f));
104 }
105 }
106 }
107 // 按照逐个顶点的两个三角形,计算三角形索引
108 for (int ring = 0; ring < prec; ring++)
109 {
110 for (int vert = 0; vert < prec; vert++)
111 {
112 indices[((ring * prec + vert) * 2) * 3 + 0] = ring * (prec + 1) + vert;
113 indices[((ring * prec + vert) * 2) * 3 + 1] = (ring + 1) * (prec + 1) + vert;
114 indices[((ring * prec + vert) * 2) * 3 + 2] = ring * (prec + 1) + vert + 1;
115 indices[((ring * prec + vert) * 2 + 1) * 3 + 0] = ring * (prec + 1) + vert + 1;
116 indices[((ring * prec + vert) * 2 + 1) * 3 + 1] = (ring + 1) * (prec + 1) + vert;
117 indices[((ring * prec + vert) * 2 + 1) * 3 + 2] = (ring + 1) * (prec + 1) + vert + 1;
118 }
119 }
120 }
121
122 float Torus::toRadians(float degrees)
123 {
124 return (degrees * 2.0f * 3.14159f) / 360.0f;
125 }
126
127 Torus::Torus()
128 {
129 prec = 48;
130 inner = 0.5f;
131 outer = 0.2f;
132 init();
133 }
134
135 Torus::Torus(float innerRadius, float outerRadius, int precIn)
136 {
137 prec = precIn;
138 inner = innerRadius;
139 outer = outerRadius;
140 init();
141 }
142
143 Torus::~Torus()
144 {
145 }
146
147 int Torus::getNumVertices()
148 {
149 return numVertices;
150 }
151
152 int Torus::getNumIndices()
153 {
154 return numIndices;
155 }
156
157 std::vector<int> Torus::getIndices()
158 {
159 return indices;
160 }
161
162 std::vector<glm::vec3> Torus::getVertices()
163 {
164 return vertices;
165 }
166
167 std::vector<glm::vec2> Torus::getTexCoords()
168 {
169 return texCoords;
170 }
171
172 std::vector<glm::vec3> Torus::getNormals()
173 {
174 return normals;
175 }
176
177 std::vector<glm::vec3> Torus::getStangents()
178 {
179 return sTangents;
180 }
181
182 std::vector<glm::vec3> Torus::getTtangents()
183 {
184 return tTangents;
185 }
main.cpp
1 ...
2 #include "Torus.h"
3 ...
4 Torus myTorus(0.5f, 0.2f, 48);
5 ...
6 void setupVertices(void)
7 {
8 std::vector<int> ind = myTorus.getIndices(); //索引
9 std::vector<glm::vec3> vert = myTorus.getVertices(); //顶点
10 std::vector<glm::vec2> tex = myTorus.getTexCoords(); //纹理
11 std::vector<glm::vec3> norm = myTorus.getNormals(); //法向量
12
13 std::vector<float> pvalues; //顶点位置
14 vector<float> tvalues; //纹理坐标
15 vector<float> nvalues; //法向量
16
17 int numVertices = myTorus.getNumVertices();
18 for (int i = 0; i < numVertices; i++)
19 {
20 pvalues.push_back((vert[i]).x);
21 pvalues.push_back((vert[i]).y);
22 pvalues.push_back((vert[i]).z);
23
24 tvalues.push_back((tex[i]).s);
25 tvalues.push_back((tex[i]).t);
26
27 nvalues.push_back((norm[i]).x);
28 nvalues.push_back((norm[i]).y);
29 nvalues.push_back((norm[i]).z);
30 }
31
32 glGenVertexArrays(1, vao); // 创建一个vao,并返回它的整数型ID存进数组vao中
33 glBindVertexArray(vao[0]); // 激活vao
34 glGenBuffers(numVBOs, vbo);// 创建4个vbo,并返回它们的整数型ID存进数组vbo中
35
36 // 把顶点放入缓冲区 #0
37 glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
38 glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);
39 // 把纹理坐标放入缓冲区 #1
40 glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
41 glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW);
42 // 把法向量放入缓冲区 #2
43 glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
44 glBufferData(GL_ARRAY_BUFFER, nvalues.size() * 4, &nvalues[0], GL_STATIC_DRAW);
45 // 把索引放入缓冲区 #3
46 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
47 glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * 4, &ind[0], GL_STATIC_DRAW);
48 }
49 ...
50 void display(GLFWwindow* window, double currentTime)
51 {
52 ...
53
54 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
55 glDrawElements(GL_TRIANGLES, myTorus.getNumIndices(), GL_UNSIGNED_INT, 0);
56 }
效果如图所示: