不使用gluSphere()在OpenGL中绘制Sphere?


80

是否有任何教程可以解释如何无需使用OpenGL在OpenGL中绘制球体gluSphere()

OpenGL的许多3D教程都在立方体上。我已经搜索过,但是大多数使用绘制球体的解决方案都可以使用gluSphere()。还有具有代码在绘制球体网站这个网站,但它并不能解释背后绘制球体数学。我还有其他版本的方法,如何在该链接中以多边形而不是四边形绘制球体。但是同样,我不明白如何用代码绘制球体。我希望能够进行可视化,以便在需要时可以修改球体。


3
查找球形坐标以进行数学解释(具体来说是从球形坐标到笛卡尔坐标的转换)。
内德·宾厄姆

Answers:


269

一种实现方法是从具有三角形边的柏拉图式实体开始,例如八面体。然后,取每个三角形并将其递归分解为较小的三角形,如下所示:

递归绘制的三角形

一旦拥有足够数量的点,就可以对其向量进行归一化,以使它们与实体的中心都保持恒定的距离。这会导致边凸出为类似于球形的形状,并随着增加点数而增加平滑度。

这里的归一化是指移​​动一个点,使其相对于另一个点的角度相同,但它们之间的距离不同。这是一个二维示例。

在此处输入图片说明

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

在此处输入图片说明

我们可以说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的圆弧上。

凸起线段

在这里,黑点从一条线开始,然后“凸出”成弧形。

此过程可以扩展为三个维度,在这种情况下,您将得到一个球体而不是一个圆。只需将dz组件添加到normalize函数即可。

归一化多边形

1级膨胀的八面体 3级鼓胀八面体

如果您看一下Epcot上的球体,则可以看到这种技术在起作用。这是一个十二面体,带有凸出的面孔,使其看起来更圆。


1
我宁愿删除到epcot领域的链接。这可能会使初学者感到困惑,因为在那里每个三角形又被细分为三个等腰三角形(类似于sqrt(3)-细分的第一部分)。我相信您会找到一个更好的例子。
克里斯汀·劳

我在家用计算机上有一个不错的实现。下班后,我很乐意在一些屏幕截图中进行编辑。
凯文(Kevin)

谢谢你的主意。但是我不了解如何通过对向量进行归一化来使侧面膨出成类似于球体的形状?我如何将侧面凸出?
嘉宏

1
@xEnOn,我已经编辑了答案以进一步解释标准化。我认为问题在于规范化不是我要解释的过程的实际技术术语,因此您很难在其他任何地方找到关于它的更多信息。对于那个很抱歉。
凯文

1
也许在这里解释“规范化”过程的更好方法是将点投影到球体上。另外,请注意,结果会有所不同,具体取决于归一化/投影是在末尾仅应用一次(在所有细分之后,似乎是此处所建议的)还是与(递归)细分步骤交错应用。似乎仅在末端投影一次即可产生顶点,这些顶点聚集在初始八面体的顶点附近,而交错的细分和投影则在顶点之间产生均匀的距离。
泰勒·斯特纳2014年

25

我将进一步解释一种使用纬度和经度生成球体的流行方法(另一种方法icospheres撰写本文时已在最受欢迎的答案中进行了解释。)

球体可以通过以下参数方程式表示:

Fuv)= [cos(u)* sin(v)* r,cos(v)* r,sin(u)* sin(v)* r]

哪里:

  • r是半径;
  • u是经度,取值范围是0到2π;和
  • v是纬度,范围是0到π。

然后,生成球体涉及以固定间隔评估参数函数。

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

以下伪代码通过以规则的时间间隔评估参数函数来生成三角形网格(这适用于任何参数曲面函数,而不仅仅是球体)。

在下面的伪代码中,UResolution是沿U轴的网格点数(此处为经度线),VResolution是沿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)
 }
}

否决票似乎有点苛刻。它是通过球体的参数方程式提到离散构造的唯一例子之一。根据一个球体可以看作是一堆圈,当它们靠近极点时会收缩,这可能会更容易理解。
Spacen Jasset

2
您好,我只是想指出p0,p1,p2,p3的每个值中的第二个应该是v或vn,而不是u或un。
妮可

8

快速解释了示例中的代码。您应该研究一下功能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并使用简单的三角函数计算了顶点坐标。

现在计算出的顶点利用发送到您的GPUglVertex...()GL_QUAD_STRIP,这意味着你要发送形成与先前2派四每两个顶点。

现在,您只需要了解三角函数的工作方式即可,但是我想您可以轻松地弄清楚。


