Simple texture onto quad [OpenGL ES 2.0 on iOS SDK 5.1]

Hi,

I’m having a bit of a hard time mapping a simple texture onto a quad using a fragment shader in OpenGL ES 2.0. It’s currently just rendering completely black. When I replace my fragment shader with ‘gl_FragColor=vec4(v_texCoord.xy,0.0,1.0)’, it renders a black/green/red/yellow gradient of colours just as expected.

Could somebody please just have a quick look through my View file and my shaders and let me know where I’m going wrong? I’d really really appreciate it!


//
//  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 _colorRenderBuffer;
    GLuint _positionSlot;
    GLuint _colorSlot;
    GLuint _modelViewUniform;
    GLuint _depthRenderBuffer;
    
    GLuint _floorTexture;
    GLuint _fishTexture;
    GLuint _texCoordSlot;
    GLuint _textureUniform;
}

@end



//
//  OpenGLView.m
//

#import "OpenGLView.h"

@implementation OpenGLView

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

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

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

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

- (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 {    
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);   
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);
}

- (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 {
    
    GLuint vertexShader = [self compileShader:@"SimpleVertex" withType:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShader:@"SimpleFragment" withType:GL_FRAGMENT_SHADER];
    
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    glLinkProgram(programHandle);
    
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"%@", messageString);
        exit(1);
    }
    
    glUseProgram(programHandle);
    
    _positionSlot = glGetAttribLocation(programHandle, "Position");
    _colorSlot = glGetAttribLocation(programHandle, "SourceColor");
    glEnableVertexAttribArray(_positionSlot);
    glEnableVertexAttribArray(_colorSlot);
    
    _modelViewUniform = glGetUniformLocation(programHandle, "Modelview");
    
    _texCoordSlot = glGetAttribLocation(programHandle, "TexCoordIn");
    glEnableVertexAttribArray(_texCoordSlot);
    _textureUniform = glGetUniformLocation(programHandle, "Texture");
}

- (void)setupVBOs {
    
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
    
    GLuint indexBuffer;
    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
    
}

- (void)render {
    glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    
    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, _floorTexture);
    glUniform1i(_textureUniform, 0);    

    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), 
                   GL_UNSIGNED_BYTE, 0);    
    
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}


- (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_NEAREST); 
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, spriteData);
    
    free(spriteData);   
    
    return texName;    
}


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {        
        [self setupLayer];        
        [self setupContext];    
        [self setupDepthBuffer];
        [self setupRenderBuffer];        
        [self setupFrameBuffer];     
        [self compileShaders];
        [self setupVBOs];
        [self render];        
    }
    _floorTexture = [self setupTexture:@"test.jpg"];
    return self;
}

@end



//
//  SimpleVertex.glsl
//

attribute vec4 Position;

attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;

void main(void) { // 4
    gl_Position = Position;
    TexCoordOut = TexCoordIn;
}



//
//  SimpleFragment.glsl
//

varying lowp vec2 TexCoordOut;
uniform sampler2D Texture;

void main(void) { // 2
    gl_FragColor = texture2D(Texture, TexCoordOut);
}

Just as a reminder: This isn’t the OpenGL ES forum!

I didn’t find anything wrong with the way you handle all the stuff that’s necessary except for one thing:

You define your vertex attributes as follows:



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


Clearly, the position of a vertex is defined by a 3-component float array. IIRC you also set the size for the VertexAttribPointer to 3 but in the vertex shader, you take a vec4 as the position.

I don’t think that mismatch is a good idea. In your case I’m not sure what really happens but if the GL defaults to 0.0 on the fourth component of the position vector, then you’d assign a vec4(x,y,z,0.0) to gl_Position which would in turn result in a divide by 0 on perspective divide. So in the general case this shouldn’t work. However, I’d like to have so confirmation or rejection from the others on this matter.

[QUOTE=thokra;1238681]
Clearly, the position of a vertex is defined by a 3-component float array. IIRC you also set the size for the VertexAttribPointer to 3 but in the vertex shader, you take a vec4 as the position.

I don’t think that mismatch is a good idea. In your case I’m not sure what really happens but if the GL defaults to 0.0 on the fourth component of the position vector, then you’d assign a vec4(x,y,z,0.0) to gl_Position which would in turn result in a divide by 0 on perspective divide.[/QUOTE]

See the OpenGL ES specs section 2.7. y and z default to 0.0 while w defaults to 1.0 in case not all 4 components are set explicitly (doesn’t prevent the implementation to be buggy…).

leftblank said that drawing the texturecoordinates works fine so the problem seems to be in fact to be the texture. If you try to sample from an incomplete texture, you will get black from your texture()/texture2D() call. In most cases it is not providing MipMaps while filtering with MipMaps is the default in GL, but here that does not seem to be the problem.

Hi,

Apologies for posting into this forum, I didn’t realise there was a separate one for OpenGL ES 2.0. Thanks both of your for your help, it looks like the problem was indeed with the texture - it is now showing on my quad - but seems awfully pixellated, and very very dark for some reason. It’s a 32-bit TIFF image so I would have expected the image to look really sharp with no loss of quality?

I am now trying to blend together multiple textures in one render pass by passing multiple textures to the shader (snippet 1) and adding the following code to render (snippet 2). However, when I attempt to pick them up in the fragment shader so I can blend them, I can only ever render whichever texture is in GL_TEXTURE0. Am I taking the wrong approach here?


    _textureBackground = glGetUniformLocation(programHandle, "TextureBackground");
    _textureLayer1 = glGetUniformLocation(programHandle, "TextureLayer1");
    _textureLayer2 = glGetUniformLocation(programHandle, "TextureLayer2"); 


    glActiveTexture(GL_TEXTURE0); 
    glBindTexture(GL_TEXTURE_2D, _bgTexture);
    glUniform1i(_textureBackground, 0); 

    glActiveTexture(GL_TEXTURE1); 
    glBindTexture(GL_TEXTURE_2D, _layer1Texture);
    glUniform1i(_textureLayer1, 0);  
    
    glActiveTexture(GL_TEXTURE2); 
    glBindTexture(GL_TEXTURE_2D, _layer2Texture);
    glUniform1i(_textureLayer2, 0); 


// SimpleVertex.glsl

attribute vec4 Position;
attribute vec4 SourceColor; 

varying vec4 DestinationColor; 

attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;

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


// SimpleFragment.glsl

varying lowp vec2 TexCoordOut;
varying lowp vec4 DestinationColor;

uniform sampler2D TextureBackground;
uniform sampler2D TextureLayer1;
uniform sampler2D TextureLayer2;

void main(void) {
    gl_FragColor = DestinationColor *  texture2D(TextureLayer1, TexCoordOut);
    // if i replace TextureLayer1 with TextureBackground, it will display that image as it is in GLTEXTURE_0
}


glActiveTexture(GL_TEXTURE0);      
glBindTexture(GL_TEXTURE_2D, _bgTexture);
glUniform1i(_textureBackground, 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, _layer1Texture);
glUniform1i(_textureLayer1, 0);

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, _layer2Texture);     
glUniform1i(_textureLayer2, 0);

With that, you’re telling every sampler to use texture unit 0 while you have three textures bound to units TEXTURE0 - TEXTURE2. A call to glUniform needs to reflect the actual texture unit, i.e. 0, 1 and 2 in your case.

Example: A sampler2D sampling a texture bound to the target GL_TEXTURE_2D on unit GL_TEXTURE4 needs to be set to unit 4 by calling

 glUniform1i(yourSampler2DLocation, 4); 

Oh thank you! Slowly trying to piece this all together, many thanks for your help :slight_smile: