Android鬼点子-通过Google官方示例学NDK(3)

1,703

如果你看遍了网上那些只是在C++里面输出一个 ‘ helloWorld ’ 的NDK教程的话,可以看看本系列的文章,本系列是通过NDK的运用的例子来学习NDK。这是本系列的第三篇,这是一个opengl的例子。

本文的代码在这里!建议下载到本地阅读。

如果对这方面感兴趣,可以看看前两篇。

Android鬼点子-通过Google官方示例学NDK(1)——主要说的是如何在NDK使用多线程,还有就是基础的java与c++的相互调用。

Android鬼点子-通过Google官方示例学NDK(2)——主要是说的不使用java代码,用c++写一个activity。

Android鬼点子-通过Google官方示例学NDK(4)——主要是说的视频解码相关的内容。

首先运行一下,效果是这样的,一个“原谅色”的是三角形,背景色会在白色到黑色之间变换。:

图1

代码结构:

图2
Activity里面放了一个GL2JNIView。GL2JNIView是GLSurfaceView的一个子类。

GL2JNIView的重点是设置一个Renderer,在Renderer中调用了C++实现。

private static class Renderer implements GLSurfaceView.Renderer {
       public void onDrawFrame(GL10 gl) {
           GL2JNILib.step();
       }

       public void onSurfaceChanged(GL10 gl, int width, int height) {
           GL2JNILib.init(width, height);
       }

       public void onSurfaceCreated(GL10 gl, EGLConfig config) {
           // Do nothing.
       }
   }

onSurfaceCreated是在创建的时候调用,onSurfaceChanged是在尺寸变化的时候调用,onDrawFrame是在绘制每一帧的时候调用。

GL2JNILib.step();GL2JNILib.init(width, height);都是JNI实现的。直接进入gl_code.cpp中看实现。 首先看看如何初始化:

JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_init(JNIEnv * env, jobject obj,  jint width, jint height)
{
   setupGraphics(width, height);
}
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, "vPosition");//获取着色器程序中,指定为attribute类型变量的id
   checkGlError("glGetAttribLocation");
   LOGI("glGetAttribLocation(\"vPosition\") = %d\n",
           gvPositionHandle);
   //设置可见区域:左下(0,0)开始 区域的 宽 高
   glViewport(0, 0, w, h);
   checkGlError("glViewport");
   return true;
}

这里主要是设置的要执行的OpenGLShading Language (GLSL)代码createProgram(gVertexShader, gFragmentShader),和取到传入参数的句柄glGetAttribLocation(gProgram, "vPosition")

先看看createProgram方法,关键的地方加了注释:

GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) {
   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;
       // 获取program的链接情况
       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获取连接错误
                   glGetProgramInfoLog(program, bufLength, NULL, buf);
                   LOGE("Could not link program:\n%s\n", buf);
                   free(buf);
               }
           }
           glDeleteProgram(program);
           program = 0;
       }
   }
   return program;
}

这里调用了一个工具方法loadShader,用来载入代码,加了注释:

//工具方法
GLuint loadShader(GLenum shaderType, const char* pSource) {
   // 创建一个vertex shader类型(GLES20.GL_VERTEX_SHADER,顶点shader)
   // 或fragment shader类型(GLES20.GL_FRAGMENT_SHADER,片元shader)
   GLuint shader = glCreateShader(shaderType);
   if (shader) {
       // 将源码添加到shader并编译之
       glShaderSource(shader, 1, &pSource, NULL);
       glCompileShader(shader);
       GLint compiled = 0;
       // 获取Shader的编译情况
       glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
       if (!compiled) {
           GLint infoLen = 0;
           glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
           if (infoLen) { //如果编译失败显示错误日志并删除此shader
               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;
}

被载入的代码:

//auto 自动推断类型 OpenGLShading Language (GLSL)代码,必须在使用前编译
//vec4(四维向量) 坐标点 P = (wx, wy, wz, w) 的 w值都是1,也必须是1
auto gVertexShader =
   "attribute vec4 vPosition;\n"// 应用程序传入顶点着色器的顶点位置
   "void main() {\n"
   "  gl_Position = vPosition;\n"
   "}\n";

auto gFragmentShader =
   "precision mediump float;\n" // 设置工作精度
   "void main() {\n"
   "  gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n" // 进行纹理采样 r:0 g:1 b:0 a:1
   "}\n";

这两句就是设置了顶点的变量和颜色。顶点变量gl_Position会被外部设置的vPosition所赋值。而vPosition又是通过gvPositionHandle =glGetAttribLocation(gProgram, "vPosition")方法在外部取到句柄。

到此初始化结束。开始绘制每一帧。

JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_step(JNIEnv * env, jobject obj)
{
    renderFrame();
}
void renderFrame() {
    static float grey;
    grey += 0.01f;
    if (grey > 1.0f) {
        grey = 0.0f;
    }
    //设置背景色 ,“底色”
    //红、绿、蓝和 alpha 值,指定值范围均为[ 0.0f,1.0f ]
    glClearColor(grey, grey, grey, 1.0f);
    checkGlError("glClearColor");
    //用来清除屏幕颜色,即将屏幕的所有像素点都还原为 “底色”,但屏蔽参数的操作
    //GL_COLOR_BUFFER_BIT	指定当前被激活为写操作的颜色缓存
    //GL_DEPTH_BUFFER_BIT	指定深度缓存
    glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    checkGlError("glClear");
    //将program加入OpenGL ES环境中(使用shader程序)
    glUseProgram(gProgram);
    checkGlError("glUseProgram");

    //指定要修改的顶点着色器中顶点变量id
    //2:指定每个顶点属性的组件数量 2个一组
    //GL_FLOAT:每个组件的数据类型
    //固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)
    //指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0
    //顶点的缓冲数据
    glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices);    // 顶点坐标传递到顶点着色器
    checkGlError("glVertexAttribPointer");
    // 允许使用顶点坐标数组,这里一共传入6个数字,2个一组,一共3组
    glEnableVertexAttribArray(gvPositionHandle);
    checkGlError("glEnableVertexAttribArray");
    // 图形绘制 绘制三角形。第一个点的索引是0 ,共3个点 详细:http://blog.sina.com.cn/s/blog_4119bd830100rvip.html
    glDrawArrays(GL_TRIANGLES , 0, 3);
    checkGlError("glDrawArrays");
}

由于glClearColor(grey, grey, grey, 1.0f);grey的值的变化,背景色会从白色到黑色渐变。

然后给初始化时取到的句柄赋值glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices);这里的gTriangleVertices就是顶点的坐标:

//屏幕中心是(0,0)
const GLfloat gTriangleVertices[] = { 0.0f, 0.5f, -0.5f, -0.5f,
        0.5f, -0.5f };

glEnableVertexAttribArray(gvPositionHandle);允许使用顶点坐标数组,这里一共传入6个数字,2个一组,一共3组。

glDrawArrays(GL_TRIANGLES , 0, 3);图形绘制,绘制三角形。第一个点的索引是0 ,共3个点

到此,一个三角形就会被绘制出来。

可以修改auto gFragmentShader = "precision mediump float;\n" // 设置工作精度 "void main() {\n" " gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n" // 进行纹理采样 r:0 g:1 b:0 a:1 "}\n";中rgb的值,来修改三角形的颜色。