build a ring
First draw a circle on the xy plane, with the origin as the center and the outer diameter as the radius. (It can be seen as translating a point from the origin along the x-axis direction by the length of the outer diameter, and then rotating it around the Z-axis to get the circle. ), and then translate the circle along the x-axis direction by the distance of the inner diameter. As shown in the left picture above, it is the cross section of the ring, thus obtaining the first component of the ring.
Rotate each vertex of this circle along the Y axis once again to get a ring. As shown in the picture on the right above, they are different torus formed during the rotation of the circle around the Y-axis.
When building these vertices, texture coordinates and normal vectors are calculated for each vertex. A vector tangent to the torus surface (called a tangent vector) is additionally generated for each vertex.
After the vertices are created, all vertices are traversed ring by ring and for each vertex two triangles are generated. The six index table entries for the two triangles are generated in a similar way to the previous sphere.
Our strategy for selecting texture coordinates for the remaining rings is to arrange them so that the S-axis of the texture image surrounds half of the horizontal perimeter of the torus, and then repeat for the other half. When we rotate around the Y-axis to generate the ring, we specify a variable ring that starts at 1 and increases to the specified precision (again called "prec"). We then set the S texture coordinate value to ring*2.0/prec so that S ranges between 0.0 and 2.0, and then subtract 1.0 whenever the texture coordinate is greater than 1.0. The motivation for this approach is to avoid excessive "stretching" of the texture image in the horizontal direction. Conversely, if we really want the texture to stretch completely around the torus, we simply remove the "*2.0" multiplier from the texture coordinate calculation.
OpenGL indexing is used here and we need to load the index itself into the VBO. Specify the type of the VBO as GL_ELEMENT_ARRAY_BUFFER (this tells OpenGL that the VBO contains an index).
This replaces the glDrawArrays() call with a glDrawElements() call, which tells OpenGL to use the index VBO to find the vertices to draw. We also enable VBOs containing indexes using glBindBuffer(), specifying which VBO contains the index and is of type GL_ELEMENT_ARRAY_BUFFER.
The prec variable has a similar effect to that of a sphere, with similar calculations of the number of vertices and indexes.
Two tangent vectors (sTangent and tTangent, although often called "tangent" and "bitangent") are calculated, and their cross product forms the normal vector.
code show as below:
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 }
The effect is as shown in the figure: