65.9K
CodeProject 正在变化。 阅读更多。
Home

OpenGL 几何图元

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2021年3月10日

CPOL

2分钟阅读

viewsIcon

9363

使用纹理和光照效果在 OpenGL 中构建盒子、圆柱体、圆锥体和球体

引言

我想编写一个简单的 X3DOM 查看器,基于 OpenGL,并在网络上搜索了有关构建盒子、圆柱体、圆锥体和球体的资料。我发现了很多好的论坛帖子,但没有一个是“现成的”。在本技巧中,我想与所有也在寻找此主题入门的人分享我的发现。因此,请不要期望在下面看到“火箭科学”,而只是“现成的”代码。

我还在编写简单的 X3DOM 查看器时,写了另一篇关于“光源特征和相关材质属性”的技巧。

背景

X3DOM 规范支持 BoxCylinderConeSphere 对象,所以我需要一种构建这些图形图元的算法。

我对 OpenGL 还不熟悉,所以对我来说,这些算法易于理解和遵循非常重要。(我的一个参考资料是 OpenGL 编程指南。)

结果是支持所有类型 OpenGL 光照(环境光、漫反射光和镜面反射光)以及纹理的几何图元。不幸的是,在 OpenGL 中同时使用所有类型的光照和纹理需要一些技巧和扩展 (EXT_secondary_color),但这将是下一个技巧的内容。以下图片展示了使用现有手段已经可以实现的效果

具有全光照的几何图元

具有简单纹理的几何图元

带有光谱颜色的三角形标记了每种情况下光源的位置。

对于圆锥体和球体,您可以清楚地看到通过平面表面分段对弯曲表面的近似。近似的质量可以通过用于平面表面分段的角度的步长来调整 - 对于图片,我使用每 360° 圆周 60 步。

Using the Code

Box

让我们从盒子开始,直接的代码 - 没有花哨的技巧

/// <summary>
/// Draws a box into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the half edge length in x direction.</param>
/// <param name="fWidthHalf">Specifies the half edge length in y direction.</param>
/// <param name="fDepthHalf">Specifies the half edge length in z direction.</param>
/// <param name="bTexture">Determine whether texture coordinates shall be calculated.</param>
/// <remarks>
///     +-----------+                y (height)
///    /   top     /|               /|\
///   /           / |                |
///  +-----------+  |                |      \
///  |           | right             +-------> x (width)
///  |   front   |  +               /       /
///  |           | /               /
///  |           |/              |/_
///  +-----------+               z (depth)
/// </remarks>
void OpenGL::DrawBox(float fWidthHalf, float fHeightHalf, float fDepthHalf, bool bTexture)
{
    // -- FRONT
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, 0.0f, +1.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    ::glEnd();

    // -- BACK
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, 0.0f, -1.0f);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    ::glEnd();

    // -- LEFT
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(-1.0f, 0.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    ::glEnd();

    // -- RIGHT
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(+1.0f, 0.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, +fDepthHalf);
    ::glEnd();

    // -- TOP
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, +1.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, +fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, +fHeightHalf, +fDepthHalf);
    ::glEnd();

    // BOTTOM
    ::glBegin(GL_TRIANGLES);
    ::glNormal3f(0.0f, -1.0f, 0.0f);
    if (bTexture) ::glTexCoord2f(0.0f, 0.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
 
    if (bTexture) ::glTexCoord2f(0.0f, 1.0f);
    ::glVertex3f(-fWidthHalf, -fHeightHalf, +fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 0.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, -fDepthHalf);
    if (bTexture) ::glTexCoord2f(1.0f, 1.0f);
    ::glVertex3f(+fWidthHalf, -fHeightHalf, +fDepthHalf);
    ::glEnd();
}

圆柱体

/// <summary>
/// Draws a cylinder into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the radius in x direction.</param>
/// <param name="fWidthHalf">Specifies the half height in y direction.</param>
/// <param name="fDepthHalf">Specifies the radius in z direction.</param>
/// <param name="dAngleStep">Specifies the angle in radiants, plain surface sections
///                          approximate a curved surface.</param>
/// <param name="pColors">Exactly three colors for top, cover and bottom or <c>NULL</c>
///                       if no individual colors are to apply.</param>
/// <param name="bTexture">Determine  whether texture coordinates shall be calculated.</param>
/// <remarks>
///    _------_                 y (height)
///   /        \   top         /|\
///  |\_      _/|               |
///  |  ------  |               |      \
///  |          |  cover        +-------> x (width)
///  |          |              /       /
///  |          |             /
///   \_      _/   bottom   |/_
///     ------              z (depth)
/// </remarks>
void OpenGL::DrawCylinder(float fWidthHalf, float fHeightHalf, float fDepthHalf,
                          double dAngleStep, const COLORREF* pColors = NULL, bool bTexture)
{
    float fReciprocalPrecisition = (float)(dAngleStep / M_2PI);
 
    // Cylinder top
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[0]) / 255.0F));
    int iSectorCount = 0;
    ::glBegin(GL_POLYGON); // ? TRIANGLE_FAN ?
    for (double dTopAngle = M_2PI; dTopAngle > 0.0; dTopAngle -= dAngleStep)
    {
        // face normal
        ::glNormal3f(0.0F, +1.0F, 0.0F);
 
        double c = cos(dTopAngle);
        double s = sin(dTopAngle);
 
        // relative texture coordinates
        float fTextureOffsetS = 0.5F + (float)(0.5F * c);
        float fTextureOffsetT = 0.5F + (float)(0.5F * s);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS, fTextureOffsetT);
        ::glVertex3f((float)(fWidthHalf * cos(dTopAngle)),
                     fHeightHalf,
                     (float)(fDepthHalf * sin(dTopAngle)));
    }
    ::glEnd();
 
    // Cylinder cover
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[1]) / 255.0F));
    iSectorCount = 0;
    ::glBegin(GL_TRIANGLES);
    for (double dCoverAngle1 = 0; dCoverAngle1 < M_2PI; dCoverAngle1 += dAngleStep)
    {
        double dCoverAngle2 = dCoverAngle1 + dAngleStep;
        float  fWidthPart1 = (float)(fWidthHalf * cos(dCoverAngle1));
        float  fWidthPart2 = (float)(fWidthHalf * cos(dCoverAngle2));
        float  fDepthPart1 = (float)(fDepthHalf * sin(dCoverAngle1));
        float  fDepthPart2 = (float)(fDepthHalf * sin(dCoverAngle2));
 
        // face normal
        ::glNormal3f((float)(1.0F * cos(dCoverAngle1 + dAngleStep / 2)),
                     0.0F,
                     (float)(1.0F * sin(dCoverAngle1 + dAngleStep / 2)));
 
        // relative texture coordinates
        float fTextureOffsetS1 = (float)(iSectorCount    ) * fReciprocalPrecisition;
        float fTextureOffsetT1 = 0.0F;
        float fTextureOffsetS2 = (float)(iSectorCount + 1) * fReciprocalPrecisition;
        float fTextureOffsetT2 = 1.0F;
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
        ::glVertex3f(fWidthPart1, -fHeightHalf, fDepthPart1);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT2);
        ::glVertex3f(fWidthPart1, fHeightHalf, fDepthPart1);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
        ::glVertex3f(fWidthPart2, fHeightHalf, fDepthPart2);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
        ::glVertex3f(fWidthPart2, fHeightHalf, fDepthPart2);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT1);
        ::glVertex3f(fWidthPart2, -fHeightHalf, fDepthPart2);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
        ::glVertex3f(fWidthPart1, -fHeightHalf, fDepthPart1);
        iSectorCount++;
    }
    ::glEnd();
 
    // Cylinder bottom
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[2]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[2]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[2]) / 255.0F));
    iSectorCount = 0;
    ::glBegin(GL_POLYGON); // ? TRIANGLE_FAN ?
    for (double dBottotmAngle = 0; dBottotmAngle < M_2PI; dBottotmAngle += dAngleStep)
    {
        // face normal
        ::glNormal3f(0.0F, -1.0F, 0.0F);
 
        double c = cos(dBottotmAngle);
        double s = sin(dBottotmAngle);
 
        // relative texture coordinates
        float fTextureOffsetS = 0.5F + (float)(0.5F * c);
        float fTextureOffsetT = 0.5F + (float)(0.5F * s);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS, fTextureOffsetT);
        ::glVertex3f((float)(fWidthHalf * cos(dBottotmAngle)),
                      -fHeightHalf,
                      (float)(fDepthHalf * sin(dBottotmAngle)));
    }
    ::glEnd();
}

