OpenGL ES 2.0 When sampling texture on a fullscreen quad weird edge is being rendered

113 views Asked by At

I’m struggling with rendering a simple overlay image on top of an already rendered frame. The frame is rendered by a mapbox library which I modified by calling my rendering code just after mapbox renders the frame. I don't know whether the fact that I’m rendering on top of an already rendered frame by mapbox library is important, but maybe it is, so I’m mentioning it here.

The problem seems somewhat related to texturing. The image I try to render is a checkerboard with red and transparent squares. The image bitmap does not have any edges. The image is visible but the edges are also coloured red which is not what I’ve intended and I've already spent a couple of days trying to figure out the reason why these edges are being rendered. I’ll be more than grateful if someone could enlighten me on what I’m doing wrong.

checkerboard with edges. Notice that one edge fades out at the top

namespace mbgl {
    void MapOverlayRenderer::arm() { // called when rendering surface has changed
        releaseLog("MOR a");

        program = createProgram();
        vertexBuffer = createVertexBuffer();
        indexBuffer = createIndexBuffer();

        glGenTextures(1, &texture);

        fillTexture();
    }

    void MapOverlayRenderer::render() { // called just after mapbox frame has rendered
        if (!_image.valid()) {
            return;
        }

        glUseProgram(program);

        auto positionHandle = glGetAttribLocation(program, "vertexData");

        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);

        glEnableVertexAttribArray(positionHandle);

        GLint numberOfValuesPerVertex = 4;
        GLsizei stride = numberOfValuesPerVertex * sizeof(GLfloat);

        glVertexAttribPointer(positionHandle, numberOfValuesPerVertex, GL_FLOAT, false, stride, nullptr);

        auto imagePositionHandle = glGetUniformLocation(program, "imagePosition");

        GLfloat imagePosition[] = {_leftPadding, _topPadding, _rightPadding, _bottomPadding};

        glUniform4fv(imagePositionHandle, 1, imagePosition);

        auto imageTextureHandle = glGetUniformLocation(program, "imageTexture");

        glUniform1i(imageTextureHandle, 0);

        glActiveTexture(GL_TEXTURE0);

        glBindTexture(GL_TEXTURE_2D, texture);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_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);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

        auto numberOfVertices = 6;

        glDrawElements(GL_TRIANGLES, numberOfVertices, GL_UNSIGNED_INT, nullptr);

        glDisableVertexAttribArray(positionHandle);
    }

    void MapOverlayRenderer::setImage(float leftPadding, float topPadding, float rightPadding,
                                      float bottomPadding, PremultipliedImage &&image) { // is called when I pass new image to be rendered
        this->_image = std::move(image);

        this->_leftPadding = leftPadding;
        this->_topPadding = topPadding;
        this->_rightPadding = rightPadding;
        this->_bottomPadding = bottomPadding;

        fillTexture();
    }

    void MapOverlayRenderer::removeImage() {
        this->_leftPadding = 0.0f;
        this->_topPadding = 0.0f;
        this->_rightPadding = 0.0f;
        this->_bottomPadding = 0.0f;

        this->_image = PremultipliedImage();
    }

    GLuint MapOverlayRenderer::createVertexBuffer() {
        GLuint result;

        glGenBuffers(1, &result);

        std::array<GLfloat, 16u> vertices{
                -1.0f, -1.0f, 0.0f, 0.0f,
                1.0f, -1.0f, 1.0f, 0.0f,
                -1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 1.0f, 1.0f
        };

        glBindBuffer(GL_ARRAY_BUFFER, result);

        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW);

        return result;
    }

    GLuint MapOverlayRenderer::createIndexBuffer() {
        GLuint result;

        glGenBuffers(1, &result);

        std::array<GLuint, 6u> indices{
                0u, 1u, 2u,
                2u, 1u, 3u
        };

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, result);

        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices, GL_STATIC_DRAW);

        return result;
    }

    GLuint MapOverlayRenderer::createVertexShader() {
        auto vertexShaderCode = R"(
            precision highp float;
            precision highp int;
            precision highp sampler2D;
            attribute vec4 vertexData;

            varying vec2 fullscreenTextureCoordinates;

            void main() {
               fullscreenTextureCoordinates = vertexData.zw;

               gl_Position = vec4(vertexData.xy, 0.0, 1.0);
            })";

        auto shader = loadShader(GL_VERTEX_SHADER, &vertexShaderCode);

        return shader;
    }

    GLuint MapOverlayRenderer::createFragmentShader() {
        auto fragmentShaderCode = R"(
            precision highp float;
            precision highp int;
            precision highp sampler2D;
            varying vec2 fullscreenTextureCoordinates;

            uniform vec4 imagePosition;
            uniform sampler2D imageTexture;

            void main() {
                float left = imagePosition[0];
                float top = imagePosition[1];
                float right = imagePosition[2];
                float bottom = imagePosition[3];

                if (fullscreenTextureCoordinates[0] < left ||
                    fullscreenTextureCoordinates[0] > right ||
                    fullscreenTextureCoordinates[1] > 1.0 - top ||
                    fullscreenTextureCoordinates[1] < 1.0 - bottom) {
                    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
                } else {
                    float imageHorizontalCoordinate = (fullscreenTextureCoordinates[0] - left) / (right - left);
                    float imageVerticalCoordinate = 1.0 - (fullscreenTextureCoordinates[1] - top) / (bottom - top);

                    vec2 imageTextureCoordinates = vec2(imageHorizontalCoordinate, imageVerticalCoordinate);

                    gl_FragColor = texture2D(imageTexture, imageTextureCoordinates);
                }
            })";

        auto shader = loadShader(GL_FRAGMENT_SHADER, &fragmentShaderCode);

        return shader;
    }

    GLuint MapOverlayRenderer::createProgram() {
        auto vertexShader = createVertexShader();
        auto fragmentShader = createFragmentShader();

        auto result = glCreateProgram();

        glAttachShader(result, vertexShader);
        glAttachShader(result, fragmentShader);

        glLinkProgram(result);

        return result;
    }

    GLuint MapOverlayRenderer::loadShader(GLenum type, GLchar const *const *shaderCode) {
        auto shader = glCreateShader(type);

        glShaderSource(shader, 1, shaderCode, nullptr);

        glCompileShader(shader);

        return shader;
    }

    void MapOverlayRenderer::fillTexture() {
        if (!_image.valid()) {
            return;
        }

        glBindTexture(GL_TEXTURE_2D, texture);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast<GLsizei>(_image.size.width), static_cast<GLsizei>(_image.size.height), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, _image.data.get());

        glGenerateMipmap(GL_TEXTURE_2D);
    }
}
1

There are 1 answers

1
Thomas On

The default texture wrapping mode is set to wrap around to the other side of the texture. If any filtering is being done, this will end up sampling pixels from the opposite side of the texture, which explains the thin red border.

To change it to clamping, use this in fillTexture after binding the texture:

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);