Timm
Timm

Reputation: 169

OpenGL circle not working

I try to draw a circle, but somehow it gets all messed up.

    typedef struct {
    float Position[3];
    float Color[4];
} Vertex;

Vertex* Vertices; //vertices store information for each "point" used to draw a triangle
GLubyte* Indices; //Used to reuse vertices
//const GLushort Indices[] = {
//    0, 1, 2,
//    2, 3,
//    3, 4
//};
int points = 181;

@interface CometGLViewController (){
    float _curRed;
    BOOL _increasing;
    GLuint _vertexBuffer;
    GLuint _indexBuffer;
    GLuint _vertexArray;
    float _rotation;
}

@property (strong, nonatomic) EAGLContext *context;
@property (strong, nonatomic) GLKBaseEffect *effect;

@end

@implementation CometGLViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

# pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }

    GLKView *view = (GLKView *)self.view;
    view.context = self.context;
    view.drawableMultisample = GLKViewDrawableMultisample4X;
    [self setupVertices];
    [self setupGL];
}

- (void)viewDidUnload
{
    [super viewDidUnload];

    [self tearDownGL];

    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    self.context = nil;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - GLKViewDelegate

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(0, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    [self.effect prepareToDraw];

    glBindVertexArrayOES(_vertexArray);
    glDrawElements(GL_TRIANGLES, points * 2 + 1, GL_UNSIGNED_BYTE, 0);
}

#pragma mark - GLKViewControllerDelegate

- (void)update
{
    if (_increasing) {
        _curRed += 1.0 * self.timeSinceLastUpdate;
    } else {
        _curRed -= 1.0 * self.timeSinceLastUpdate;
    }
    if (_curRed >= 1.0) {
        _curRed = 1.0;
        _increasing = NO;
    }
    if (_curRed <= 0.0) {
        _curRed = 0.0;
        _increasing = YES;
    }

    //Rotate
    float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 4.0f, 15.0f);
    self.effect.transform.projectionMatrix = projectionMatrix;

    GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -6.0f);
    //_rotation += 90 * self.timeSinceLastUpdate;
    //modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(_rotation), 0, 0, 1);
    self.effect.transform.modelviewMatrix = modelViewMatrix;
}

#pragma mark - OpenGL stuff
- (void)setupGL {

    [EAGLContext setCurrentContext:self.context];
    glEnable(GL_CULL_FACE);
    self.effect = [[GLKBaseEffect alloc] init];

// ----- Setup textures
//    NSDictionary * options = [NSDictionary dictionaryWithObjectsAndKeys:
//                              [NSNumber numberWithBool:YES],
//                              GLKTextureLoaderOriginBottomLeft,
//                              nil];
//    
//    NSError * error;
//    NSString *path = [[NSBundle mainBundle] pathForResource:@"tile_floor" ofType:@"png"];
//    GLKTextureInfo * info = [GLKTextureLoader textureWithContentsOfFile:path options:options error:&error];
//    if (info == nil) {
//        NSLog(@"Error loading file: %@", [error localizedDescription]);
//    }
//    
//    self.effect.texture2d0.name = info.name;
//    self.effect.texture2d0.enabled = true;

    glGenVertexArraysOES(1, &_vertexArray);
    glBindVertexArrayOES(_vertexArray);
// ----- create new buffer,  work with "vertexBuffer", glBufferData sends data for opengl usage
    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * points, Vertices, GL_STATIC_DRAW);

    glGenBuffers(1, &_indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * (points * 2 + 1), Indices, GL_STATIC_DRAW);

// ----- Setup vertices attributes
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Position));
    glEnableVertexAttribArray(GLKVertexAttribColor);
    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Color));
}

- (void)tearDownGL {

    [EAGLContext setCurrentContext:self.context];

    glDeleteBuffers(1, &_vertexBuffer);
    glDeleteBuffers(1, &_indexBuffer);
    glDeleteVertexArraysOES(1, &_vertexArray);

    self.effect = nil;

}

- (void)setupVertices
{
    Vertices = malloc(sizeof(Vertex) * points);
    Vertex v = {{0, 0, 0}, {0, 0, 1, 1}};
    Vertices[0] = v;
    GLubyte ind[points *2 +1];
    ind[0] = 0;
    int indPos = 1;

    for(int i = 1; i < points; ++i){
        float theta = 2.0 * 3.1415926 * ( ((float)i-1)*2 / (float)points );
        Vertex v = {{cosf(theta), sinf(theta), 0}, {1, 1, 0, 1}};
        Vertices[i] = v;

        ind[indPos] = i;
        if(i != points-1)
            ind[indPos + 1] = i+1;
        else
            ind[indPos +1] = 1;
        indPos = indPos +2;

        NSLog(@"%f und %f", cosf(theta), sinf(theta));
        NSLog(@"%i und %i", i, i+1);
    }
    Indices = ind;
}

