首先可以看到,球体类似于地球经纬度,经度旋转360度,纬度旋转180度,就可以表示整个球体的每个位置


那么,我们将经度为x与z轴,维度为y轴,进行代码运算,则刚刚好能计算出一个球体
这里还要用到pitch和yaw角的运算,learnopengl中有一篇摄像机的文章讲述了这里,我就不再说明了
这是链接地址,如果看不懂也可以百度去看看大家的理解
https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
我们先创建球体顶点
我这里用的是qt,所以有qvector类,大家如果用的是glm,则的glm::vec3
std::vector<QVector3D> positions;
std::vector<QVector2D> uv;
const unsigned int X_SEGMENTS = 64;
const unsigned int Y_SEGMENTS = 64;
for (unsigned int x = 0; x <= X_SEGMENTS; ++x)
{
for (unsigned int y = 0; y <= Y_SEGMENTS; ++y)
{
float xSegment = (float)x / (float)X_SEGMENTS;
float ySegment = (float)y / (float)Y_SEGMENTS;
float xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
float yPos = std::cos(ySegment * PI);
float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
positions.push_back(QVector3D(xPos, yPos, zPos));
uv.push_back(QVector2D(xSegment, ySegment));
}
}
在这里用了65*65个球体顶点,cos和sin函数使用弧度进行绘制,记得要先声明PI,我是这样声明的#define PI 3.14159265359f ,也可以声明为常数
在learnopengl教程中,他绘制时使用的是glDrawElements(GL_TRIANGLE_STRIP,indexCount,GL_UNSIGNED_INT,0);
这种方式 GL_TRIANGLE_STRIP,起始是三个点(一个三角形),后面每增加一个点就会根据新增点的前两个点和新增点绘制一个新三角形
所以我们创建索引时也就很方便了,还是拿这幅图来说

一行有65个点,第一行为0-64,第二行为65-129,那么我们绘制索引是这样的 0,65,1,这样是第一个三角形,然后是66,这样就根据上面图片一样,挨个点绘制三角形,而且是第一行从左到右,第二行从右到左,(由于首尾相接)再依次循环的,才能正确画出来
还有一个问题就是y轴只需要遍历64次,因为每遍历一次,会用到两行数据,到64次时,会把65行的顶点都用上,所以不用再遍历第65次,而x轴则需要遍历65次,因为一个四边形有4个点,一次占用3个点,65个顶点是63个三角形,加上首次那两个没有用到的顶点,刚好是65次
我们更新索引后的代码为
std::vector<QVector3D> positions;
std::vector<QVector2D> uv;
std::vector<QVector3D> normals;
std::vector<unsigned int> indices;
const unsigned int X_SEGMENTS = 64;
const unsigned int Y_SEGMENTS = 64;
for (unsigned int x = 0; x <= X_SEGMENTS; ++x)
{
for (unsigned int y = 0; y <= Y_SEGMENTS; ++y)
{
float xSegment = (float)x / (float)X_SEGMENTS;
float ySegment = (float)y / (float)Y_SEGMENTS;
float xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
float yPos = std::cos(ySegment * PI);
float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
positions.push_back(QVector3D(xPos, yPos, zPos));
uv.push_back(QVector2D(xSegment, ySegment));
normals.push_back(QVector3D(xPos, yPos, zPos));
}
}
bool oddRow = false; //奇数
//后面使用triangle_strip,他会把后面与前面的相接,所以y轴只用遍历64次,而x轴要便利65次;
for (unsigned int y = 0; y < Y_SEGMENTS; ++y)
{
if (!oddRow) // even rows: y == 0, y == 2; and so on
{
for (unsigned int x = 0; x <= X_SEGMENTS; ++x)
{
indices.push_back(y * (X_SEGMENTS + 1) + x);
indices.push_back((y + 1) * (X_SEGMENTS + 1) + x);
}
}
else
{
for (int x = X_SEGMENTS; x >= 0; --x)
{
indices.push_back((y + 1) * (X_SEGMENTS + 1) + x);
indices.push_back(y * (X_SEGMENTS + 1) + x);
}
}
oddRow = !oddRow;
}
这样顶点数据就确定完成了,剩下的就是把数据装载到vertexarray中了,那些是opengl的知识,这里就不介绍了