前言
像正真做這種關(guān)于opengl 的開發(fā),一般建議都是直接使用原生代碼,而不是使用android已經(jīng)封裝好的opengl代碼捡絮, 對于像opengl這么偏底層的圖形接口是很有必要去深入到native層去一趟究竟的暖混。
寫作本文的目的也只是與大家一起去學(xué)習(xí)與交流,還是那句話(talk is cheap参淫,show me the code)救湖,如果不想聽我在這里瞎bb,盡可將我已經(jīng)寫好的DEMO copy下來涎才,然后運行起來鞋既,像看看效果, 這比說一大堆廢話管用耍铜。此代碼主要參考了oculus mobile sdk, 我只是將其中一部分代碼裁剪出來邑闺,再自己加以修改,來作為案例學(xué)習(xí)之用棕兼,僅供大家參考陡舅。
環(huán)境
-
構(gòu)建環(huán)境
簡單說說我的開發(fā)環(huán)境,首先NDK開發(fā)工具包必不可少伴挚,目前還是采取通用的Android.mk 與 Application.mk 這樣的Mikefile 文件進行構(gòu)建靶衍,這種方式網(wǎng)上有大量教程,建議去google developer 去查閱用法, ** 有一點需要注意下**茎芋,在Application.mk 中的NDK_TOOLCHAIN_VERSION
的版本是隨你的ndk版本而定的颅眶,比如我ndk版本是r10e,這里用的toolchan版本是4.8田弥,如果你用的其它ndk版本涛酗,那么你需要改一下toolchan的版本號。 google的新的ndk demo中,已經(jīng)采取直接將ndk的配置寫在gradle腳本里面來代替現(xiàn)在這種傳統(tǒng)的配置了煤杀。
-
繪制環(huán)境
我這里是通過調(diào)用opengl es api函數(shù)向java層提供的GLSurfaceView輸出并渲染圖像眷蜈。也可以使用NDK提供的原生window api 去進行渲染圖像。
- 1 執(zhí)行下面步驟以在原生代碼使用opengl es 2.0/3.0 的API:
- 修改Android.mk構(gòu)建文件沈自。添加
LOCAL_LDLIBS += -lGLESv2 -lGLESv3
- 代碼中包含opengl es頭文件酌儒。
#include<GLES3/gl3.h> #include<GLES2/gl2ext.h>
, 我是用的gles 3.0的庫,因為2.0 是沒有我需要用到的glGenVertexArrays
API的枯途, 不過繪制視頻需要binding使用gl2ext庫提供的GL_TEXTURE_EXTERNAL_OES 作為圖像映射的紋理目標(biāo)
忌怎。有的人可能會問,為什么不是一般常用的GL_TEXTURE_2D
呢酪夷? 其實原因在于SurfaceTexture榴啸,我查了下SurfaceTexture源碼, 發(fā)現(xiàn)它內(nèi)部使用的是eglCreateImageKHR
去創(chuàng)建的image晚岭, 而這個api必須使用TEXTURE_EXTERNAL_OES
鸥印,并且用了TEXTURE_EXTERNAL_OES
就不能使用TexImage2D等系列接口去生成紋理image,所以說坦报,SurfaceTexture類的功能是起到替代opengl 的TexImageXX接口的作用库说, 因此視頻圖像的捕獲與圖像更新還是用的java層封裝好的SurfaceTexture
, 那么這就涉及到了naive代碼與java代碼的交互片择。
- 2 Native代碼與Java的交互
一個簡單的步驟:
2.1 通過隨意一個JNIEnv指針去獲取一個JavaVM在jni層的一個指針潜的,注意,一個應(yīng)用代表一個javaVM字管。但JNIEnv指針在不同的線程中有不同JNIEnv
JavaVM *gJavaVM; //// 通常是設(shè)置的一個全局的對象 env->GetJavaVM(&gJavaVM);
2.2 獲取JavaVM在opengl的代表JNIEnv指針啰挪。由于我們需要在opengl 繪制線程 的上下文 中與javaVM交互,因此JNIEnv指針的獲取必須在opengl上下文中嘲叔。
具體代碼:
JNIEnv *AttachJava()
{
JavaVMAttachArgs args = {
JNI_VERSION_1_4, // 表示jni版本號亡呵,還有1_1,1_2, 1_6這幾種
0, /* NULL or name of thread as modified UTF-8 str */
0 /* global ref of a ThreadGroup object, or NULL */
};
JNIEnv* jni;
int status = gJavaVM->AttachCurrentThread( &jni, &args);
if (status < 0) {
LOG_ERROR("<SurfaceTexture> faild to attach current thread!");
return NULL;
}
return jni;
}
2.3 在native代碼中,通過jni創(chuàng)建一個運行在javaVM環(huán)境中的SurfaceTexture的實例 jobject
const char *stClassPath = "android/graphics/SurfaceTexture";
const jclass surfaceTextureClass = jni->FindClass(stClassPath);
if (surfaceTextureClass == 0) {
LOG_ERROR("FindClass (%s) failed", stClassPath);
}
// // find the constructor that takes an int
const jmethodID constructor = jni->GetMethodID( surfaceTextureClass, "<init>", "(I)V" );
if (constructor == 0) {
LOG_ERROR("GetMethonID(<init>) failed");
}
jobject obj = jni->NewObject(surfaceTextureClass, constructor, texId);
if (obj == 0) {
LOG_ERROR("NewObject() failed");
}
javaSurfaceTextureObj = jni->NewGlobalRef(obj);
if (javaSurfaceTextureObj == 0) {
LOG_ERROR("NewGlobalRef() failed");
}
//Now that we have a globalRef, we can free the localRef
jni->DeleteLocalRef(obj);
這個過程等價于在java代碼中new 一個SurfaceTexture':surfaceTexture = new SurfaceTexture(textId)
硫戈。其實锰什,現(xiàn)在我們只是將SurfaceTexture的創(chuàng)建工作從Java層搬到了Native層,其余的操作莫過于下面這個幾個動作了:
在native中實現(xiàn) 像在java代碼做的下面這個幾個動作掏愁,實現(xiàn)視頻幀的捕獲與更新:
surfaceTexture .setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
frameAvailable = ture;
}
});
// 在繪制線程循環(huán)中執(zhí)行下面操作
if (frameAvailable) {
surfaceTexture .updateTexImage();
surfaceTexture .getTransformMatrix(textureTransform);
frameAvailable = false;
}
在native中
- 獲取updateTexImage方法的MethodID,
updateTexImageMethodId = jni->GetMethodID( surfaceTextureClass, "updateTexImage", "()V")
- 然后 在需要調(diào)用這個方法時歇由,通過這個MethodID卵牍,找到這個方法果港,使用
jni->CallVoidMethod(javaSurfaceTextureObj, updateTexImageMethodId)
, 從而實現(xiàn)了native調(diào)用java的非靜態(tài)方法。
ok, opengl進行紋理映射綁定的的texture已經(jīng)有了糊昙,那么下面就是圖像的渲染工作了辛掠。
圖形渲染
看過我在DEMO 中的關(guān)于java代碼中使用opengl的步驟可以看出繪制的套路來:
-
創(chuàng)建一個與shader代碼鏈接的shader program 對象。
program = createProgram(vextexShader, fragmentShader);
-
獲取vertex shader代碼中聲明的屬性變量的 handle。
因為頂點帶有傳輸?shù)綀D形管線上的屬性萝衩。要繪制一個對象回挽,我們需要指定它的頂點,以及頂點如何定義平面猩谊。這些屬性包括頂點千劈,法線,紋理坐標(biāo)以及其它屬性牌捷。
positionHandle = glGetAttribLocation(shaderProgram, "aPosition");
textureParamHandle = glGetUniformLocation(shaderProgram, "texture");
-
初始化頂點數(shù)據(jù)墙牌。
(包括頂點坐標(biāo),紋理坐標(biāo)暗甥,以及數(shù)組的一個索引(因為這里使用了glDrawElements
)喜滨。
// 聲明一個結(jié)構(gòu)體,保存頂點數(shù)據(jù)
struct Vertices
{
float positions[4][4];
float texCoords[4][4];
};
// 指定頂點連接順序
unsigned short indices[] = {
0, 1, 2, 0, 2 ,3
};
-
通過這些頂點數(shù)據(jù)來填充vertex shader中的屬性撤防。
這里用到了VBO去緩存頂點數(shù)據(jù)(也就是將主存儲客戶端(CPU端)中保存的頂點數(shù)據(jù)虽风,緩存到GPU中, 提高了GPU渲染效率)寄月, 每一組緩沖區(qū)就是 OpenGL 的頂點數(shù)組對象辜膝,叫做VAO。
glGenVertexArrays( 1, &vao );
glBindVertexArray( vao );
glGenBuffers( 1, &vb );
glBindBuffer( GL_ARRAY_BUFFER, vb );
glBufferData( GL_ARRAY_BUFFER, sizeof( vertices ), &vertices, GL_STATIC_DRAW );
glGenBuffers( 1, &ib ) ;
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ib );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( indices ), indices, GL_STATIC_DRAW );
glEnableVertexAttribArray ( positionHandle );
glVertexAttribPointer ( positionHandle, 3, GL_FLOAT, false, 0, (const GLvoid *)offsetof( Vertices, positions ));
glEnableVertexAttribArray ( textureCoordHandle );
glVertexAttribPointer ( textureCoordHandle, 4, GL_FLOAT, false, 0, (const GLvoid *)offsetof( Vertices, texCoords ));
-
OpenGL 渲染緩沖區(qū)中的數(shù)據(jù)剥懒。
我們可以使用下面的代碼來讓 VAO 包含索引緩沖區(qū):
glUseProgram(program);
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, index.size(), GL_UNSIGNED_INT, NULL);
ok内舟, opengl 繪制圖形的大致套路就是這樣。詳情請參考源碼初橘!
總結(jié)
由于才疏學(xué)淺验游,本文只起到一個拋磚引玉的作用, 簡單的描述了一下native代碼中使用opengl的列子保檐,只在場景中畫出了一個簡單的長方行貼圖來顯示圖像耕蝉。如果 深入一點,做一個球形貼圖夜只,然后再通過一連串的矩陣運算(MVP)垒在,就可以做個全景視頻了。說實話扔亥,在工作中是很難有機會寫到這么原始opengl代碼场躯,我都已經(jīng)很久沒有寫過opengl相關(guān)代碼了,現(xiàn)在基本就是在別人已經(jīng)封裝好的代碼基礎(chǔ)上寫寫業(yè)務(wù)邏輯旅挤,要不是因為一年前寫的幾篇文字踢关,引起這么多人的關(guān)注,可能我都很難再把以前擼過的代碼再重溫一遍來跟大家一起分享粘茄,感謝大家的關(guān)注签舞!