Android鬼點子-通過Google官方示例學NDK(3)

我是綠色大米呀發表於2018-01-04

如果你看遍了網上那些只是在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的值,來修改三角形的顏色。

Android鬼點子-通過Google官方示例學NDK(3)

相關文章