阅读《计算机图形学编程(使用OpenGL和C++)》10 - 画环形
构建一个环
先在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 }
效果如图所示: