有没有教程可以解释如何在OpenGL中绘制球体而不使用gluSphere()
?
许多关于OpenGL的3D教程只涉及立方体。我已经搜索了很多,但是大多数绘制球体的解决方案都是使用gluSphere()
。也有网站提供了绘制球体的代码,链接为这个,但是它没有解释绘制球体的数学原理。该链接还提供了其他绘制球体的版本,使用的是多边形而不是四边形。但是同样地,我不理解代码如何绘制球体。我希望能够可视化球体,以便以后需要时进行修改。
有没有教程可以解释如何在OpenGL中绘制球体而不使用gluSphere()
?
许多关于OpenGL的3D教程只涉及立方体。我已经搜索了很多,但是大多数绘制球体的解决方案都是使用gluSphere()
。也有网站提供了绘制球体的代码,链接为这个,但是它没有解释绘制球体的数学原理。该链接还提供了其他绘制球体的版本,使用的是多边形而不是四边形。但是同样地,我不理解代码如何绘制球体。我希望能够可视化球体,以便以后需要时进行修改。
你可以通过使用一个具有三角形面的正多面体来完成这个任务,例如一个八面体。然后,将每个三角形递归地分割成更小的三角形,如下所示:
当您拥有足够数量的点时,您需要对它们的向量进行归一化,使它们都与固体中心保持恒定距离。这会导致侧面凸出成类似于球体的形状,并且随着点数增加,平滑度也会增加。
在这里,归一化意味着移动一个点,使其相对于另一个点的角度相同,但它们之间的距离不同。以下是一个二维示例。
A和B之间相距6个单位。但是假设我们想要在AB线上找到一个离A点12个单位远的点。
我们可以说C是相对于A归一化后距离为12的B的标准形式。我们可以使用以下代码获取C:#returns a point collinear to A and B, a given distance away from A.
function normalize(a, b, length):
#get the distance between a and b along the x and y axes
dx = b.x - a.x
dy = b.y - a.y
#right now, sqrt(dx^2 + dy^2) = distance(a,b).
#we want to modify them so that sqrt(dx^2 + dy^2) = the given length.
dx = dx * length / distance(a,b)
dy = dy * length / distance(a,b)
point c = new point
c.x = a.x + dx
c.y = a.y + dy
return c
这里,黑色点从一条线开始并“凸出”成弧线。
这个过程可以扩展到三维空间,这时你会得到一个球体而不是圆。只需在normalize函数中添加一个dz分量即可。
如果你看Epcot的球体,你可以看到这种技术在起作用。它是一个十二面体,其面部凸出使其看起来更加圆润。
var startU=0
var startV=0
var endU=PI*2
var endV=PI
var stepU=(endU-startU)/UResolution // step size between U-points on the grid
var stepV=(endV-startV)/VResolution // step size between V-points on the grid
for(var i=0;i<UResolution;i++){ // U-points
for(var j=0;j<VResolution;j++){ // V-points
var u=i*stepU+startU
var v=j*stepV+startV
var un=(i+1==UResolution) ? endU : (i+1)*stepU+startU
var vn=(j+1==VResolution) ? endV : (j+1)*stepV+startV
// Find the four points of the grid
// square by evaluating the parametric
// surface function
var p0=F(u, v)
var p1=F(u, vn)
var p2=F(un, v)
var p3=F(un, vn)
// NOTE: For spheres, the normal is just the normalized
// version of each vertex point; this generally won't be the case for
// other parametric surfaces.
// Output the first triangle of this grid square
triangle(p0, p2, p1)
// Output the other triangle of this grid square
triangle(p3, p1, p2)
}
}
这个示例中的代码很容易理解。你应该查看函数void drawSphere(double r, int lats, int longs)
:
void drawSphere(double r, int lats, int longs) {
int i, j;
for(i = 0; i <= lats; i++) {
double lat0 = M_PI * (-0.5 + (double) (i - 1) / lats);
double z0 = sin(lat0);
double zr0 = cos(lat0);
double lat1 = M_PI * (-0.5 + (double) i / lats);
double z1 = sin(lat1);
double zr1 = cos(lat1);
glBegin(GL_QUAD_STRIP);
for(j = 0; j <= longs; j++) {
double lng = 2 * M_PI * (double) (j - 1) / longs;
double x = cos(lng);
double y = sin(lng);
glNormal3f(x * zr0, y * zr0, z0);
glVertex3f(r * x * zr0, r * y * zr0, r * z0);
glNormal3f(x * zr1, y * zr1, z1);
glVertex3f(r * x * zr1, r * y * zr1, r * z1);
}
glEnd();
}
}
参数lat
定义了球体中要有多少个水平线,lon
定义了要有多少个垂直线。而r
则是你的球体的半径。
现在进行了一个双重迭代来计算顶点坐标,使用简单的三角函数来计算。
计算得到的顶点现在使用glVertex...()
作为GL_QUAD_STRIP
发送到你的GPU,这意味着你正在发送每两个顶点,这些顶点与先前发送的两个顶点形成一个四边形。
现在你只需要理解三角函数的工作原理,但我想你可以很容易地弄清楚。
这是一个关于如何使用“三角带”绘制“极坐标”球体的示例,它需要成对地绘制点:
const float PI = 3.141592f;
GLfloat x, y, z, alpha, beta; // Storage for coordinates and angles
GLfloat radius = 60.0f;
int gradation = 20;
for (alpha = 0.0; alpha < GL_PI; alpha += PI/gradation)
{
glBegin(GL_TRIANGLE_STRIP);
for (beta = 0.0; beta < 2.01*GL_PI; beta += PI/gradation)
{
x = radius*cos(beta)*sin(alpha);
y = radius*sin(beta)*sin(alpha);
z = radius*cos(alpha);
glVertex3f(x, y, z);
x = radius*cos(beta)*sin(alpha + PI/gradation);
y = radius*sin(beta)*sin(alpha + PI/gradation);
z = radius*cos(alpha + PI/gradation);
glVertex3f(x, y, z);
}
glEnd();
}
第一个点(glVertex3f)遵循参数方程,第二个点则向下一个平行线移动了一个alpha角度的步长。
void draw_sphere(float r)
{
float pi = 3.141592;
float di = 0.02;
float dj = 0.04;
float db = di * 2 * pi;
float da = dj * pi;
for (float i = 0; i < 1.0; i += di) //horizonal
for (float j = 0; j < 1.0; j += dj) //vertical
{
float b = i * 2 * pi; //0 to 2pi
float a = (j - 0.5) * pi; //-pi/2 to pi/2
//normal
glNormal3f(
cos(a + da / 2) * cos(b + db / 2),
cos(a + da / 2) * sin(b + db / 2),
sin(a + da / 2));
glBegin(GL_QUADS);
//P1
glTexCoord2f(i, j);
glVertex3f(
r * cos(a) * cos(b),
r * cos(a) * sin(b),
r * sin(a));
//P2
glTexCoord2f(i + di, j);//P2
glVertex3f(
r * cos(a) * cos(b + db),
r * cos(a) * sin(b + db),
r * sin(a));
//P3
glTexCoord2f(i + di, j + dj);
glVertex3f(
r * cos(a + da) * cos(b + db),
r * cos(a + da) * sin(b + db),
r * sin(a + da));
//P4
glTexCoord2f(i, j + dj);
glVertex3f(
r * cos(a + da) * cos(b),
r * cos(a + da) * sin(b),
r * sin(a + da));
glEnd();
}
}
如果你想像狡猾的狐狸一样,可以从GLU中复制代码。请查看MesaGL源代码(http://cgit.freedesktop.org/mesa/mesa/)。
@Constantinius 的回答的 Python 版本:
lats = 10
longs = 10
r = 10
for i in range(lats):
lat0 = pi * (-0.5 + i / lats)
z0 = sin(lat0)
zr0 = cos(lat0)
lat1 = pi * (-0.5 + (i+1) / lats)
z1 = sin(lat1)
zr1 = cos(lat1)
glBegin(GL_QUAD_STRIP)
for j in range(longs+1):
lng = 2 * pi * (j+1) / longs
x = cos(lng)
y = sin(lng)
glNormal(x * zr0, y * zr0, z0)
glVertex(r * x * zr0, r * y * zr0, r * z0)
glNormal(x * zr1, y * zr1, z1)
glVertex(r * x * zr1, r * y * zr1, r * z1)
glEnd()
正二十面体有20个面(12个顶点),可以通过3个黄金矩形轻松构建;只需要以这个作为起点而不是一个八面体。你可以在这里找到一个例子。
我知道这有点偏题,但我相信如果有人寻找这个特定情况的话,这可能会有所帮助。