Hamish
Hamish

Reputation: 80781

Open GL ES transparent texture not blending

I am trying to draw a texture with transparency onto a background gradient, created by a vertex shader that interpolates colors between vertices. However, only the opaque parts of texture are being drawn.

I am using the blending function:

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

Rendering code:

struct vertex {
    float position[3];
    float color[4];
    float texCoord[2];
};

typedef struct vertex vertex;

const vertex vertices[] = {
    {{1, -1, 0}, {0, 167.0/255.0, 253.0/255.0, 1}, {1, 0}}, // BR (0)
    {{1, 1, 0}, {0, 222.0/255.0, 1.0, 1}, {1, 1}}, // TR (1)
    {{-1, 1, 0}, {0, 222.0/255.0, 1.0, 1}, {0, 1}}, // TL (2)
    {{-1, -1, 0}, {0, 167.0/255.0, 253.0/255.0, 1}, {0, 0}}, // BL (3)
};

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

-(void) render {

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

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), 0);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)(sizeof(float)*3));
    glVertexAttribPointer(textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)(sizeof(float)*7));

    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indicies)/sizeof(indicies[0]), GL_UNSIGNED_BYTE, 0);

    // Not sure if required for blending to work..
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(textureUniform, 0);

    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), 0);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)(sizeof(float)*3));
    glVertexAttribPointer(textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)(sizeof(float)*7));


    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indicies)/sizeof(indicies[0]), GL_UNSIGNED_BYTE, 0);


    [context presentRenderbuffer:GL_RENDERBUFFER];
}

I'm unsure whether I need to do two lots of drawing to the render buffer in order for the blend function to work, so currently I am drawing without the texture binded, then with it binded.

Fragment Shader:

varying lowp vec4 destinationColor;

varying lowp vec2 texCoordOut;
uniform sampler2D tex;

void main() {
    lowp vec4 tex2D = texture2D(tex, texCoordOut);
    gl_FragColor = vec4(tex2D.rgb+destinationColor.rgb, tex2D.a*destinationColor.a);
}

Vertex Shader:

attribute vec4 position;

attribute vec4 sourceColor;
varying vec4 destinationColor;

attribute vec2 texCoordIn;
varying vec2 texCoordOut;


void main() {
    destinationColor = sourceColor;
    gl_Position = position;
    texCoordOut = texCoordIn;
}

Texture loading code:

-(GLuint) loadTextureFromImage:(UIImage*)image {

    CGImageRef textureImage = image.CGImage;

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

    GLubyte* spriteData = (GLubyte*) malloc(width*height*4);

    CGColorSpaceRef cs = CGImageGetColorSpace(textureImage);
    CGContextRef c = CGBitmapContextCreate(spriteData, width, height, 8, width*4, cs, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(cs);

    CGContextScaleCTM(c, 1, -1);
    CGContextTranslateCTM(c, 0, -CGContextGetClipBoundingBox(c).size.height);

    CGContextDrawImage(c, (CGRect){CGPointZero, {width, height}}, textureImage);
    CGContextRelease(c);

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

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

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

    glBindTexture(GL_TEXTURE_2D, 0);

    free(spriteData);

    return glTex;
}

Any ideas what I am doing wrong?

Upvotes: 0

Views: 104

Answers (2)

Reigertje
Reigertje

Reputation: 725

The answer you gave yourself might suffice in your particular situation, but I don't think it is a very nice solution. You will probably run into many problems when you want to render more than two objects.

You draw the same object twice. First without a texture bound and then with the texture bound - with the blending done in the shader. But how would you do that with a third object?

I recommend using a different set of vertices for both objects. Something like this:

const vertex gradient_background[] = {
    {{1, -1, 0}, {0, 167.0/255.0, 253.0/255.0, 1}, {1, 0}},
    {{1, 1, 0}, {0, 222.0/255.0, 1.0, 1}, {1, 1}}, 
    {{-1, 1, 0}, {0, 222.0/255.0, 1.0, 1}, {0, 1}}, 
    {{-1, -1, 0}, {0, 167.0/255.0, 253.0/255.0, 1}, {0, 0}}
};

const vertex textured_object[] = {
    {{1, -1, 0}, {0, 0, 0, 0}, {1, 0}},
    {{1, 1, 0}, {0, 0, 0, 0}, {1, 1}},
    {{-1, 1, 0}, {0, 0, 0, 0}, {0, 1}}, 
    {{-1, -1, 0}, {0, 0, 0, 0}, {0, 0}}
};

And adjust your render function appropriately, also unbind texture to 0 after drawing.

-(void) render {

    ...

    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(gradient_background), 0);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(gradient_background), (GLvoid*)(sizeof(float)*3));
    glVertexAttribPointer(textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(gradient_background), (GLvoid*)(sizeof(float)*7));

    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indicies)/sizeof(indicies[0]), GL_UNSIGNED_BYTE, 0);

    ...

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(textureUniform, 0);

    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(textured_object), 0);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(textured_object), (GLvoid*)(sizeof(float)*3));
    glVertexAttribPointer(textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(textured_object), (GLvoid*)(sizeof(float)*7));


    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indicies)/sizeof(indicies[0]), GL_UNSIGNED_BYTE, 0);

    // Don't forget to unbind texture for next draw
    glBindTexture(GL_TEXTURE_2D, 0);

    ...
}

Fragment shader

varying lowp vec4 destinationColor;

varying lowp vec2 texCoordOut;
uniform sampler2D tex;

void main() {
    lowp vec4 tex2D = texture2D(tex, texCoordOut); // Returns (0, 0, 0, 1) when texture 0 is bound
    gl_FragColor = destinationColor + tex2D;
}

Then use

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

Or any other blending function you wish.

Upvotes: 1

Hamish
Hamish

Reputation: 80781

Okay, well I feel silly. Attempting to use the blending function outside the fragment shader didn't do anything as the texture was already drawn. I just needed to use the equivalent inside the fragment shader:

varying lowp vec4 destinationColor;

varying lowp vec2 texCoordOut;
uniform sampler2D tex;

void main() {
    lowp vec4 tex2D = texture2D(tex, texCoordOut);    
    lowp vec4 result = tex2D + vec4(1.0 - tex2D.a) * destinationColor;

    gl_FragColor = result;
}

Upvotes: 0

Related Questions