圆锥体

/// <summary>
/// Draws a cone into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the radius in x direction.</param>
/// <param name="fWidthHalf">Specifies the half height in y direction.</param>
/// <param name="fDepthHalf">Specifies the radius in z direction.</param>
/// <param name="dAngleStep">Specifies the angle in radiants, plain surface sections
                             approximate a curved surface.</param>
/// <param name="pColors">Exactly two colors for cover and bottom or <c>NULL</c>
///                       if no individual colors are to apply.</param>
/// <param name="bTexture">Determine  whether texture coordinates shall be calculated.
/// </param>
/// <remarks>
///       /\                    y (height)
///      |  |      top         /|\
///     /    \                  |
///    |      |                 |      \
///   /_------_\   cover        +-------> x (width)
///  |/        \|              /       /
///  |          |             /
///   \_      _/   bottom   |/_
///     ------              z (depth)
/// </remarks>
void DrawCone(float fWidthHalf, float fHeightHalf, float fDepthHalf,
              double dAngleStep, const COLORREF* pColors = NULL, bool bTexture = false);
{
    float fReciprocalPrecisition = (float)(dAngleStep / M_2PI);
 
    // Cone "Cover"
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[0]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[0]) / 255.0F));
    int iSectorCount = 0;
    ::glBegin(GL_TRIANGLES);
    for (double dCoverAngle1 = 0.0F; dCoverAngle1 < M_2PI; dCoverAngle1 += dAngleStep)
    {
        double dCoverAngle2 = dCoverAngle1 + dAngleStep;
        float  fWidthPart1 = (float)(fWidthHalf * cos(dCoverAngle1));
        float  fWidthPart2 = (float)(fWidthHalf * cos(dCoverAngle2));
        float  fDepthPart1 = (float)(fDepthHalf * sin(dCoverAngle1));
        float  fDepthPart2 = (float)(fDepthHalf * sin(dCoverAngle2));
 
        // face normal
        ::glNormal3f((fWidthPart1 + fWidthPart2) / 2, 0.0F, (fDepthPart1 + fDepthPart2) / 2);
 
        // relative texture coordinates
        float fTextureOffsetS1 = iSectorCount       * fReciprocalPrecisition;
        float fTextureOffsetS2 = (iSectorCount + 1) * fReciprocalPrecisition;
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS1, 1.0);
        ::glVertex3f(0.0F, fHeightHalf, 0.0F);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, 0.0);
        ::glVertex3f(fWidthPart2, -fHeightHalf, fDepthPart2);
        if (bTexture) ::glTexCoord2f(fTextureOffsetS2, 0.0);
        ::glVertex3f(fWidthPart1, -fHeightHalf, fDepthPart1);
 
        iSectorCount++;
    }
    ::glEnd();
 
    // Cone Bottom
    if (pColors != NULL)
        ::glColor3f(MIN(1.0F, GetRValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetGValue(pColors[1]) / 255.0F),
                    MIN(1.0F, GetBValue(pColors[1]) / 255.0F));
    iSectorCount = 0;
    ::glBegin(GL_POLYGON); // ? TRIANGLE_FAN ?
    for (double dBottomAngle = 0; dBottomAngle < M_2PI; dBottomAngle += dAngleStep)
    {
        // face normal
        ::glNormal3f(0.0F, -1.0F, 0.0F);
 
        double c = cos(dBottomAngle);
        double s = sin(dBottomAngle);
 
        // relative texture coordinates
        float fTextureOffsetS = 0.5F + (float)(0.5F * c);
        float fTextureOffsetT = 0.5F + (float)(0.5F * s);
 
        if (bTexture) ::glTexCoord2f(fTextureOffsetS, fTextureOffsetT);
       ::glVertex3f((float)(fWidthHalf * c), -fHeightHalf, (float)(fDepthHalf * s));
    }
    ::glEnd();
}

球体

/// <summary>
/// Draws a sphere into the scene.
/// The default orientation (on identity GL_MODELVIEW matrix) is upright.
/// The default center position (on identity GL_MODELVIEW matrix) is [0.0, 0.0, 0.0].
/// </summary>
/// <param name="fWidthHalf">Specifies the radius in x direction.</param>
/// <param name="fWidthHalf">Specifies the radius in y direction.</param>
/// <param name="fDepthHalf">Specifies the radius in z direction.</param>
/// <param name="dAngleStep">Specifies the angle in radiants, plain surface sections
                             approximate a curved surface.</param>
/// <param name="bTexture">Determine  whether texture coordinates shall be calculated.</param>
/// <remarks>
///                stack  -----sector----->    y (height)
///     _-------_    |        _-------_       /|\
///    /         \   |       /  / | \  \       |
///   /___     ___\  |      /  /  |  \  \      |      \
///  |    -----    | |     |  |   |   |  |     +-------> x (width)
///  |___       ___| |     |  |   |   |  |    /       /
///   \  -------  /  |      \  \  |  /  /    /
///    \_       _/   |       \_ \ | / _/   |/_
///      -------    \|/        -------     z (depth)
/// </remarks>
void DrawSphere(float fWidthHalf, float fHeightHalf, float fDepthHalf,
                double dAngleStep, bool bTexture = false)
{
    float f1_10thPi = (float)(M_PI * 0.1f);
    float f2_10thPi = (float)(M_PI * 0.2f);
    float f3_10thPi = (float)(M_PI * 0.3f);
    float f7_10thPi = (float)(M_PI * 0.7f);
    float f8_10thPi = (float)(M_PI * 0.8f);
    float f9_10thPi = (float)(M_PI * 0.9f);
 
    float fPiQuarter = (float)(M_PI * 0.25f);
    float fPiHalf = (float)(M_PI * 0.50f);
 
    float fReciprocalPrecisition = dAngleStep / M_2PI;
    int   iStackCount = 0;
    ::glBegin(GL_TRIANGLES);
    // Plane: X/Y; Rotation axis: Z; 0° in positive X direction
    for (double dStackAngle1 = 0.0f; dStackAngle1 < M_PI; dStackAngle1 += dAngleStep)
    {
        double dStackAngle2 = dStackAngle1 + dAngleStep;
        double s1 = sin(dStackAngle1);
        double s2 = sin(dStackAngle2);
        float fStackHeightHalf1 = (float)(-fHeightHalf * cos(dStackAngle1));
        float fStackHeightHalf2 = (float)(-fHeightHalf * cos(dStackAngle2));
        float fStackWidthHalfTop = (float)(fWidthHalf  * s1);
        float fStackWidthHalfBtm = (float)(fWidthHalf  * s2);
        float fStackDepthHalfTop = (float)(fDepthHalf  * s1);
        float fStackDepthHalfBtm = (float)(fDepthHalf  * s2);
        int   iSectorCount = 0;
 
        // Plane: X/Z; Rotation axis: Y; 0° in positive X direction
        for (double dSectorAngle1 = 0.0f; dSectorAngle1 < M_2PI; dSectorAngle1 += dAngleStep)
        {
            double dSectorAngle2 = dSectorAngle1 + dAngleStep;
 
            // face corners
            float fLeftTop[3] = { (float)(fStackWidthHalfTop * cos(dSectorAngle1)),
                                  fStackHeightHalf1,
                                  (float)(fStackDepthHalfTop * sin(dSectorAngle1)) };
            float fRghtTop[3] = { (float)(fStackWidthHalfTop * cos(dSectorAngle2)),
                                  fStackHeightHalf1,
                                  (float)(fStackDepthHalfTop * sin(dSectorAngle2)) };
            float fLeftBtm[3] = { (float)(fStackWidthHalfBtm * cos(dSectorAngle1)),
                                  fStackHeightHalf2,
                                  (float)(fStackDepthHalfBtm * sin(dSectorAngle1)) };
            float fRghtBtm[3] = { (float)(fStackWidthHalfBtm * cos(dSectorAngle2)),
                                  fStackHeightHalf2,
                                  (float)(fStackDepthHalfBtm * sin(dSectorAngle2)) };
 
            // face normal
            float n[3] = { (fLeftTop[0] + fRghtBtm[0]) / 2,
                           (fStackHeightHalf1 + fStackHeightHalf2) / 2,
                           (fLeftTop[2] + fRghtBtm[2]) / 2 };
 
            // relative texture coordinates
            float fTextureOffsetS1 = iSectorCount           * fReciprocalPrecisition;
            float fTextureOffsetT1 = 2 * iStackCount        * fReciprocalPrecisition;
            float fTextureOffsetS2 = (iSectorCount + 1)     * fReciprocalPrecisition;
            float fTextureOffsetT2 = 2 * (iStackCount  + 1) * fReciprocalPrecisition;
 
 
            if (fLeftBtm[0] != fRghtBtm[0] || fLeftBtm[2] != fRghtBtm[2])
            {
                // face normal
                ::glNormal3f(n[0], n[1], n[2]);
 
                if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
                ::glVertex3fv(fLeftTop);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT2);
                ::glVertex3fv(fLeftBtm);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
                ::glVertex3fv(fRghtBtm);
            }
            if (fLeftTop[0] != fRghtTop[0] || fLeftTop[2] != fRghtTop[2])
            {
                // face normal
                ::glNormal3f(n[0], n[1], n[2]);
 
                if (bTexture) ::glTexCoord2f(fTextureOffsetS1, fTextureOffsetT1);
                ::glVertex3fv(fLeftTop);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT2);
                ::glVertex3fv(fRghtBtm);
                if (bTexture) ::glTexCoord2f(fTextureOffsetS2, fTextureOffsetT1);
                ::glVertex3fv(fRghtTop);
            }
 
            iSectorCount++;
        }
        iStackCount++;
    }
    ::glEnd();
}

环境

BoxCylinderConeSphere 的调用环境就像一个模子里刻出来的 - 这就是为什么我只在这里展示 Box 的调用环境

void Box::RenderToOpenGL(X3DNode* pParentNode, HDC dcOpenGlWindow)
{
	Vector3f size = GetSize();
	GLuint   uiTextureID = 0;
 
	X3DShapeNode* pParentShape = dynamic_cast<X3DShapeNode*>(pParentNode);
	if (pParentShape != NULL)
	{
		X3DAppearanceNode* pAppearence = pParentShape->GetAppearance();
		if ((Appearance*)pAppearence != NULL)
		{
			((Appearance*)pAppearence)->ApplyMaterial(dcOpenGlWindow);
			uiTextureID = ((Appearance*)pAppearence)->ApplyTexture(dcOpenGlWindow);
		}
	} 
 
	if (uiTextureID != 0)
	{
		::glBindTexture(GL_TEXTURE_2D, uiTextureID);
		::glEnable(GL_TEXTURE_2D);
	}
 
	OpenGL::DrawBox(size.x * 0.5f, size.y * 0.5f, size.z * 0.5f, (uiTextureID != 0));
	
	if (uiTextureID != 0)
	{
		::glDisable(GL_TEXTURE_2D);
		::glBindTexture(GL_TEXTURE_2D, 0);
	}
}

由于我的应用程序将是一个 X3DOM 查看器,我也会使用 X3DOM 的类层次结构。根据这个,MaterialTexture (按 Appearance 分组) 以及 Box/Cylinder/Cone/Sphere 都是 Shape 的子级。

材质和纹理

以下代码位于 ApplyMaterial(...)ApplyTexture(...) 之后 (目前只实现了 X3DOM 规范的一小部分)

/// <summary>Applies the <c>X3DMaterialNode</c> to the subsequent scene objects.</summary>
/// <param name="dcOpenGlWindow">The device context of the OpenGL window.</param>
void Appearance::ApplyMaterial(HDC dcOpenGlWindow)
{
	X3DMaterialNode* pMaterial = GetMaterial();
	if (pMaterial != NULL)
	{
		SFColor oColor = pMaterial->GetDiffuseColor();
		::glColor3f(oColor.r, oColor.g, oColor.b);
	}
	else
	{
		SFColor oColor = CSSColors::Colors[CSSColors::IndexOf(L"Gray")].Value;
		::glColor3f(oColor.r, oColor.g, oColor.b);
	}
}

