一、简介

  • 通过这个 Sample,你将了解到 Android 中是怎么使用 OpenGL ES
  • 通过绘制一个简单的静态三角形,来简单入门和了解它大致的流程(类似于 HelloWorld 工程)
  • 介绍使用 Native 层Java 层 两种方式来分别实现
  • 本文暂不介绍具体的语法,但会给比较详细的注释和解释,帮助你理解
  • 如果你还不了解 OpenGL ES 3.0 的渲染管线流程,建议你先了解一下。
    传送门:OpenGL ES 3.0 渲染管线介绍

二、Native 实现

1. 头文件

由于我们使用的是 OpenGL ES 3.0,所以主要使用此头文件:

2. Activity

最终还是要显示在 Activity 上的,所以我们先准备这样一个 Activity,它直接使用 GLSurfaceView 作为 contentView。

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 #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 == 0) { // 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 0;    }    return shader;}GLuint CreateProgram(const char *vertexSource, const char *fragmentSource) {    // 1. load shader    GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertexSource);    if (vertexShader == 0) {        LOGE("load vertex shader failed! ");        return 0;    }    GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fragmentSource);    if (vertexShader == 0) {        LOGE("load fragment shader failed! ");        return 0;    }    // 2. create gl program    GLuint program = glCreateProgram();    if (program == 0) {        LOGE("create program failed! ");        return 0;    }    // 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 == 0) { // 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 0;    }    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 #include #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 eslayout(location = 0) in vec4 vPosition;void main() {    gl_Position = vPosition;}

fragment.fsh

同样第一行是声明使用的版本,然后绘制的颜色直接使用红色,即 RGBA (range: 0.0 ~ 1.0)

#version 300 esprecision 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 == 0) {            Log.e(TAG, "create shared failed! type: " + type);            return 0;        }        // 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] == 0) { // compile failed            Log.e(TAG, "Error compiling shader. type: " + type + ":");            Log.e(TAG, GLES30.glGetShaderInfoLog(shader));            GLES30.glDeleteShader(shader); // delete shader            return 0;        }        return shader;    }    public static int createProgram(String vertexSource, String fragmentSource) {        // 1. load shader        int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource);        if (vertexShader == 0) {            Log.e(TAG, "load vertex shader failed! ");            return 0;        }        int fragmentShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource);        if (fragmentShader == 0) {            Log.e(TAG, "load fragment shader failed! ");            return 0;        }        // 2. create gl program        int program = GLES30.glCreateProgram();        if (program == 0) {            Log.e(TAG, "create program failed! ");            return 0;        }        // 3. attach shader        GLES30.glAttachShader(program, vertexShader);        GLES30.glAttachShader(program, fragmentShader);        // we can delete shader after attach        GLES30.glDeleteShader(vertexShader);        GLES30.glDeleteShader(fragmentShader);        // 4. link program        GLES30.glLinkProgram(program);        // 5. check link status        int[] linkStatus = new int[1];        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0);        if (linkStatus[0] == 0) { // link failed            Log.e(TAG, "Error link program: ");            Log.e(TAG, GLES30.glGetProgramInfoLog(program));            GLES30.glDeleteProgram(program); // delete program            return 0;        }        return program;    }    public static String loadFromAssets(String fileName, Resources resources) {        String result = null;        try {            InputStream is = resources.getAssets().open(fileName);            int length = is.available();            byte[] data = new byte[length];            is.read(data);            is.close();            result = new String(data, "UTF-8");            result.replace("\\r\\n", "\\n");        } catch (IOException e) {            e.printStackTrace();        }        return result;    }}

4. vertex.vsh 和 fragment.fsh

同 Native 实现。

5. 运行效果

同 Native 运行效果。

四、总结

  • Native 实现方式和 Java 实现方式原理都是一样的,包括方法名都基本是一样的
  • 部分未给出的代码,详细参见工程中的代码

五、完整工程地址

https://github.com/afei-cn/OpenGLSample

更多相关文章

  1. android -- sim/usim卡导联系人
  2. android绘制播放音频的波形图
  3. Android(安卓)热修复以及阿里AndFix方案使用
  4. android fragment新手简单应用(实现界面之间的跳转)
  5. Android(安卓)自定义View金额、价格样式显示MoneyView
  6. Android实现HID鼠标的指针自定义
  7. Android中MVP模式讲解及实践
  8. Android(安卓)ListView同一个item显示2列的实现方法(仿2列商品列
  9. Android(安卓)UI卡顿面试知识小结

随机推荐

  1. 2011.06.21——— android GridView的拖
  2. 实现Android(安卓)滑动退出Activity的功
  3. ImageView的属性android:scaleType,即Imag
  4. Android去掉状态栏和标题栏的两种方式
  5. Android(安卓)studio 工具中的“Attach D
  6. Android中的颜色设置
  7. Android平台开发-Android(安卓)keypad ma
  8. 【代码】android 通过GPS获取用户地理位
  9. android ndk 编译 libcurl
  10. Android访问WCF服务(使用json实现参数传