OpenGL sprites - please help this n00b

Member
Posts: 73
Joined: 2009.03
Post: #1
I am an experienced game developer, but have never worked with OpenGL directly. I used the GLSprite demo program as a starting point. I wrote a class to separate the sprite loading and drawing from the main program so I can load several different sprites and draw them all. However, I am at the point where my code compiles and runs, but nothing shows up. It's driving me up a wall. Here's the main parts of what I have so far...

EAGLView.h:
Code:
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import "GLSprite.h"

@interface EAGLView : UIView
{
@private
    
    /* The pixel dimensions of the backbuffer */
    GLint backingWidth;
    GLint backingHeight;
    
    EAGLContext *context;
    
    /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
    GLuint viewRenderbuffer, viewFramebuffer;
    
    /* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */
    GLuint depthRenderbuffer;
    
    GLSprite *sprite;
    
    NSTimer *animationTimer;
    NSTimeInterval animationInterval;
}

- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView;

@property NSTimeInterval animationInterval;

@end

EAGLView.m
Code:
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

// Sets up an array of values to use as the sprite vertices.
const GLfloat spriteVertices[] = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f,  0.5f,
0.5f,  0.5f,
};

// Sets up an array of values for the texture coordinates.
const GLshort spriteTexcoords[] = {
0, 0,
1, 0,
0, 1,
1, 1,
};

@interface EAGLView (EAGLViewPrivate)

- (BOOL)createFramebuffer;
- (void)destroyFramebuffer;

@end

@interface EAGLView (EAGLViewSprite)

- (void)setupView;

@end

@implementation EAGLView

@synthesize animationInterval;

// You must implement this
+ (Class) layerClass
{
    return [CAEAGLLayer class];
}


//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder
{
    if((self = [super initWithCoder:coder])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
        
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
        
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
        
        if(!context || ![EAGLContext setCurrentContext:context] || ![self createFramebuffer]) {
            [self release];
            return nil;
        }
        
        animationInterval = 1.0 / 60.0;
        
        [self setupView];
        [self drawView];
    }
    
    return self;
}


- (void)layoutSubviews
{
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
    [self drawView];
}


- (BOOL)createFramebuffer
{
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
    
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
    
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
    
    return YES;
}


- (void)destroyFramebuffer
{
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
    
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}


- (void)startAnimation
{
    animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}

- (void)stopAnimation
{
    [animationTimer invalidate];
    animationTimer = nil;
}

- (void)setAnimationInterval:(NSTimeInterval)interval
{
    animationInterval = interval;
    
    if(animationTimer) {
        [self stopAnimation];
        [self startAnimation];
    }
}

