Search code examples
iosobjective-copengl-esopengl-es-2.0fbo

OpenGL ES 2.0 - iOS - Render to texture using FBO


Based on the tutorial :

"http://www.raywenderlich.com/4404/opengl-es-2-0-for-iphone-tutorial-part-2-textures"

and the book "Open GL ES 2.0 Programming Guide"

I'm trying to render a scene to a FBO texture, and then render that FBO texture on the display window.

I don't know why, but I have a white screen..

Edit 1 : The white screen was due to binding the depth buffer in the render loop. Now the problem is that the cube rendered in the FBO texture is not rendered on screen..

Edit 2 : Updated the code and added shader files

Here is my code :

OpenGLView.h :

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>

@interface OpenGLView : UIView {
    CAEAGLLayer* _eaglLayer;
    EAGLContext* _context;

    GLuint _viewFrameBuffer;
    GLuint _colorRenderBuffer;
    GLuint _depthRenderBuffer;



    float _cubeCurrentRotation;

    GLuint _cubeTexture;

    GLuint _cubeVertexBuffer;
    GLuint _cubeIndexBuffer;
    GLuint _viewportSizedQuadVertexBuffer;
    GLuint _viewportSizedQuadIndexBuffer;

    GLuint _FBO;
    GLuint _FBOTexture;
    GLuint _FBODepthBuffer;

    // shaders

    // simple shader
    GLuint _simpleShaderProgram;
    GLuint _simpleVertexShader;
    GLuint _simpleFragmentShader;
    GLuint _positionSlot;
    GLuint _colorSlot;
    GLuint _projectionUniform;
    GLuint _modelViewUniform;
    GLuint _texCoordSlot;
    GLuint _textureUniform;
    // texture shader
    GLuint _textureShaderProgram;
    GLuint _textureVertexShader;
    GLuint _texturePositionSlot;
    GLuint _textureColorSlot;
    GLuint _textureTexCoordSlot;
    GLuint _textureTextureUniform;
}

@end

OpenGLView.m :

    //
//  OpenGLView.m
//  HelloOpenGL
//
//  Created by Ray Wenderlich on 5/24/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "OpenGLView.h"
#import "CC3GLMatrix.h"

@implementation OpenGLView

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

#define TEX_COORD_MAX 1

const Vertex cubeVertices[] = {
    // Front
    {{1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Back
    {{1, 1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{-1, -1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{1, -1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 0, 1}, {0, 0}},
    // Left
    {{-1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{-1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, -2}, {0, 0, 0, 1}, {0, 0}},
    // Right
    {{1, -1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Top
    {{1, 1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, 1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Bottom
    {{1, -1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, -1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, -1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, -2}, {0, 0, 0, 1}, {0, 0}}
};

const GLubyte cubeIndices[] = {
    // Front
    0, 1, 2,
    2, 3, 0,
    // Back
    4, 5, 6,
    6, 7, 4,
    // Left
    8, 9, 10,
    10, 11, 8,
    // Right
    12, 13, 14,
    14, 15, 12,
    // Top
    16, 17, 18,
    18, 19, 16,
    // Bottom
    20, 21, 22,
    22, 23, 20
};

const Vertex viewPortQuadVertices[] = {
    {{1, -1, 0}, {1, 1, 1, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, 0}, {1, 1, 1, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, 0}, {1, 1, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, 0}, {1, 1, 1, 1}, {0, 0}}
};

const GLubyte viewPortQuadIndices[] = {
    0, 1, 2,
    2, 3, 0,
};

+ (Class)layerClass {
    return [CAEAGLLayer class];
}

- (void)dealloc {
    [_context release];
    _context = nil;
    [super dealloc];
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupLayer];
        [self setupContext];
        [self setupDepthBuffer];
        [self setupRenderBuffer];
        [self setupFrameBuffer];
        [self setupFBOs];
        [self compileShaders];
        [self setupVBOs];
        [self setupDisplayLink];
        _cubeTexture = [self setupTexture:@"tile_floor.png"];
    }
    return self;
}

- (void)setupDisplayLink {
    CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)setupLayer {
    _eaglLayer = (CAEAGLLayer*) self.layer;
    _eaglLayer.opaque = YES;
}

- (void)setupContext {
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    _context = [[EAGLContext alloc] initWithAPI:api];
    if (!_context) {
        NSLog(@"Failed to initialize OpenGLES 2.0 context");
        exit(1);
    }

    if (![EAGLContext setCurrentContext:_context]) {
        NSLog(@"Failed to set current OpenGL context");
        exit(1);
    }
}

- (void)setupRenderBuffer {
    glGenRenderbuffers(1, &_colorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}

- (void)setupDepthBuffer {
    glGenRenderbuffers(1, &_depthRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height);
}

- (void)setupFrameBuffer {
    glGenFramebuffers(1, &_viewFrameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _viewFrameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);
}

-(void)setupFBOs {
    GLint maxRenderBufferSize;
    glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderBufferSize);

    GLuint textureWidth = self.frame.size.width;
    GLuint textureHeight = self.frame.size.height;

    if(maxRenderBufferSize <= textureWidth
       || maxRenderBufferSize <= textureHeight) {
            NSLog(@"FBO cant allocate that much space");
    }

    glGenFramebuffers(1, &_FBO);
    glGenRenderbuffers(1, &_FBODepthBuffer);
    glGenTextures(1, &_FBOTexture);

    glBindTexture(GL_TEXTURE_2D, _FBOTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight,
                 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glBindRenderbuffer(GL_RENDERBUFFER, _FBODepthBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, textureWidth, textureHeight);

    glBindFramebuffer(GL_FRAMEBUFFER, _FBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _FBOTexture, 0);

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _FBODepthBuffer);

    GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"FBO is not complete :%u", status);
    }
}

- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
    NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"glsl"];
    NSError* error;
    NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString) {
        NSLog(@"Error loading shader: %@", error.localizedDescription);
        exit(1);
    }

    GLuint shaderHandle = glCreateShader(shaderType);

    const char * shaderStringUTF8 = [shaderString UTF8String];
    int shaderStringLength = [shaderString length];
    glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);

    glCompileShader(shaderHandle);

    GLint compileSuccess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
    if (compileSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"%@", messageString);
        exit(1);
    }

    return shaderHandle;
}

- (void)compileShaders {
    _simpleVertexShader = [self compileShader:@"SimpleVertex" withType:GL_VERTEX_SHADER];
    _simpleFragmentShader = [self compileShader:@"SimpleFragment" withType:GL_FRAGMENT_SHADER];
    _textureVertexShader = [self compileShader:@"TextureVertex" withType:GL_VERTEX_SHADER];

    _simpleShaderProgram = glCreateProgram();
    glAttachShader(_simpleShaderProgram, _simpleVertexShader);
    glAttachShader(_simpleShaderProgram, _simpleFragmentShader);
    glLinkProgram(_simpleShaderProgram);

    GLint linkSuccess;
    glGetProgramiv(_simpleShaderProgram, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(_simpleShaderProgram, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"%@", messageString);
        exit(1);
    }

    glUseProgram(_simpleShaderProgram);

    _positionSlot = glGetAttribLocation(_simpleShaderProgram, "Position");
    _colorSlot = glGetAttribLocation(_simpleShaderProgram, "SourceColor");
    glEnableVertexAttribArray(_positionSlot);
    glEnableVertexAttribArray(_colorSlot);

    _projectionUniform = glGetUniformLocation(_simpleShaderProgram, "Projection");
    _modelViewUniform = glGetUniformLocation(_simpleShaderProgram, "Modelview");

    _texCoordSlot = glGetAttribLocation(_simpleShaderProgram, "TexCoordIn");
    glEnableVertexAttribArray(_texCoordSlot);
    _textureUniform = glGetUniformLocation(_simpleShaderProgram, "Texture");


    // Texture shader
    _textureShaderProgram = glCreateProgram();
    glAttachShader(_textureShaderProgram, _textureVertexShader);
    glAttachShader(_textureShaderProgram, _simpleFragmentShader);
    glLinkProgram(_textureShaderProgram);

    glGetProgramiv(_textureShaderProgram, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(_textureShaderProgram, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"%@", messageString);
        exit(1);
    }

    glUseProgram(_textureShaderProgram);

    _texturePositionSlot = glGetAttribLocation(_textureShaderProgram, "texPosition");
    _textureColorSlot = glGetAttribLocation(_textureShaderProgram, "texSourceColor");
    glEnableVertexAttribArray(_texturePositionSlot);
    glEnableVertexAttribArray(_textureColorSlot);

    _textureTexCoordSlot = glGetAttribLocation(_textureShaderProgram, "texTexCoordIn");
    glEnableVertexAttribArray(_textureTexCoordSlot);
    _textureTextureUniform = glGetUniformLocation(_textureShaderProgram, "Texture");
}