/// <summary>Applies the <c>X3DTextureNode</c> to the subsequent scene objects.</summary>
/// <param name="dcOpenGlWindow">The device context of the OpenGL window.</param>
/// <returns>The applied texture (name/id) on success, or <c>0</c> otherwise.</returns>
GLuint Appearance::ApplyTexture(HDC dcOpenGlWindow)
{
	GLuint           uiTextureID = 0;
	X3DTextureNode*  pTexture    = GetTexture();
 
	if (pTexture != NULL)
	{
		String strUri = pTexture->GetUri();
		if (!String::IsNullOrEmpty(strUri))
		{
			uiTextureID = OpenGL::GetTexture(strUri.Value());
			if (uiTextureID == 0)
			{
				uiTextureID = OpenGL::AddTexture(strUri.Value(), dcOpenGlWindow);
			}
		}
	}
 
	return uiTextureID;
}

为了完整起见,这里是 AddTexture(...) 的代码 (目前仅支持 24bpp 位图,例如,它们可以使用 MS Paint 创建)

/// <summary>
/// Register a new texture (name/id) based on the indicated image URI.
/// </summary>
/// <param name="wszFilename">The file name of the bitmap file to load.</param>
/// <param name="dcOpenGlWindow">The device context of the OpenGL window.</param>
/// <returns>The texture (name/id) on success, or <c>0</c> otherwise.</returns>
GLuint OpenGL::AddTexture(const WCHAR* wszFilename, HDC dcOpenGlWindow)
{
    GLuint  uiTextureID = 0;
 
    ::glGenTextures(1, &uiTextureID);
 
    if (uiTextureID == 0)
    {
        Console::WriteError(
            L"OpenGL::AddTexture() Failed to acquire a new texture (name/id)!\n");
        return uiTextureID;
    }
 
    int   iWidth = 0;
    int   iHeight = 0;
    BYTE* pbyBitmapPixel = NULL;
 
    if (OpenGL::LoadTextureImageFile(wszFilename, dcOpenGlWindow, iWidth, iHeight,
                                     &pbyBitmapPixel))
    {
        ::glEnable(GL_TEXTURE_2D);
        ::glBindTexture(GL_TEXTURE_2D, uiTextureID);
 
        // set the texture wrapping
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 
        // scale linearly when image bigger/smaller than texture
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 
        ::glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
        ::glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
        ::glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 
        ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, iWidth, iHeight, 0,
                       GL_BGR_EXT, GL_UNSIGNED_BYTE, pbyBitmapPixel);
        __GLEXTFP_GLGENERATEMIPMAPS __glextGenerateMipmaps =
            (__GLEXTFP_GLGENERATEMIPMAPS)__GLEXT_GetProcAddress("glGenerateMipmaps");
        if (__glextGenerateMipmaps != NULL)
            __glextGenerateMipmaps(GL_TEXTURE_2D);
        else
            Console::WriteWarning(
                L" OpenGL::AddTexture() Extension 'glGenerateMipmaps' not available!\n");
 
        //::gluBuild2DMipmaps(GL_TEXTURE_2D, 3, iWidth, iHeight, GL_BGR_EXT,
        //                    GL_UNSIGNED_BYTE, pbyBitmapPixel);
 
        free(pbyBitmapPixel);
        pbyBitmapPixel = NULL;
 
        ::glBindTexture(GL_TEXTURE_2D, 0);
        ::glDisable(GL_TEXTURE_2D);
 
        _textures[wszFilename] = uiTextureID;
    }
    else
    {
        Console::WriteError(
            L"OpenGL::AddTexture() Failed to assign bitmap to new texture (name/id)!\n");
        ::glDeleteTextures(1, &uiTextureID);
        uiTextureID = 0;
    }
 
    return uiTextureID;
}

就这样。祝您在 OpenGL 中玩得开心!

关注点

即使 ::glBegin(GL_TRIANGLES)::glEnd() 背后的数学是清楚的 - 正确设置所有坐标也需要一些反复试验。 ::glNormal3f(...)::glTexCoord2f(...) 也是如此。

历史

  • 2021年3月10日:初始技巧
  • 2021年4月17日:添加了 pColors 参数到圆柱体和圆锥体
© . All rights reserved.