- (void)setupView
{    
    // Sets up matrices and transforms for OpenGL ES
    glViewport(0, 0, backingWidth, backingHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
    
    // Clears the view with black
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    
    // Sets up pointers and enables states needed for using vertex arrays and textures
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Create a sprite
    sprite = [GLSprite createSpriteFromImage:@"Sprite.png"];
}

// Updates the OpenGL view when the timer fires
- (void)drawView
{
    // Make sure that you are drawing to the current context
    [EAGLContext setCurrentContext:context];
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    
    glClear(GL_COLOR_BUFFER_BIT);

    // Prepare vertices for sprite drawing.
    glVertexPointer(2, GL_FLOAT, 0, spriteVertices);
    glTexCoordPointer(2, GL_FLOAT, 0, spriteTexcoords);

    // Draw the sprite
    [sprite drawAt:160 andY:240];
    
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

// Stop animating and release resources when they are no longer needed.
- (void)dealloc
{
    [sprite release];
    [self stopAnimation];
    
    if([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
    
    [context release];
    context = nil;
    
    [super dealloc];
}

@end

GLSprite.h:
Code:
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

@interface GLSprite : NSObject {
    GLuint spriteTexture;
    GLfloat size;    // width and height are the same
}

-(GLSprite *)initFromImage:(NSString *) imageFile;

- (void) drawAt:(GLfloat)x andY:(GLfloat)y;
- (void) drawAt:(GLfloat)x andY:(GLfloat)y atScale:(GLfloat)scale;
- (void) drawAt:(GLfloat)x andY:(GLfloat)y withRotation:(GLfloat)rotation;
- (void) drawAt:(GLfloat)x andY:(GLfloat)y atScale:(GLfloat)scale withRotation:(GLfloat)rotation;

+ (GLSprite *)createSpriteFromImage:(NSString *) imageFile;

@end

GLSprite.m:
Code:
#import "GLSprite.h"

@implementation GLSprite

-(GLSprite *)initFromImage:(NSString *)imageFile
{
    if (self = [super init])
    {
        CGImageRef spriteImage;
        CGContextRef spriteContext;
        GLubyte *spriteData;
        
        // Creates a Core Graphics image from an image file
        spriteImage = [UIImage imageNamed:@"Sprite.png"].CGImage;
        
        if(spriteImage) {
            // Get the width and height of the image
            size = (GLfloat)CGImageGetWidth(spriteImage);

            // Texture dimensions must be square, and a power of 2. If not, then I'm an idiot for making a bad file.
            
            // Allocated memory needed for the bitmap context
            spriteData = (GLubyte *) malloc(size * size * 4);
            // Uses the bitmatp creation function provided by the Core Graphics framework.
            spriteContext = CGBitmapContextCreate(spriteData, size, size, 8, size * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
            // After you create the context, you can draw the sprite image to the context.
            CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)size, (CGFloat)size), spriteImage);
            // You don't need the context at this point, so you need to release it to avoid memory leaks.
            CGContextRelease(spriteContext);
            
            // Use OpenGL ES to generate a name for the texture.
            glGenTextures(1, &spriteTexture);
            // Bind the texture name.
            glBindTexture(GL_TEXTURE_2D, spriteTexture);
            // Specify a 2D texture image, providing the pointer to the image data in memory
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
            // Release the image data
            free(spriteData);
            
            // Set the texture parameters to use a minifying filter and a linear filter (weighted average)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            // Enable use of the texture
            glEnable(GL_TEXTURE_2D);
            // Set a blending function to use
            glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
            // Enable blending
            glEnable(GL_BLEND);
        
            return self;
        }
    }
    return nil;
}

+ (GLSprite *)createSpriteFromImage:(NSString *) imageFile
{
    return [[[self alloc] initFromImage:imageFile] retain];
    // When I use autorelease, the debugger acts like there is an error, but no error is reported.
    // So, don't know what's going on with that.
}

- (void) drawAt:(GLfloat)x andY:(GLfloat)y
{
    [self drawAt:x andY:y atScale:1 withRotation:0];
}

- (void) drawAt:(GLfloat)x andY:(GLfloat)y atScale:(GLfloat)scale
{
    [self drawAt:x andY:y atScale:scale withRotation:0];
}

- (void) drawAt:(GLfloat)x andY:(GLfloat)y withRotation:(GLfloat)rotation
{
    [self drawAt:x andY:y atScale:1 withRotation:rotation];
}

- (void) drawAt:(GLfloat)x andY:(GLfloat)y atScale:(GLfloat)scale withRotation:(GLfloat)rotation
{
    //Bind this texture.
    glBindTexture(GL_TEXTURE_2D, spriteTexture);
    
    //Push the matrix so we can keep it as it was previously.
    glPushMatrix();
    
    //Translate the OpenGL context to the center of the sprite for rotation and scale.
    glTranslatef(x+size/2, y+size/2, 0.0f);
    
    //Apply the rotation over the Z axis.
    glRotatef(rotation, 0.0f, 0.0f, 1.0f);

    // Scale relative to pixel size.
    glScalef(scale * size, scale * size, 1.0f);
    
    //Translate back to the top left corner of the sprite for drawing.
    glTranslatef(-size/2, -size/2, 0.0f);
    
    //Finally draw the arrays.
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    
    //Restore the model view matrix to prevent contamination.
    glPopMatrix();
}

@end
Quote this message in a reply
Moderator
Posts: 3,572
Joined: 2003.06
Post: #2
There are a few problems here. First, your texCoordPointer is specifying floats, when you're defining shorts. Next up is that your transforms in your sprite drawing method are all messed up. Third, you're attempting to draw a triangle fan with geometry set up for a triangle strip. Change these lines in your drawView method to get something drawing for now so you can then work out the transforms:

Code:
    glTexCoordPointer(2, GL_SHORT, 0, spriteTexcoords); // <-- needs to be SHORT, not FLOAT

    // Draw the sprite
    //[sprite drawAt:160 andY:240]; // <-- funky transforms in here, so skip for now
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // <-- do this instead for now
Quote this message in a reply
Member
Posts: 73
Joined: 2009.03
Post: #3
Thank you so much. This did indeed get the sprite on the screen. I should be able to figure out the transforms. Smile
Quote this message in a reply
Member
Posts: 51
Joined: 2009.02
Post: #4
Gillissie,

I am in the same boat. I'm an experienced dev new to iPhone.

I'm frustrated that the OpenGL sample programs on the iPhone consist of a single image rotating in the center of the screen. Two differently-textured objects bouncing around would be my dream.

Earlier programmers seem to have benefited from CrashLander, which I cannot find. I'm aware of its limitations and bugs, but I still want to see it.

If anyone has it, I would be eternally grateful if I could get a copy. rhettanderson is the name and gmail email is my game. You can deduce my email from those hints.

Gillissie, any chance we could work through some early kinks together?
Quote this message in a reply
Moderator
Posts: 3,572
Joined: 2003.06
Post: #5
The main reason people are interested in CrashLanding is for the Texture2D class, which you can currently find in cocos2d-iPhone.
Quote this message in a reply
Member
Posts: 51
Joined: 2009.02
Post: #6
Thanks for the response, AnotherJake. But I want to see an example that has more than one visual object in it. Something to play with while I'm learning.
Quote this message in a reply
Member
Posts: 73
Joined: 2009.03
Post: #7
Nosredna Wrote:Gillissie,
Gillissie, any chance we could work through some early kinks together?

I will offer anyone help if I know the answer to a question. I was able to get my transformations worked out. I learned a bit from doing that.

For now, I am exclusively going to work with 2D graphics, so I wanted to the mimic traditional 2D screen coords (upper-left is 0,0 and going right increases x, going down increases y). Also for now, I am only working with a vertical screen. Based on this info, I set up my orthographic view (or matrix) like this (based on the GLSprite demo):

Code:
glOrthof(0.0f, (GLfloat)backingwidth, (GLfloat)-backingHeight, 0.0f, -1.0f, 1.0f);

That sets up your screen's coord system like I described above.

Next, you have to compensate for the upside-down screen coords by setting your sprites' texture coords upside down. I specified that like this (the second number on each line is the "V" coordinate of the UV map):

Code:
const GLshort spriteTexcoords[] = {
0, 1,
1, 1,
0, 0,
1, 0,
}

Finally, in my GLSprite class's draw method, I used the following transformations to match the ortho screen matrix that I set up earlier. I noticed that you get different results if you do the transformations in a different order. This order worked for my scaling, rotation and positioning.

Code:
// Where x and y are screen coords from upper-left,
// and size is a specified "normal" pixel size when creating the sprite,
// and scale is a multiplier to adjust the "normal" size in the game loop.

glBindTexture(GL_TEXTURE_2D, spriteTexture);

plPushMatrix()

// Add half the sprite size for drawing the sprite from the upper-left corner of the sprite instead of the center.
glTranslatef(x+size/2, (y+size/2) * -1, 0.0f);

// use negative rotation for clockwise positive rotation
glRotatef(-rotation, 0.0f, 0.0f, 1.0f);

// Scale relative to pixel size. this must be done after translation and rotation.
glScalef(scale * size, scale * size, 1.0f);

// draw it
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glPopMatrix()
Quote this message in a reply
Member
Posts: 51
Joined: 2009.02
Post: #8
Cool. All that helps to know.

What I did yesterday was add an alpha to the draw so I can see a fade (0-1).

What graphics programs work to create PNGs with alpha? I know they have to be power-of-2 width and height, but I've had some PNGs that end up with garbage in them.
Quote this message in a reply
Moderator
Posts: 3,572
Joined: 2003.06
Post: #9
I use Photoshop for making my PNGs.

If you're doing images less than 64x64 and you're using Texture2D, then you'll need to replace a malloc with calloc to get rid of the garbage showing up in the alpha channel. Otherwise, I've never seen any problems like that.
Quote this message in a reply
Member
Posts: 51
Joined: 2009.02
Post: #10
AnotherJake Wrote:I use Photoshop for making my PNGs.

If you're doing images less than 64x64 and you're using Texture2D, then you'll need to replace a malloc with calloc to get rid of the garbage showing up in the alpha channel. Otherwise, I've never seen any problems like that.

I was using a 32x32! Thanks!
Quote this message in a reply
Member
Posts: 73
Joined: 2009.03
Post: #11
Nosredna Wrote:What I did yesterday was add an alpha to the draw so I can see a fade (0-1).

Please tell me how you do this. I was going to investigate this next too.
Quote this message in a reply
Member
Posts: 73
Joined: 2009.03
Post: #12
AnotherJake Wrote:I use Photoshop for making my PNGs.

If you're doing images less than 64x64 and you're using Texture2D, then you'll need to replace a malloc with calloc to get rid of the garbage showing up in the alpha channel. Otherwise, I've never seen any problems like that.

Using calloc works on images < 64 pixels in size, but it also works on images >= 64 pixels. Is there a reason not to use it for all images?
Quote this message in a reply
Moderator
Posts: 3,572
Joined: 2003.06
Post: #13
Gillissie Wrote:Using calloc works on images < 64 pixels in size, but it also works on images >= 64 pixels. Is there a reason not to use it for all images?

In this case, no. I think calloc might be a tad slower than malloc (don't quote me on that), but it makes virtually no difference when loading images on iPhone. Should/can/might-as-well use calloc for all of them.
Quote this message in a reply
Member
Posts: 51
Joined: 2009.02
Post: #14
Gillissie Wrote:Please tell me how you do this. I was going to investigate this next too.

I added scale and alpha. I'll give give details tomorrow.
Quote this message in a reply
Member
Posts: 73
Joined: 2009.03
Post: #15
Nosredna Wrote:I'll give give details tomorrow.

I figured this out on my own. The key function is glColor4f. The trick is to multiply each of the color values by the alpha value too, otherwise you'll still see the sprite even at 0 alpha, which seems weird to me.

Basically, like this before drawing:

glColorf(r * a, g * a, b * a, a);
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  openGL n00b looking for FBO drawing examples or direction auptown 5 7,073 Nov 4, 2009 02:16 AM
Last Post: Eskema
  iPhone opengl 2d sprites landscape mode mnorton 6 5,582 Sep 9, 2009 08:27 PM
Last Post: mnorton