@PintoDoido:它是从OP的原始链接中消失的;为了清楚起见,我将Archive.org添加了链接并将功能编辑到该答案中。
genpfault '19

2
半径丢失。
tomasantunes

1
不使用第一个参数“ double r”。
ollydbg19年

1
那是正确的。该代码示例不是我原始答案的一部分。@genpfault:您在编辑中添加了代码示例。你能解决这个例子吗?
康斯坦丁

1
谢谢一束:)
康斯坦丁

1

如果您想像狐狸一样狡猾,则可以将GLU中的代码插入一半。查看MesaGL源代码(http://cgit.freedesktop.org/mesa/mesa/)。


4
虽然我在这种情况下理解了“半英寸”的含义,但我认为您可能想为其他95%不太精通鸡肋y语的读者进行编辑!
柔印


1

我的示例如何使用“三角带”绘制“极性”球,它包含成对绘制点:

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)如下参数方程式,第二个点移动了一个单步的α角(从下一个平行线开始)。


1

尽管已接受的答案解决了问题,但最后还是有一些误解。十二面体是(或者可能是)所有面都具有相同面积的规则多面体。Epcot似乎就是这种情况(顺便说一句,它根本不是十二面体)。由于@Kevin提出的解决方案没有提供此特性,因此我认为我可以添加一种这样做的方法。

生成N面多面体的一个好方法是,所有顶点都位于同一球体中,并且其所有面都具有相似的面积/表面,它是从二十面体开始的,并且迭代地细分和归一化其三角形面(如已接受的答案所示) )。例如,十二面体实际上是截短的二十面体

规则的二十面体有20个面(12个顶点),可以很容易地由3个金色矩形构成。以此为起点而不是八面体只是一个问题。您可以在此处找到示例。

我知道这有点题外话,但我相信如果有人来这里寻找这种特殊情况可能会有所帮助。


0

一种方法是制作一个面向相机的四边形,并编写一个顶点和片段着色器,以渲染看起来像球形的东西。您可以使用可以在互联网上找到的圆/球方程。

一件好事是,球体的轮廓从任何角度看都是相同的。但是,如果球体不在透视图的中心,则它看起来可能更像椭圆。您可以为此计算公式,然后将其放在片段阴影中。然后,如果确实有一个播放器在球体周围的3D空间中移动,则随着播放器移动,灯光阴影需要改变。

任何人都可以评论他们是否尝试过此方法,或者它太昂贵而无法实用?


只有在平行投影下才是正确的。如果使用透视投影,在渲染输出的球体的轮廓是一般的圆。
Reto Koradi

0

@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()

0
void draw_sphere()
{

    //              z
    //              |
    //               __
    //             /|          
    //              |           
    //              |           
    //              |    *      \
    //              | _ _| _ _ _ |    _y
    //             / \c  |n     /                    p3 --- p2
    //            /   \o |i                           |     |
    //           /     \s|s      z=sin(v)            p0 --- p1
    //         |/__              y=cos(v) *sin(u)
    //                           x=cos(v) *cos(u) 
    //       /
    //      x
    //


    double pi = 3.141592;
    double di =0.02;
    double dj =0.04;
    double du =di*2*pi;
    double dv =dj*pi;


    for (double i = 0; i < 1.0; i+=di)  //horizonal
    for (double j = 0; j < 1.0; j+=dj)  //vertical
    {       
        double u = i*2*pi;      //0     to  2pi
        double v = (j-0.5)*pi;  //-pi/2 to pi/2

        double  p[][3] = { 
            cos(v)     * cos(u)      ,cos(v)     * sin(u)       ,sin(v),
            cos(v)     * cos(u + du) ,cos(v)     * sin(u + du)  ,sin(v),
            cos(v + dv)* cos(u + du) ,cos(v + dv)* sin(u + du)  ,sin(v + dv),
            cos(v + dv)* cos(u)      ,cos(v + dv)* sin(u)       ,sin(v + dv)};

        //normal
        glNormal3d(cos(v+dv/2)*cos(u+du/2),cos(v+dv/2)*sin(u+du/2),sin(v+dv/2));

        glBegin(GL_POLYGON);
            glTexCoord2d(i,   j);    glVertex3dv(p[0]);
            glTexCoord2d(i+di,j);    glVertex3dv(p[1]);
            glTexCoord2d(i+di,j+dj); glVertex3dv(p[2]);
            glTexCoord2d(i,   j+dj); glVertex3dv(p[3]);
        glEnd();
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.