- (void)setupVBOs {
    glGenBuffers(1, &_cubeVertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _cubeVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), cubeVertices, GL_STATIC_DRAW);

    glGenBuffers(1, &_cubeIndexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _cubeIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndices), cubeIndices, GL_STATIC_DRAW);

    glGenBuffers(1, &_viewportSizedQuadVertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _viewportSizedQuadVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(viewPortQuadVertices), viewPortQuadVertices, GL_STATIC_DRAW);

    glGenBuffers(1, &_viewportSizedQuadIndexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _viewportSizedQuadIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(viewPortQuadIndices), viewPortQuadIndices, GL_STATIC_DRAW);
}

- (GLuint)setupTexture:(NSString *)fileName {
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        exit(1);
    }

    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);

    GLubyte * spriteData = (GLubyte *) calloc(width*height*4, sizeof(GLubyte));

    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);

    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);

    CGContextRelease(spriteContext);

    GLuint texName;
    glGenTextures(1, &texName);
    glBindTexture(GL_TEXTURE_2D, texName);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);

    free(spriteData);
    return texName;
}

- (void)render:(CADisplayLink*)displayLink {

    glBindFramebuffer(GL_FRAMEBUFFER, _FBO);
    //glBindRenderbuffer(GL_RENDERBUFFER, _FBODepthBuffer);

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    glClearColor(255.0/255.0, 255.0/255.0, 255.0/255.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    glViewport(0, 0, self.frame.size.width, self.frame.size.height);

    glUseProgram(_simpleShaderProgram);

    // Cube draw in FBO
    CC3GLMatrix *projection = [CC3GLMatrix matrix];
    float h = 4.0f * self.frame.size.height / self.frame.size.width;
    [projection populateFromFrustumLeft:-2 andRight:2 andBottom:-h/2 andTop:h/2 andNear:4 andFar:1000];
    glUniformMatrix4fv(_projectionUniform, 1, 0, projection.glMatrix);

    CC3GLMatrix *modelView = [CC3GLMatrix matrix];
    [modelView populateFromTranslation:CC3VectorMake(sin(CACurrentMediaTime()), 0, -7)];
    _cubeCurrentRotation += displayLink.duration * 90;
    [modelView rotateBy:CC3VectorMake(_cubeCurrentRotation, _cubeCurrentRotation, 0)];
    glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix);

    glBindBuffer(GL_ARRAY_BUFFER, _cubeVertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _cubeIndexBuffer);

    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));

    glVertexAttribPointer(_texCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 7));

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _cubeTexture);
    glUniform1i(_textureUniform, 0);

    glDrawElements(GL_TRIANGLES, sizeof(cubeIndices)/sizeof(cubeIndices[0]), GL_UNSIGNED_BYTE, 0);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // ---------------------------------------------------------------
    // FBO -> window

    glBindFramebuffer(GL_FRAMEBUFFER, _viewFrameBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    glClearColor(0.0/255.0, 0.0/255.0, 0.0/255.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    glViewport(0, 0, self.frame.size.width, self.frame.size.height);

    glUseProgram(_textureShaderProgram);

    glBindBuffer(GL_ARRAY_BUFFER, _viewportSizedQuadVertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _viewportSizedQuadIndexBuffer);

    glVertexAttribPointer(_texturePositionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    glVertexAttribPointer(_textureColorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));

    glVertexAttribPointer(_textureTexCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 7));

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _FBOTexture);
    glUniform1i(_textureTextureUniform, 0);
    glDrawElements(GL_TRIANGLES, sizeof(viewPortQuadIndices)/sizeof(viewPortQuadIndices[0]), GL_UNSIGNED_BYTE, 0);

    [_context presentRenderbuffer:GL_RENDERBUFFER];

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);
}

@end

SimpleVertex.glsl :

attribute vec4 Position; 
attribute vec4 SourceColor; 

varying vec4 DestinationColor; 

uniform mat4 Projection;
uniform mat4 Modelview;

attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;

void main(void) { 
    DestinationColor = SourceColor; 
    gl_Position = Projection * Modelview * Position;
    TexCoordOut = TexCoordIn;
}

TextureVertex.glsl :

attribute vec4 texPosition;
attribute vec4 texSourceColor;

varying vec4 DestinationColor;

attribute vec2 texTexCoordIn;
varying vec2 TexCoordOut;

void main(void) { 
    DestinationColor = texSourceColor;
    gl_Position = texPosition;
    TexCoordOut = texTexCoordIn;
}

SimpleFragment.glsl :

varying lowp vec4 DestinationColor;

varying lowp vec2 TexCoordOut;
uniform sampler2D Texture;

void main(void) {
    gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut);
}

Solution

  • Ok I solved it by correcting the view port and not binding the depth buffer in the render loop, thanks to @FelixK !