【问题标题】:Drawing Sphere in OpenGL without using gluSphere()?在OpenGL中绘制球体而不使用gluSphere()?
【发布时间】:2011-12-02 23:34:23
【问题描述】:

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

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

【问题讨论】:

  • 查找球坐标以获得数学解释(特别是从球坐标到笛卡尔坐标的转换)。

标签: c++ opengl geometry


【解决方案1】:
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();
    }
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      快速解释示例中的代码。你应该看看函数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,这意味着您发送的每两个顶点与之前发送的两个顶点形成一个四边形。

      你现在只需要了解三角函数是如何工作的,但我想你可以很容易地弄清楚。

      【讨论】:

      • @PintoDoido:它来自 OP 的原始链接,在某个时候死了;为了清楚起见,我存档了链接并将函数编辑到这个答案中。
      • 半径丢失。
      • 第一个参数“double r”没有使用。
      • 没错。代码示例不是我原始答案的一部分。 @genpfault:您在编辑中添加了代码示例。你能修复这个例子吗?
      • 非常感谢 :)
      【解决方案4】:

      虽然接受的答案解决了问题,但最后还是有一点误解。 十二面体是(或可能是)所有面具有相同面积的正多面体。 Epcot 似乎就是这种情况(顺便说一下,它根本不是 十二面体)。由于@Kevin 提出的解决方案没有提供这个特性,我想我可以添加一种方法。

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

      Regular icosahedrons 有 20 个面(12 个顶点),可以很容易地由 3 个黄金矩形构成;这只是将其作为起点而不是八面体的问题。你可以找到一个例子here

      我知道这有点跑题了,但我相信如果有人来这里寻找这个特定案例可能会有所帮助。

      【讨论】:

        【解决方案5】:

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

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

        F(u, v) = [ 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)
         }
        }
        

        【讨论】:

        • 否决票似乎有点苛刻。这是通过球体的参数方程提到离散构造的唯一答案之一。这也可能更容易理解,因为球体可以被视为一堆圆圈,当它们靠近两极时会缩小。
        • 您好,我只是想指出,p0、p1、p2、p3 的每个值的第二个应该是 v 或 vn,而不是 u 或 un。
        【解决方案6】:

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

        一件好事是球体的轮廓从任何角度看起来都一样。然而,如果球体不在透视图的中心,那么它可能看起来更像一个椭圆。您可以为此计算出方程式并将它们放入片段着色中。如果确实有玩家在球体周围的 3D 空间中移动,那么光照阴影需要随着玩家的移动而改变。

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

        【讨论】:

        • 只有在平行投影下才会这样。如果使用透视投影,则渲染输出中球体的轮廓不是通常是圆形。
        【解决方案7】:

        我的示例如何使用“三角形带”来绘制“极地”球体,它包括成对绘制点:

        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 角度(从下一个平行点开始)。

        【讨论】:

          【解决方案8】:

          您可以这样做的一种方法是从具有三角形边的柏拉图立体开始 - 例如octahedron。然后,取出每个三角形并递归地将其分解为更小的三角形,如下所示:

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

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

          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 的圆弧上。

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

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

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

          【讨论】:

          • 我宁愿删除到 epcot 球体的链接。它可能会使初学者感到困惑,因为每个三角形又被细分为三个等腰三角形(类似于 sqrt(3)-细分的第一部分)。我相信你会找到一个更好的例子。
          • 我在我的家用机器上有一个很好的实现。我很乐意在下班后编辑一些屏幕截图。
          • 感谢您的想法。但是我不明白如何通过规范化向量,我可以将侧面凸出成类似于球体的形状?我如何使两侧凸出?
          • @xEnOn,我已经编辑了我的答案以进一步解释规范化。我认为问题在于规范化并不是我试图解释的过程的实际技术术语,因此您很难在其他任何地方找到有关它的更多信息。对此感到抱歉。
          • 也许在这里解释“标准化”过程的更好方法是将点投影到球体上。另外,请注意,结果会有所不同,具体取决于归一化/投影是在最后应用一次(在所有细分之后,这似乎是这里建议的)还是与(递归)细分步骤交错。看起来,最后只投影一次会产生聚集在初始八面体顶点附近的顶点,而交错的细分和投影会产生顶点之间的均匀距离。
          【解决方案9】:

          查看OpenGL红皮书:http://www.glprogramming.com/red/chapter02.html#name8 它通过多边形细分解决了这个问题。

          【讨论】:

            【解决方案10】:

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

            【讨论】:

            • 虽然我理解“半英寸”在此上下文中的含义,但我想您可能想为其他 95% 不流利使用cockney rhyming slang 的读者编辑它!跨度>
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多