一、概述

  案例:使用opengles+egl渲染一张图片

  关键类介绍:

    1.新建一个DrawPictureActivity.java用于充当显示容器:初始化SurfaceView并设置SurfaceView的callback回调函数。并在其onSurfaceCreated函数中对DrawPicture对象进行初始化。

    2.新建DrawPicture.java用于java层和native层进行通讯

    3.新建picture_controller.cpp用于和DrawPicture.java沟通java传递给jni层的数据。并在其init方法中创建ANativeWindow、提取Bitmap中的像素数组,初始化C++类Picture类

    4.新建picture.cpp类,此类为整个渲染过程提供:OpenGL ES上下文环境、显示设备EGLSurface、GPU程序(shader)、OpenGL ES程序

  渲染步骤:

    1.DrawPictureActivity.java中在其SurfaceHolder.addCallback的回调函数中初始化DrawPicture.java类,并调用DrawPicture对象的initBitmap方法将客户端的bitmap传递给jni层

    2.在jni层的(picture_controller.cpp)initBitmap方法中,根据传递过来的Surface对ANativeWindow进行创建,根据传递过来的Bitmap对其中的像素进行提取。

    3.实例化C++的Picture并调用其init方法,在init方法中会初始化OpenGL ES上下文环境以及显示设备EGLSurface,并调用eglMakeCurrent来为当前线程绑定上下文及显示设备。

    3.调用C++的Picture实例中的renderPicture方法,通过此方法,a.创建一个纹理id、绑定纹理id、设置纹理的过滤方式及平铺方式、并将从第二步中提取出来的像素数组上传到这个纹理上。b.创建shader 。c.创建opengles程序 。d.给opengles着色器传递坐标数据(顶点坐标、纹理坐标)。e.调用glDrawArray方法开始绘制纹理 f.调用eglSwapBuffers方法将back frame buffer 和front frame buffer交互,将图像显示到用户屏幕上。

      

二、关键代码

  1.DrawPictureActivity.java:初始化SurfaceView并得到SurfaceHolder,然后给SurfaceHolder设置回调函数

surfaceView = findViewById(R.id.pictureSurfaceView);

        holder = surfaceView.getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                drawPicture = new DrawPicture();
                parseBitmap();
                Log.e("surfaceCreated", "" + pixels.length);
//                drawPicture.initByte(bitmapWidth, bitmapHeight, bitmap2RGB(bitmap), holder.getSurface());
                drawPicture.initBitmap(bitmap,holder.getSurface());
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
                drawPicture.onSurfaceChanged(width,height);

            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

            }
        });

  2.DrawPicture.java:和jni沟通的类

public native void initBitmap(Bitmap bitmap,Surface surface);
    /**
     * 当surface发生改变的时候更新width、height
     *
     * @param mWidth  宽度
     * @param mHeight 高度
     */
    public native void onSurfaceChanged(int mWidth, int mHeight);

  3.picture_controller.cpp :提取bitmap像素数组、初始化ANativeWindow、初始化Picture C++类

  

extern "C"
JNIEXPORT void JNICALL
Java_com_yw_ywmediaplayer_activity_nativeinterface_DrawPicture_initBitmap(JNIEnv *env, jobject thiz,
                                                                          jobject bitmap,
                                                                          jobject surface) {
    void* pixels  =NULL;
    AndroidBitmapInfo bitmapInfo;
    int ret = AndroidBitmap_getInfo(env,bitmap,&bitmapInfo);
    if(ret<0){
        LOGE("get bitmap info error");
        return;
    }
    LOGE("bitmapInfo:width=[%d],height=[%d]",bitmapInfo.width,bitmapInfo.height);
    if(bitmapInfo.format!=ANDROID_BITMAP_FORMAT_RGBA_8888){
        LOGE("not support RGBA8888");
        return;
    }

    ret = AndroidBitmap_lockPixels(env,bitmap,&pixels);
    if(ret<0){
        LOGE("get bitmap pixels fail");
        return;
    }

    _window = ANativeWindow_fromSurface(env, surface);
    LOGE("实例化Picture");
    picture = new Picture(bitmapInfo.width, bitmapInfo.height, pixels, _window);
    LOGE("picture->init()");
    picture->init();



    AndroidBitmap_unlockPixels(env,bitmap);

}

  3.Picture C++类

    a.初始化OpenGL ES上下文环境及显示设备   

/**
 * 初始化egl并创建eglsurface
 */
void Picture::init() {
    eglCore = new EGLCore();
    eglCore->init();
    eglSurface = eglCore->createWindowSurface(_window);
    //给当前线程绑定上下文环境及显示设备
    eglCore->makeCurrent(eglSurface);
}

  b.设置窗口大小及清除各种缓冲区

void Picture::initViewPort() {
    //设置窗口大小
    glViewport(0, 0, screenWidth, screenHeight);
    LOGE("screenWidth=[%d],screenHeight=[%d]",screenWidth,screenHeight);
    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    //reset xoffset yoffset
    resizeTexture();
}

  c.创建纹理id、绑定纹理id、设置纹理过滤方式及纹理的重复映射和简约映射方式、将像素数组上传到纹理上

