在 OpenGL 中绘制球体而不使用 gluSphere() ? ?

有没有任何教程可以解释我如何在 OpenGL 中画一个球体而不必使用 gluSphere()

许多 OpenGL 的3D 教程只是在立方体上。我已经搜索,但大多数的解决方案,绘制一个球是使用 gluSphere()。还有一个网站,有代码绘制一个球体在 这个网站,但它没有解释背后的数学绘制球体。我还有其他版本的如何绘制多边形球体,而不是四边形在该链接。但是,我还是不明白这些球体是怎么用代码画出来的。我希望能够可视化,这样我就可以修改球体,如果我需要的话。

107767 次浏览

一种方法是从一个三角形边的柏拉图立体开始,比如 八面体。然后,取每个三角形并递归地把它分成更小的三角形,像这样:

recursively drawn triangles

一旦有了足够的点数,就可以对它们的向量进行标准化,使它们与立体中心的距离保持恒定。这会导致两边膨胀成类似球体的形状,随着点数的增加,光滑度也会随之增加。

这里的归一化意味着移动一个点,使它与另一个点的角度相同,但它们之间的距离是不同的。 这是一个二维的例子。

enter image description here

A 和 B 相隔6个单位。但是假设我们想在 AB 线上找到一个点距离 A 有12个单位。

enter image description here

我们可以说 C 是 B 相对于 A 的规范化形式,距离为12。我们可以通过这样的代码获得 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

如果我们对很多点进行归一化处理,这些点都是关于相同的点 A 和距离 R 的,那么归一化点将全部位于圆心 A 和半径 R 的圆弧上。

bulging line segment

在这里,黑点开始在一条线和“凸出”成一个弧线。

这个过程可以扩展到三维空间,在这种情况下,你得到的是一个球体而不是一个圆。只需在规范化函数中添加一个 dz 组件。

normalized polygons

level 1 bulging octahedron level 3 bulging octahedron

如果你看看 未来世界的球体,你可以看到这种技术在起作用。这是一个十二面体,为了让它看起来更圆一些,面部凸出。

示例中的代码很快就得到了解释。您应该查看函数 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是球体的半径。

现在在 lat/lon上有一个双重迭代,并且用简单的三角法计算了顶点坐标。

计算出的顶点现在使用 glVertex...()作为 GL_QUAD_STRIP发送到 GPU,这意味着每两个顶点构成一个四边形,前面两个顶点被发送。

你们现在需要了解的是三角函数是如何工作的,但我想你们很容易就能弄明白。

如果你想像狐狸一样狡猾,你可以从 GLU 的半英寸代码。查看 MesagL 源代码( http://cgit.freedesktop.org/mesa/mesa/)。

查看 OpenGL 红皮书: http://www.glProgramming.com/red/chater02.html # name8”rel = “ nofollow”> http://www.glprogramming.com/red/chapter02.html#name8 它通过对多边形的细分来解决这个问题。

我将进一步解释一种流行的用经纬度生成球体的方法(另一种 在撰写本文时,等温球已经在最流行的答案中得到了解释。)

一个球体可以用以下参数方程表示:

F (翻译) = [ cos (u) * sin (v) * r,cos (v) * r,sin (u) * sin (v) * r ]

地点:

  • R 是半径;
  • U 是经度,范围从0到2π;
  • V 是纬度,范围从0到 π。

然后生成球体需要在固定的区间内计算参数函数。

例如,要生成16条经线,沿 轴将有17条网格线,步长为 Π/8(2π/16)(第17行绕过)。

下面的伪代码通过计算参数函数生成一个三角形网格 在规则的间隔(这适用于 任何参数曲面函数,而不仅仅是球面)。

在下面的伪代码中,解决方案是沿 U 轴的网格点数 (这里是经线) ,解决方案是沿 V 轴的网格点数 (这里,纬度线)

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)
}
}

我的例子如何使用’三角形条’绘制一个“极性”球体,它包括在绘制点成对:

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)如参数方程所示,第二个点移动了一个阿尔法角(从下一个平行点开始)。

一种方法是制作一个面向摄像机的四边形,并编写一个顶点和片段着色器,呈现一些看起来像球体的东西。你可以用方程来表示一个圆/球,你可以在网上找到。

一个很好的事情是球体的轮廓从任何角度看都是一样的。然而,如果球体不在透视图的中心,那么它看起来可能更像一个椭圆。你可以计算出这个方程,然后把它们放在片段阴影中。然后光阴需要改变,因为球员移动,如果你确实有一个球员在三维空间移动的球体。

有没有人能评论一下他们是否尝试过这种方法,或者这种方法是否太过昂贵而不实用?

虽然公认的答案解决了问题,但最后还是有一点误解。十二面体是(或可能是)正多面体,其中所有面都有相同的面积。这似乎是未来世界的情况(顺便说一下,它根本不是一个 十二面体)。由于@Kevin 提出的解决方案没有提供这个特征,我想我可以添加一个方法。

生成 N 面多面体的一个好方法是,所有顶点都位于同一个球面内,所有面都有相似的面积/表面,从二十面体开始,迭代地细分和归一化它的三角面(正如公认的答案所建议的那样)。例如,十二面体实际上是 < em > 截断的 二十面体

正二十面体 有20个面(12个顶点) ,可以很容易地由3个黄金矩形构成; 这只是一个起点,而不是一个八面体的问题。您可以找到一个例子 给你

我知道这有点跑题但我相信如果有人来这里找这个特殊的案子会有所帮助。

enter image description here

   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();
}
}

改编自@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()