0
votes

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?

2
Your fragment shader does not compile for me. Don't you mean gl_FragColor = destinationColor + texture2D(tex, texCoordOut);Reigertje
oops, yes that's that I mean. I've updated OPHamish
When do you expect the blending happens?Reigertje
well, before the texture is drawn onHamish
But you draw the same vertices with colors twice. These colors all have an alpha component of 1. So the addition destinationColor + texture2D(tex, texCoordOut); you do in the fragment shader will never result in an alpha value below 1.Reigertje

2 Answers

1
votes

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.

0
votes

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;
}