/**创建纹理*/
    //创建并绑定纹理
    LOGE("glGenTextures");
    GLuint textureId = 0;
    glGenTextures(1, &textureId);
    LOGE("glBindTexture");
    //此处创建纹理以后需要绑定纹理id,并给纹理设置过滤方式及映射方式,不然会黑屏
    glBindTexture(GL_TEXTURE_2D,
                  textureId);//在opengles操作过程中必须告诉opengles操作的是哪个纹理,所以要调用操作opengles提供的绑定纹理的方法绑定纹理

    /**设置纹理参数*/
    //设置纹理的过滤方式(双线性过滤):当纹理对象(可以理解为一张图片)被渲染到物体表面上的时候(实际上是OpenGL绘制管线将纹理的元素映射到OpenGL生成的片段上的时候),有可能要被放大或者缩小,而当其放大或者缩小的时候,具体应该如何确定每个像素是如何被填充的,就由开发者配置的纹理对象的纹理过滤器来指明
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    //设置纹理映射过程中用到的重复映射和简约映射规则:将该纹理的s轴和t轴的坐标设置为GL_CLAMP_TO_EDGE类型,因为纹理坐标可以超出(0,1)的范围,而按照上述设置规则,所有大于1的纹理值都要设置为1,所有小于0的值都要置为0。
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    /**将RGBA表示的橡树数组上传到纹理上*/
    //将RGBA数组表示的像素内容上传到纹理上
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmapWidth, bitmapHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 (uint8_t *) pixels);

  d.创建opengles可执行程序及着色器

/**创建opengles显卡可执行程序*/
    //创建opengles程序
    LOGE("glCreateProgram");
    GLuint programId = glCreateProgram();
    GLuint vertexShader = createShader(GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
    GLuint framgentShader = createShader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);
    LOGE("glAttachShader");
    glAttachShader(programId, vertexShader);
    glAttachShader(programId, framgentShader);
    LOGE("glLinkProgram");
    glLinkProgram(programId);
    GLint status;
    glGetProgramiv(programId, GL_LINK_STATUS, &status);
    if (status == GL_FALSE) {
        GLint bufLength = 0;
        glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &bufLength);
        if (bufLength) {
            char *buf = (char *) malloc(bufLength);
            if (buf) {
                glGetProgramInfoLog(programId, bufLength, NULL, buf);
                LOGI("Could not link program:\n%s\n", buf);
                free(buf);
            }
        }
        glDeleteProgram(programId);
        programId = 0;
        return -1;
    }
    GLint mPositionHandle = glGetAttribLocation(programId, "position");
    GLint mTextureHandle = glGetAttribLocation(programId, "texcoord");
    GLint mUniformSampler = glGetUniformLocation(programId, "yuvTexSampler");
    LOGE("glUseProgram");
    glUseProgram(programId);
/**
 * 创建shader
 * @param shaderType shader类型
 * @param source shader程序字符串
 * @return shader
 */
GLuint Picture::createShader(GLenum shaderType, const char *source) {
    GLint status;
    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &source, NULL);
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (!status) {
        GLint infoLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen) {
            char *buf = (char *) malloc(infoLen);
            if (buf) {
                glGetShaderInfoLog(shader, infoLen, NULL, buf);
                LOGI("Could not compile shader %d:\n%s\n", shaderType, buf);
                free(buf);
            }
        } else {
            LOGI("Guessing at GL_INFO_LOG_LENGTH size\n");
            char *buf = (char *) malloc(0x1000);
            if (buf) {
                glGetShaderInfoLog(shader, 0x1000, NULL, buf);
                LOGI("Could not compile shader %d:\n%s\n", shaderType, buf);
                free(buf);
            }
        }
        glDeleteShader(shader);
        shader = 0;
    }
    return shader;
}

  e.设置顶点坐标、纹理坐标、激活纹理并使用纹理

LOGE("glGetAttribLocation");
    //设置顶点坐标
    glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, 0, 0, VERTEXS_POINTS);
    glEnableVertexAttribArray(mPositionHandle);


    //设置纹理坐标
    LOGE("glGetAttribLocation");
    GLfloat TEXTURE_POINTS[] = {
            xOffset, yOffset,
            xOffset, 1.0f - yOffset,
            1.0f - xOffset, yOffset,
            1.0f - xOffset, 1.0f - yOffset
    };
    glVertexAttribPointer(mTextureHandle, 2, GL_FLOAT, 0, 0, TEXTURE_POINTS);
    glEnableVertexAttribArray(mTextureHandle);

    glActiveTexture(GL_TEXTURE0);
//    glBindTexture(GL_TEXTURE_2D, textureId);
    glUniform1i(mUniformSampler, 0);
    checkGlError("glUniform1i");

  f.开始绘制

//开始绘制
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

  g.将back frame buffer和front frame buffer进行交换,并将图像显示到屏幕上

LOGE("将front frame buffer 与back frame buffer交换并显示到屏幕上");
    eglCore->swapBuffers(eglSurface);

 

  h.到此结束,运行后就能显示出图片了。

  

三、示例图片

android 渲染3D车模 安卓图形渲染_java

 

 

android 渲染3D车模 安卓图形渲染_java_02