Search code examples
opengl-estexture2dopengl-es-3.0

How to load and display an image in OpenGL ES 3.0 using C++


I'm trying to make a simple app on Android Studio using the NDK, JNI to call C++ code that load and display an image. I have managed to create the surface and draw a simple Triangle.

Now, I'm looking for a way to load and display an image in OpenGL ES 3.0 using C++. I have done the search around but all of them is either too complex to me or outdated or written in Java. It would be great if someone could guide me with a simple example.


Solution

  • After working around, I finally make it. Below is my C++ source code. I use stb_image library to load the image.

    /*
     * Copyright (C) 2009 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    // OpenGL ES 3.0 code
    
    #include <jni.h>
    #include <android/log.h>
    
    #include <GLES3/gl3.h>
    #include <GLES3/gl3ext.h>
    #include <GLES3/gl3ext.h>
    
    #include <string>
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include "stb_image.h"
    
    #define  LOG_TAG    "libgl2jni"
    #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    
    GLuint mTexture, VAO;// Create reference Id for the texture
    GLuint gProgram;
    GLuint gvPositionHandle;
    
    static void printGLString(const char *name, GLenum s) {
        const char *v = (const char *) glGetString(s);
        LOGI("GL %s = %s\n", name, v);
    }
    
    static void checkGlError(const char* op) {
        for (GLint error = glGetError(); error; error
                = glGetError()) {
            LOGI("after %s() glError (0x%x)\n", op, error);
        }
    }
    
    auto gVertexShader =
    "#version 300 es\n"
    "layout (location=0) in vec3 position;\n"
    "layout (location=1) in vec3 color;\n"
    "layout (location=2) in vec2 texCoord;\n"
    
    "out vec3 ourColor;\n"
    "out vec2 TexCoord;\n"
    
    "void main()\n"
    "{\n"
        "gl_Position = vec4(position,1.0f); // Add the xOffset to the x position of the vertex position\n"
        "ourColor = color;\n"
        "TexCoord= vec2(texCoord.x,1.0f-texCoord.y);\n"
    "}";
    
    auto gFragmentShader =
    
    "#version 300 es\n"
    "in vec3 ourColor;\n"
    "in vec2 TexCoord;\n"
    
    "out vec4 color;\n"
    
    "uniform sampler2D ourTexture;\n"
    
    
    "void main()\n"
    "{\n"
        "color = texture(ourTexture , TexCoord);\n"
    "}\n";
    
    GLuint loadShader(GLenum shaderType, const char* pSource) {
        GLuint shader = glCreateShader(shaderType);
        if (shader) {
            glShaderSource(shader, 1, &pSource, NULL);
            glCompileShader(shader);
            GLint compiled = 0;
            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
            if (!compiled) {
                GLint infoLen = 0;
                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
                if (infoLen) {
                    char* buf = (char*) malloc(infoLen);
                    if (buf) {
                        glGetShaderInfoLog(shader, infoLen, NULL, buf);
                        LOGE("Could not compile shader %d:\n%s\n",
                                shaderType, buf);
                        free(buf);
                    }
                    glDeleteShader(shader);
                    shader = 0;
                }
            }
        }
        return shader;
    }
    
    GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) { // Load and Bind Fragments to Progam and link program then return result
        GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
        if (!vertexShader) {
            return 0;
        }
    
        GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
        if (!pixelShader) {
            return 0;
        }
    
        GLuint program = glCreateProgram();
        if (program) {
            glAttachShader(program, vertexShader);
            checkGlError("glAttachShader");
            glAttachShader(program, pixelShader);
            checkGlError("glAttachShader");
            glLinkProgram(program);
            GLint linkStatus = GL_FALSE;
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
            if (linkStatus != GL_TRUE) {
                GLint bufLength = 0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                if (bufLength) {
                    char* buf = (char*) malloc(bufLength);
                    if (buf) {
                        glGetProgramInfoLog(program, bufLength, NULL, buf);
                        LOGE("Could not link program:\n%s\n", buf);
                        free(buf);
                    }
                }
                glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }
    
    
    
    bool setupGraphics(int w, int h) {
        printGLString("Version", GL_VERSION);
        printGLString("Vendor", GL_VENDOR);
        printGLString("Renderer", GL_RENDERER);
        printGLString("Extensions", GL_EXTENSIONS);
    
        LOGI("setupGraphics(%d, %d)", w, h);
        gProgram = createProgram(gVertexShader, gFragmentShader);
        if (!gProgram) {
            LOGE("Could not create program.");
            return false;
        }
        gvPositionHandle = glGetAttribLocation(gProgram, "position");
        checkGlError("glGetAttribLocation");
        LOGI("glGetAttribLocation(\"position\") = %d\n",
                gvPositionHandle);
    
        glViewport(0, 0, w, h);
        checkGlError("glViewport");
        return true;
    }
    
    GLfloat recVertices[] = {
            // Positions          // Colors          // Texture Coords
            0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,    1.0f, 1.0f,   // Top Right
            0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,    1.0f, 0.0f,   // Bottom Right
            -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // Bottom Left
            -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // Top Left
    };
    
    GLuint indices[] = {  // Note that we start from 0!
            0, 1, 3, // First Triangle
            1, 2, 3  // Second Triangle
    };
    void initBuffers()
    {
    
        GLuint VBOs[2], EBO; // Initialize an buffer to store all the verticles and transfer them to the GPU
        glGenVertexArrays (1,&VAO); // Generate VAO
        glGenBuffers(1, VBOs); // Generate VBO
        glGenBuffers(1, &EBO); // Generate EBO
        glBindVertexArray (VAO);// Bind the Vertex Array
    
        glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);//Bind verticles array for OpenGL to use
        glBufferData(GL_ARRAY_BUFFER, sizeof(recVertices), recVertices, GL_STATIC_DRAW);
    
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//Bind the indices for information about drawing sequence
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
        // 1. set the vertex attributes pointers
        // Position Attribute
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
        // Color Attribute
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
        //Texture Coordinate Attribute
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
        glEnableVertexAttribArray(2);
    
        glBindVertexArray(0);//3. Unbind VAO
    
    }
    
    void generateTexture()
    {
    
        glGenTextures(1 , &mTexture);
        glBindTexture(GL_TEXTURE_2D, mTexture);// Bind our 2D texture so that following set up will be applied
    
        //Set texture wrapping parameter
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_MIRRORED_REPEAT);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_MIRRORED_REPEAT);
    
        //Set texture Filtering parameter
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
    
        //Load the image
        int picWidth,picHeight,n;
        unsigned char* image = stbi_load("/storage/emulated/sdcard/Lighthouse.jpg", &picWidth, &picHeight, &n,0);
        if (image == NULL ) {
            LOGI("Failed to load image: %s", stbi_failure_reason());
        }
        //Generate the image
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB , picWidth , picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
        glGenerateMipmap(GL_TEXTURE_2D);
    
        stbi_image_free(image);// Free the reference to the image
        glBindTexture(GL_TEXTURE_2D,0); //Unbind 2D textures
    
    }
    void renderFrame() {
        static float grey;
        grey += 0.01f;
        if (grey > 1.0f) {
            grey = 0.0f;
        }
    
        generateTexture();
        glClearColor(grey+0.05f, grey-0.03f, grey+0.02f, grey-0.04f);
        checkGlError("glClearColor");
        glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
        checkGlError("glClear");
    
        glUseProgram(gProgram);
        checkGlError("glUseProgram");
    
        /*glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, recVertices);
        checkGlError("glVertexAttribPointer");
        glEnableVertexAttribArray(gvPositionHandle);
        checkGlError("glEnableVertexAttribArray");
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        checkGlError("glDrawArrays");*/
        glActiveTexture(GL_TEXTURE0);
        checkGlError("glActiveTexture");
        glBindTexture(GL_TEXTURE_2D,mTexture);
        checkGlError("glBindTexture");
        GLint mlocation = glGetUniformLocation(gProgram,"ourTexture");
        checkGlError("glGetUniformLocation");
        glUniform1i(mlocation,0);
        checkGlError("glUniform1i");
        initBuffers();
        glBindVertexArray(VAO);
        checkGlError("glBindVertexArray");
        glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
    
    }
    
    extern "C" {
        JNIEXPORT void JNICALL Java_com_android_gles3jni_GLES3JNILib_init(JNIEnv * env, jobject obj,  jint width, jint height);
        JNIEXPORT void JNICALL Java_com_android_gles3jni_GLES3JNILib_step(JNIEnv * env, jobject obj);
    };
    
    JNIEXPORT void JNICALL Java_com_android_gles3jni_GLES3JNILib_init(JNIEnv * env, jobject obj,  jint width, jint height)
    {
        setupGraphics(width, height);
    }
    
    JNIEXPORT void JNICALL Java_com_android_gles3jni_GLES3JNILib_step(JNIEnv * env, jobject obj)
    {
    
        renderFrame();
    }