思路


我们都知道,如果我们画一个正N边形,当N越来越大时,画出来的形状就越接近圆形。这一理论基础为我们画圆提供了思路。

由前面我们也当知悉,OpenGL 是断断没有 DrawCircle 或者 DrawEllipse 这样方便直接的接口的。我们这里讲述的,更像是这种接口的实现,借由本节,我们将可以自己封装这样的接口。

几何上,给定一个中心,一个半径,就可以画圆。圆是360度,因此我们不妨取360个顶点,连接这些顶点,我们期待可以画出一个「圆」。

因此,问题现在已经简化成,如何构造圆的顶点?

顶点的构造


假设圆点坐标为 (a, b),半径为 r,那么圆上的任意一点的坐标计算公式为:

x = a + sin(α) * r
y = b + cos(α) * r

其中,α 为弧度。

以下是源代码:

void Draw ( ESContext *esContext )
{
    UserData *userData = esContext->userData;
    GLfloat vCenter[] = {0.0f, 0.0f};
    GLfloat vRadius = .5f;
    GLfloat vVertices[720];

    for (int i=0; i < 720; i+=2) {
        GLfloat radians = M_PI * i / 180.0;
        vVertices[i] = vCenter[0] + sin(radians) * vRadius;
        vVertices[i+1] = vCenter[1] + cos(radians) * vRadius;
    }

    // Set the viewport
    glViewport ( 0, 0, esContext->width, esContext->height );

    // Clear the color buffer
    glClear ( GL_COLOR_BUFFER_BIT );

    // Use the program object
    glUseProgram ( userData->programObject );

    // Load the vertex data
    glVertexAttribPointer ( 0, 2, GL_FLOAT, GL_FALSE, 0, vVertices );
    glEnableVertexAttribArray ( 0 );

    glDrawArrays ( GL_TRIANGLE_FAN, 0, 360 );
}

注意到,由于画的是2D图形,我们移除了 Z 坐标,每个顶点由 X、Y 坐标组成。for 循环中还有一个将角度转换成弧度的计算。

vCenter 是圆点坐标;vRadius 是半径。

运行结果:

很遗憾这不是我们想要的结果,问题出在哪里呢?我们的计算并没有问题。

问题的修复


仔细观察上面绘图的结果,不难发现,图形的 X、Y 方向上的半径与显示窗口的 宽、长 成比例,这是造成显示结果不如预期的原因。我们的 vRadius 变量指定的 0.5,表示的竟然是 X、Y 各自方向上的比例。

方案一

既然是显示窗口长宽比例的缘故,我们可以将 viewPort 设置成正方形:

// Set the viewport
glViewport ( 0, 0, esContext->width, esContext->width );

运行结果:

画出来的图形是圆形了,但是位置却被改变了,因为我们修改了 viewport 导致的。这个方案似乎不怎么样。

方案二

我们可以在 X、Y 上各自提供半径,使其绝对值相等。修改成:

void Draw ( ESContext *esContext )
{
    UserData *userData = esContext->userData;
    GLfloat vCenter[] = {0.0f, 0.0f};
    GLfloat vRadius[] = {0.5f, 0.5 * esContext->width / esContext->height};
    GLfloat vVertices[720];

    for (int i=0; i < 720; i+=2) {
        GLfloat radians = M_PI * i / 180.0;
        vVertices[i] = vCenter[0] + sin(radians) * vRadius[0];
        vVertices[i+1] = vCenter[1] + cos(radians) * vRadius[1];
    }

    // Set the viewport
    glViewport ( 0, 0, esContext->width, esContext->height );

    // Clear the color buffer
    glClear ( GL_COLOR_BUFFER_BIT );

    // Use the program object
    glUseProgram ( userData->programObject );

    // Load the vertex data
    glVertexAttribPointer ( 0, 2, GL_FLOAT, GL_FALSE, 0, vVertices );
    glEnableVertexAttribArray ( 0 );

    glDrawArrays ( GL_TRIANGLE_FAN, 0, 360 );
}

运行结果:

看起来不错!

小结


如何画椭圆,相信已从上面的错误示例中得到灵感。不再赘述。

这里有个问题是:

  • 有没有更好的解决方案三?

到现在,画三角形、多边形、圆形都已经不成问题,即便换成点、线图元,也是可以绘制的。总结其主要思路是,把一个复杂的图形分割成多个图元(点、线、三角形),这样问题便转化成组织这些图元的顶点。这就是整个的解决问题的思路。

更多


到现在为止,我们画的都是2D图形,而 OpenGL ES 实际上是个 3D 图形库。如何绘制一些基本的 3D 图形,比如长方体、圆球呢?