#pragma mark - Touch control

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.paused = !self.paused;
}

@end

pic pic2

EDIT1: Full code now. I tried to use GLushort instead of GLubyte to use all 360 points but its crashing when drawing with something else then GL_POINTS.

EDIT2: Updated to current Code. Current pic also added.I guess there's still smth wrong. I reduced points to 180 (+1 for mid) so i dont get in trouble with GLubyte. Therefor I just took every second value, calculating the vertices.

Upvotes: 0

Views: 1138

Answers (2)

Andon M. Coleman
Andon M. Coleman

Reputation: 43329

Your indices are wrong for the primitive mode you are using.

They are in fan-order, but then you pass GL_TRIANGLE_FAN as the primitive type to glDrawElements (...). This is not a matter of redundancy, it is simply wrong. You have 3N indices, which means the only logical primitive mode to use with these indices is GL_TRIANGLES.

As it stands right now, you could solve this simply by replacing GL_TRIANGLE_FAN with GL_TRIANGLES in your call to glDrawElements (...), but this will not reap the benefits of a primitive mode that is optimized for triangle adjacency.


I will explain a better solution below that will reduce the necessary size of your index array and use GL_TRIANGLE_FAN correctly.

Judging by the following code:

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(0, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    [self.effect prepareToDraw];

    glBindVertexArrayOES(_vertexArray);
    glDrawElements(GL_TRIANGLE_FAN, points * 3, GL_UNSIGNED_SHORT, 0);
}

I can conclude that you were sort of on the right track, except you forgot that fans are not defined by 3 indices per-triangle; each additional triangle in a fan shares a common central vertex and re-uses the last vertex. Thus, the number of indices is N+2, where N is the number of triangles.

What you want to do is walk the perimeter of the circle with your indices, you will generate a total of V-2 triangles where V is the number of vertices on the perimeter of your circle. If you correct your indices to do this, then you should get a nice triangulated circle (or something that approximates one, anyway).


Visually, this is how a triangle fan works:

  

Each triangle shares the central vertex A, and re-uses the last vertex addressed. Thus after defining ABC each following triangle only requires 1 point (e.g. D, E, F).

Indices:     A,B,C,D,E,F     [Count: 6]
Triangles:  (A,B,C)
            (A) (C,D)
            (A)   (D,E)
            (A)     (E,F)    [N=4]  -->  4+2 = 6

Another way of thinking about this is that each triangle shares an edge radiating from a central vertex with the prior triangle; like a paper fan.


UPDATE:

I believe this should fix your problem, the number of indices will actually match the number of vertices in a proper fanned circle. My discussion of the number of triangles in relation to vertices above may have generated some confusion, the number of triangles is not actually defined anywhere in the code.

- (void)setupVertices
{
    Vertices = malloc(sizeof(Vertex) * points);
    Vertex v = {{0, 0, 0}, {0, 0, 1, 1}};
    Vertices[0] = v;
    GLubyte ind[points];
    ind[0] = 0;
    int indPos = 1;

    for(int i = 1; i < points; ++i){
        float theta = 2.0 * 3.1415926 * ( ((float)i-1)*2 / (float)points );
        Vertex v = {{cosf(theta), sinf(theta), 0}, {1, 1, 0, 1}};
        Vertices[i] = v;

        ind[indPos] = i;

        NSLog(@"%f und %f", cosf(theta), sinf(theta));
        NSLog(@"%i und %i", i, i+1);
    }
    Indices = ind;
}

Now, when it comes time to draw the fan you will use points-many indices. Incidentally, you do not even need indexed vertex rendering to draw a fan like this efficiently; the indices are all sequential. glDrawArrays (GL_TRIANGLE_FAN, 0, <num_points>) will get the job done.

While we are on the topic of efficiency, I would like to take this opportunity to point out that GLubyte is not a hardware supported index type on desktop GPUs. The GPU will wind up performing a bunch of unaligned memory accesses if you use 8-bit indices, so any client storage benefits are offset by added run-time overhead on the GPU.

This is something I see a lot of. While it is always good to use the smallest type possible in GL, you need to consider the smallest type the GPU can use natively. The problem is that the detail of hardware supported index types is rarely discussed anywhere and only those who have been in the industry for a long time seem to know this detail. Using the fantastic new OpenGL Debug Output facilities, most drivers will print a performance warning when you use a GLubyte index type, so hopefully this detail will start to receive more attention.

Upvotes: 4

derhass
derhass

Reputation: 45342

You are using

GLubyte ind[points*3];

but you have

int points = 360;

as ubyte is just 8 bit, you can adress up to 256 different vertices, which explains that you don't get a full circle but only 256 degrees. However, I think there might be something else wrong, as I don't see how this mistake would explain all of the issues in your screenshots...

Upvotes: 1

Related Questions