简体   繁体   中英

How do I texture a cylinder in OpenGL created with triangle_strip?

Here's the cylinder I have created:

void drawCylinder(float r, float g, float b) {
    setMaterialColors(r, g, b);
    glColor4f(r, g, b, 1.0);
    /* top triangle */
    double i, resolution  = 0.1;
    double height = 1, radius = 0.5;
    glPushMatrix();
    glTranslatef(0, -0.5, 0);
    glBegin(GL_TRIANGLE_FAN);
        glVertex3f(0, height, 0);  /* center */
        for (i = 0; i <= 2 * PI; i += resolution)
            glVertex3f(radius * cos(i), height, radius * sin(i));
    glEnd();

    /* bottom triangle: note: for is in reverse order */
    glBegin(GL_TRIANGLE_FAN);
        glVertex3f(0, 0, 0);  /* center */
        for (i = 2 * PI; i >= 0; i -= resolution)
            glVertex3f(radius * cos(i), 0, radius * sin(i));
        /* close the loop back to 0 degrees */
        glVertex3f(radius, height, 0);
    glEnd();

    /* middle tube */
    glBegin(GL_QUAD_STRIP);
        for (i = 0; i <= 2 * PI; i += resolution)
        {
            glVertex3f(radius * cos(i), 0, radius * sin(i));
            glVertex3f(radius * cos(i), height, radius * sin(i));
        }
        /* close the loop back to zero degrees */
        glVertex3f(radius, 0, 0);
        glVertex3f(radius, height, 0);
    glEnd();
    glPopMatrix();
}

Now, I am unable to map textures to it. I tried keeping texture coordinate same as the triangle strip coordinates, but it didn't work.

PS: I don't want to use gluCylinder. I know it exists.


EDIT: It's fine if I can only texture the disks. The quad strip needn't be textured.

For the center: Divide i by 2 * PI to get a number that varies between 0.0 and 1.0 and use that as a texture coordinate:

for (i = 0; i <= 2 * PI; i += resolution)
{
    const float tc = ( i / (float)( 2 * PI ) );
    glTexCoord2f( tc, 0.0 );
    glVertex3f(radius * cos(i), 0, radius * sin(i));
    glTexCoord2f( tc, 1.0 );
    glVertex3f(radius * cos(i), height, radius * sin(i));
}

/* close the loop back to zero degrees */
glTexCoord2f( 0.0, 0.0 );
glVertex3f(radius, 0, 0);
glTexCoord2f( 0.0, 1.0 );
glVertex3f(radius, height, 0);

For the endcaps: You can use the sin() / cos() output (almost) directly:

glTexCoord2f( 0.5, 0.5 );
glVertex3f(0, height, 0);  /* center */
for (i = 0; i <= 2 * PI; i += resolution)
{
    // scale sin/cos range (-1 to 1) by 0.5 to get -0.5 to 0.5
    // then shift that range up/right by 0.5 to get 0 to 1:
    glTexCoord2f( 0.5f * cos(i) + 0.5f, 0.5f * sin(i) + 0.5f );
    glVertex3f(radius * cos(i), height, radius * sin(i));
}

Complete example:

#include <GL/glew.h>
#include <GL/glut.h>
#include <cmath>

void cylinder()
{
    const double PI = 3.14159;

    /* top triangle */
    double i, resolution  = 0.1;
    double height = 1;
    double radius = 0.5;

    glPushMatrix();
    glTranslatef(0, -0.5, 0);

    glBegin(GL_TRIANGLE_FAN);
        glTexCoord2f( 0.5, 0.5 );
        glVertex3f(0, height, 0);  /* center */
        for (i = 2 * PI; i >= 0; i -= resolution)

        {
            glTexCoord2f( 0.5f * cos(i) + 0.5f, 0.5f * sin(i) + 0.5f );
            glVertex3f(radius * cos(i), height, radius * sin(i));
        }
        /* close the loop back to 0 degrees */
        glTexCoord2f( 0.5, 0.5 );
        glVertex3f(radius, height, 0);
    glEnd();

    /* bottom triangle: note: for is in reverse order */
    glBegin(GL_TRIANGLE_FAN);
        glTexCoord2f( 0.5, 0.5 );
        glVertex3f(0, 0, 0);  /* center */
        for (i = 0; i <= 2 * PI; i += resolution)
        {
            glTexCoord2f( 0.5f * cos(i) + 0.5f, 0.5f * sin(i) + 0.5f );
            glVertex3f(radius * cos(i), 0, radius * sin(i));
        }
    glEnd();

    /* middle tube */
    glBegin(GL_QUAD_STRIP);
        for (i = 0; i <= 2 * PI; i += resolution)
        {
            const float tc = ( i / (float)( 2 * PI ) );
            glTexCoord2f( tc, 0.0 );
            glVertex3f(radius * cos(i), 0, radius * sin(i));
            glTexCoord2f( tc, 1.0 );
            glVertex3f(radius * cos(i), height, radius * sin(i));
        }
        /* close the loop back to zero degrees */
        glTexCoord2f( 0.0, 0.0 );
        glVertex3f(radius, 0, 0);
        glTexCoord2f( 0.0, 1.0 );
        glVertex3f(radius, height, 0);
    glEnd();

    glPopMatrix();
}

