Android 為例編寫一個 OpenGL ES 3.0 例項,Native & Java 兩種實現
阿新 • • 發佈:2018-12-06
一、簡介
- 通過這個 Sample,你將瞭解到 Android 中是怎麼使用 OpenGL ES
- 通過繪製一個簡單的靜態三角形,來簡單入門和了解它大致的流程(類似於 HelloWorld 工程)
- 介紹使用
Native 層
和Java 層
兩種方式來分別實現 - 本文暫不介紹具體的語法,但會給比較詳細的註釋和解釋,幫助你理解
二、Native 實現
1. 標頭檔案
由於我們使用的是 OpenGL ES 3.0,所以主要使用吃標頭檔案:<GLES3/gl3.h>
2. Activity
最終還是要顯示在 Activity 上的,所以我們先準備這樣一個 Activity,它直接使用 GLSurfaceView
public class SampleActivity extends AppCompatActivity {
private static final String TAG = "SampleActivity";
public static final String TYPE_NAME = "type";
public static final int TYPE_NATIVE = 0;
public static final int TYPE_JAVA = 1;
private GLSurfaceView mGlSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!checkOpenGLES30()) {
Log.e(TAG, "con't support OpenGL ES 3.0!");
finish();
}
mGlSurfaceView = new GLSurfaceView(this);
mGlSurfaceView. setEGLContextClientVersion(3);
mGlSurfaceView.setRenderer(getRenderer());
mGlSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(mGlSurfaceView);
}
private GLSurfaceView.Renderer getRenderer() {
Intent intent = getIntent();
int type = intent.getIntExtra(TYPE_NAME, TYPE_NATIVE);
Log.d(TAG, "type: " + type);
GLSurfaceView.Renderer renderer;
if (type == TYPE_NATIVE) {
renderer = new NativeRenderer(this);
} else {
renderer = new JavaRenderer(this);
}
return renderer;
}
private boolean checkOpenGLES30() {
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
return (info.reqGlEsVersion >= 0x30000);
}
@Override
protected void onPause() {
mGlSurfaceView.onPause();
super.onPause();
}
@Override
protected void onResume() {
mGlSurfaceView.onResume();
super.onResume();
}
}
3. Renderer
我們先介紹 NativeRenderer
的實現,如下:
public class NativeRenderer implements GLSurfaceView.Renderer {
private Context mContext;
static {
System.loadLibrary("native-renderer");
}
public NativeRenderer(Context context) {
mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
registerAssetManager(mContext.getAssets());
glInit();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glResize(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
glDraw();
}
public native void registerAssetManager(AssetManager assetManager);
public native void glInit();
public native void glResize(int width, int height);
public native void glDraw();
}
主要定義了4個 native
的方法,需要我們在 native 層實現。
4. ShaderUtils
說實現之前,我們先寫一個工具類,負責載入和建立。工具類的作用就是可以重複使用的:
#include "ShaderUtils.h"
#include <stdlib.h>
#include "LogUtils.h"
GLuint LoadShader(GLenum type, const char *shaderSource) {
// 1. create shader
GLuint shader = glCreateShader(type);
if (shader == GL_NONE) {
LOGE("create shader failed! type: %d", type);
return GL_NONE;
}
// 2. load shader source
glShaderSource(shader, 1, &shaderSource, NULL);
// 3. compile shared source
glCompileShader(shader);
// 4. check compile status
GLint compiled;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_NONE) { // compile failed
GLint len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
if (len > 1) {
char *log = static_cast<char *>(malloc(sizeof(char) * len));
glGetShaderInfoLog(shader, len, NULL, log);
LOGE("Error compiling shader: %s", log);
free(log);
}
glDeleteShader(shader); // delete shader
return GL_NONE;
}
return shader;
}
GLuint CreateProgram(const char *vertexSource, const char *fragmentSource) {
// 1. load shader
GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertexSource);
if (vertexShader == GL_NONE) {
LOGE("load vertex shader failed! ");
return GL_NONE;
}
GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fragmentSource);
if (vertexShader == GL_NONE) {
LOGE("load fragment shader failed! ");
return GL_NONE;
}
// 2. create gl program
GLuint program = glCreateProgram();
if (program == GL_NONE) {
LOGE("create program failed! ");
return GL_NONE;
}
// 3. attach shader
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
// we can delete shader after attach
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 4. link program
glLinkProgram(program);
// 5. check link status
GLint linked;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (linked == GL_NONE) { // link failed
GLint len = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
if (len > 1) {
char *log = static_cast<char *>(malloc(sizeof(char) * len));
glGetProgramInfoLog(program, len, NULL, log);
LOGE("Error link program: %s", log);
free(log);
}
glDeleteProgram(program); // delete program
return GL_NONE;
}
return program;
}
char *readAssetFile(const char *filename, AAssetManager *mgr) {
if (mgr == NULL) {
LOGE("pAssetManager is null!");
return NULL;
}
AAsset *pAsset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN);
off_t len = AAsset_getLength(pAsset);
char *pBuffer = (char *) malloc(len + 1);
pBuffer[len] = '\0';
int numByte = AAsset_read(pAsset, pBuffer, len);
LOGD("numByte: %d, len: %d", numByte, len);
AAsset_close(pAsset);
return pBuffer;
}
5. NativeRenderer.cpp
終於到了我們的渲染實現了,主要就是實現之前定義的那幾個 native
方法:
#include "com_afei_openglsample_NativeRenderer.h"
#include <android/asset_manager_jni.h>
#include <GLES3/gl3.h>
#include "LogUtils.h"
#include "ShaderUtils.h"
GLuint g_program;
GLint g_position_handle;
AAssetManager *g_pAssetManager = NULL;
JNIEXPORT void JNICALL Java_com_afei_openglsample_NativeRenderer_glInit
(JNIEnv *env, jobject instance) {
char *vertexShaderSource = readAssetFile("vertex.vsh", g_pAssetManager);
char *fragmentShaderSource = readAssetFile("fragment.fsh", g_pAssetManager);
g_program = CreateProgram(vertexShaderSource, fragmentShaderSource);
if (g_program == GL_NONE) {
LOGE("gl init failed!");
}
// vPosition 是在 'vertex.vsh' 檔案中定義的
GLint g_position_handle =glGetAttribLocation(g_program, "vPosition");
LOGD("g_position_handle: %d", g_position_handle);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 背景顏色設定為黑色 RGBA (range: 0.0 ~ 1.0)
}
JNIEXPORT void JNICALL Java_com_afei_openglsample_NativeRenderer_glResize
(JNIEnv *env, jobject instance, jint width, jint height) {
glViewport(0, 0, width, height); // 設定視距視窗
}
JNIEXPORT void JNICALL Java_com_afei_openglsample_NativeRenderer_glDraw
(JNIEnv *env, jobject instance) {
GLint vertexCount = 3;
// OpenGL的世界座標系是 [-1, -1, 1, 1]
GLfloat vertices[] = {
0.0f, 0.5f, 0.0f, // 第一個點(x, y, z)
-0.5f, -0.5f, 0.0f, // 第二個點(x, y, z)
0.5f, -0.5f, 0.0f // 第三個點(x, y, z)
};
glClear(GL_COLOR_BUFFER_BIT); // clear color buffer
// 1. 選擇使用的程式
glUseProgram(g_program);
// 2. 載入頂點資料
glVertexAttribPointer(g_position_handle, vertexCount, GL_FLOAT, GL_FALSE, 3 * 4, vertices);
glEnableVertexAttribArray(g_position_handle);
// 3. 繪製
glDrawArrays(GL_TRIANGLES, 0, vertexCount);
}
JNIEXPORT void JNICALL Java_com_afei_openglsample_NativeRenderer_registerAssetManager
(JNIEnv *env, jobject instance, jobject assetManager) {
if (assetManager) {
g_pAssetManager = AAssetManager_fromJava(env, assetManager);
} else {
LOGE("assetManager is null!")
}
}
6. CMakeLists.txt
當然,它也是或不可少的,負責編譯我們的 native 動態庫。
cmake_minimum_required(VERSION 3.4.1)
include_directories( ${CMAKE_SOURCE_DIR}/src/main/cpp/inc )
add_library( native-renderer
SHARED
src/main/cpp/src/ShaderUtils.cpp
src/main/cpp/src/com_afei_openglsample_NativeRenderer.cpp )
target_link_libraries( native-renderer
# for 'AAssetManager_fromJava'
android
# for opengl es 3.0 library
GLESv3
# for log library
log )
7. vertex.vsh 和 fragment.fsh
我們將頂點著色器和片元著色器的程式碼放在了 assets
目錄下,實現分別為:
vertex.vsh
第一行是宣告使用的版本,這裡我們只是簡單的將外面的輸入,直接傳給了 gl_Position
#version 300 es
layout(location = 0) in vec4 vPosition;
void main() {
gl_Position = vPosition;
}
fragment.fsh
同樣第一行是宣告使用的版本,然後繪製的顏色直接使用紅色,即 RGBA (range: 0.0 ~ 1.0)
#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );
}
8. 執行效果
三、Java 實現
1. Activity
和 Native 實現的程式碼一樣,唯一的區別是使用 JavaRenderer
類作為渲染。
2. JavaRenderer
程式碼來看其實和 Native 層的極其類似,畢竟只是使用 Java 包了一層。
public class JavaRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "JavaRenderer";
private Context mContext;
private int mProgram;
private int mPositionHandle;
public JavaRenderer(Context context) {
mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
String vertexSource = ShaderUtils.loadFromAssets("vertex.vsh", mContext.getResources());
String fragmentSource = ShaderUtils.loadFromAssets("fragment.fsh", mContext.getResources());
mProgram = ShaderUtils.createProgram(vertexSource, fragmentSource);
// vPosition 是在 'vertex.vsh' 檔案中定義的
mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
Log.d(TAG, "mPositionHandle: " + mPositionHandle);
// 背景顏色設定為黑色 RGBA (range: 0.0 ~ 1.0)
GLES30.glClearColor(0, 0, 0, 1);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 視距區域設定使用 GLSurfaceView 的寬高
GLES30.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
int vertexCount = 3;
// OpenGL的世界座標系是 [-1, -1, 1, 1]
float[] vertices = new float[]{
0.0f, 0.5f, 0, // 第一個點(x, y, z)
-0.5f, -0.5f, 0, // 第二個點(x, y, z)
0.5f, -0.5f, 0 // 第三個點(x, y, z)
};
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); // 一個 float 是四個位元組
vbb.order(ByteOrder.nativeOrder()); // 必須要是 native order
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0); // 這一行不要漏了
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT); // clear color buffer
// 1. 選擇使用的程式
GLES30.glUseProgram(mProgram);
// 2. 載入頂點資料
GLES30.glVertexAttribPointer(mPositionHandle, vertexCount, GLES30.GL_FLOAT, false, 3 * 4, vertexBuffer);
GLES30.glEnableVertexAttribArray(mPositionHandle);
// 3. 繪製
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount);
}
}
3. ShaderUtils
同樣,我們將公共的方法抽離為一個工具類,並且,程式碼也是和 Native 層的極其類似。
public class ShaderUtils {
public static final String TAG = "ShaderUtils";
public static int loadShader(int type, String source) {
// 1. create shader
int shader = GLES30.glCreateShader(type);
if (shader == GLES30.GL_NONE) {
Log.e(TAG, "create shared failed! type: " + type);
return GLES30.GL_NONE;
}
// 2. load shader source
GLES30.glShaderSource(shader, source);
// 3. compile shared source
GLES30.glCompileShader(shader);
// 4. check compile status
int[] compiled = new int[1];
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == GLES30.GL_NONE) { // compile failed
Log.e(TAG, "Error compiling shader. type: " + type + ":");
Log.e(TAG, GLES30.glGetShaderInfoLog(shader));
GLES30.glDeleteShader(shader); // delete shader
shader = GLES30.GL_NONE;
}
return shader;
}
public static int createProgram(String vertexSource, String fragmentSource) {
// 1. load shader
int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == GLES30.GL_NONE) {
Log.e(TAG, "load vertex shader f