GLuint tex;
void init()
{
    unsigned char data[] =
    {
        128, 128, 128, 255,
        255, 0, 0, 255,
        0, 255, 0, 255,
        0, 0, 255, 255,
    };

    glGenTextures( 1, &tex );
    glBindTexture( GL_TEXTURE_2D, tex );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexImage2D( GL_TEXTURE_2D, 0,GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
}

float angle = 0;
void timer( int value )
{
    angle += 6;
    glutPostRedisplay();
    glutTimerFunc( 16, timer, 0 );
}

void display()
{
    glClearColor( 0, 0, 0, 1 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective( 60, 1.0, 0.1, 100.0 );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glTranslatef( 0, 0, -5 );

    glEnable( GL_CULL_FACE );
    glEnable( GL_DEPTH_TEST );

    glRotatef( angle, 0.2, 0.3, 0.1 );

    glEnable( GL_TEXTURE_2D );
    glBindTexture( GL_TEXTURE_2D, tex );
    cylinder();

    glutSwapBuffers();
}

int main(int argc, char **argv)
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
    glutInitWindowSize( 600, 600 );
    glutCreateWindow( "GLUT" );
    init();
    glutDisplayFunc( display );
    glutTimerFunc( 0, timer, 0 );
    glutMainLoop();
    return 0;
}

It looks like you have a functional solution. But there's a few more things that I think are worth improving compared to the original code, so I still want to provide an alternative. It addresses:

  • It's very inefficient, using a lot of sin / cos calls, as well as divisions, which are all fairly expensive. The code below uses one sin , one cos , and a couple of divisions that can be evaluated at compile time. The rest is all additions and multiplications, which are much faster operations.
  • The original code uses floating point values for loop control. Since floating point values are inherently imprecise, this will more or less "randomly" result in one more or less iteration, depending on how the rounding happens to work out.
  • The original code uses a wild mix of float , double and int types. float is fine for coordinates, there is no need to use slower double precision, with also expensive type casts back to float .

Code (untested, but hopefully close enough to convey the proposed approach):

// Number of segments a circle is divided into.
const unsigned DIV_COUNT = 32;

// Calculate angle increment from point to point, and its cos/sin.
float angInc = 2.0f * M_PI / static_cast<float>(DIV_COUNT);
float cosInc = cos(angInc);
float sinInc = sin(angInc);

// Draw top cap.
glBegin(GL_TRIANGLE_FAN);

glTexCoord2f(0.5f, 0.5f);
glVertex3f(0.0f, height, 0.0f);

glTexCoord2f(1.0f, 0.5f);
glVertex3f(radius, height, 0.0f);

float xc = 1.0f;
float yc = 0.0f;
for (unsigned iDiv = 1; iDiv < DIV_COUNT; ++iDiv) {
    float xcNew = cosInc * xc - sinInc * yc;
    yc = sinInc * xc + cosInc * yc;
    xc = xcNew;
    glTexCoord2f(0.5f + 0.5f * xc, 0.5f + 0.5f * yc);
    glVertex3f(radius * xc, height, -radius * yc);
}

glTexCoord2f(1.0f, 0.5f);
glVertex3f(radius, height, 0.0f);

glEnd();

// Draw bottom cap.
glBegin(GL_TRIANGLE_FAN);

glTexCoord2f(0.5f, 0.5f);
glVertex3f(0.0f, 0.0f, 0.0f);

glTexCoord2f(1.0f, 0.5f);
glVertex3f(radius, 0.0f, 0.0f);

xc = 1.0f;
yc = 0.0f;
for (unsigned iDiv = 1; iDiv < DIV_COUNT; ++iDiv) {
    float xcNew = cosInc * xc - sinInc * yc;
    yc = sinInc * xc + cosInc * yc;
    xc = xcNew;
    glTexCoord2f(0.5f + 0.5f * xc, 0.5f + 0.5f * yc);
    glVertex3f(radius * xc, 0.0f, radius * yc);
}

glTexCoord2f(1.0f, 0.5f);
glVertex3f(radius, 0.0f, 0.0f);

glEnd();

float texInc = 1.0f / static_cast<float>(DIV_COUNT);

// Draw cylinder.
glBegin(GL_TRIANGLE_STRIP);

glTexCoord2f(0.0f, 0.0f);
glVertex3f(radius, 0.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f);
glVertex3f(radius, height, 0.0f);

xc = 1.0f;
yc = 0.0f;
float texCoord = 0.0f;
for (unsigned iDiv = 1; iDiv < DIV_COUNT; ++iDiv) {
    float xcNew = cosInc * xc - sinInc * yc;
    yc = sinInc * xc + cosInc * yc;
    xc = xcNew;
    texCoord += texInc;
    glTexCoord2f(texCoord, 0.0f);
    glVertex3f(radius * xc, 0.0f, radius * yc);
    glTexCoord2f(texCoord, 1.0f);
    glVertex3f(radius * xc, height, radius * yc);
}

glTexCoord2f(0.0f, 0.0f);
glVertex3f(radius, 0.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f);
glVertex3f(radius, height, 0.0f);

glEnd();

If you're interested in moving to modern OpenGL, my answer to this question shows how to draw a circle using VBOs: How to draw a circle using VBO in ES2.0 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM