Android 为例编写一个 OpenGL ES 3.0 实例,Native & Java 两种实现

18 篇文章 22 订阅

一、简介

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

二、Native 实现

1. 头文件

由于我们使用的是 OpenGL ES 3.0,所以主要使用此头文件:<GLES3/gl3.h>

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 <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 == 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 <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 == 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

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: OpenGLES 3.0是一个用于移动设备的图形渲染API,可以用来渲染3D图形和动画。要实现视频数据离屏渲染,可以按照以下步骤: 1.创建一个FBO(Frame Buffer Object)。FBO是一个特殊的OpenGL对象,它可以将渲染结果存储到一个纹理或者渲染缓冲区中。 2.创建一个纹理对象,用来存储渲染结果。将纹理对象作为FBO的颜色附件绑定到FBO上。 3.将FBO绑定到OpenGL上下文中。 4.创建一个渲染程序(Program),用于将视频数据渲染到FBO中。该渲染程序应该包含一个顶点着色器(Vertex Shader)和一个片元着色器(Fragment Shader)。 5.将视频数据上传到纹理对象中。 6.设置顶点和纹理坐标,将视频数据渲染到FBO中。 7.将FBO从OpenGL上下文中解绑。 8.从纹理对象中获取渲染结果数据。 9.销毁FBO、纹理对象和渲染程序。 以上步骤需要使用OpenGL ES 3.0的相关函数和常量进行操作,具体实现可以参考OpenGL ES 3.0的相关文档和示代码。 ### 回答2: OpenGLESOpenGL for Embedded Systems)是一种针对嵌入式系统的2D和3D图形API。OpenGLES 3.0是OpenGLES的一个版本,它引入了更多的功能和更高的性能,以满足现代移动设备和嵌入式系统对图形渲染的需求。 要实现为视频数据进行离屏渲染,首先需要获取视频数据。可以通过各种方式获取视频数据,如从文件中读取、网络传输等等。获取到视频数据后,就可以进行渲染操作了。 接下来,需要创建一个离屏渲染的环境。在OpenGLES中,可以通过创建一个离屏帧缓冲对象(FBO)来实现离屏渲染。离屏帧缓冲对象可以将渲染结果存储在纹理或渲染缓冲中。 创建离屏帧缓冲对象后,需要将其绑定为当前的渲染目标。这样,所有的渲染操作将会在离屏帧缓冲对象中进行,而不是直接渲染到屏幕上。可以使用glBindFramebuffer函数来绑定离屏帧缓冲对象。 一旦离屏帧缓冲对象被绑定,就可以通过OpenGL ES的渲染管线来进行渲染操作了。可以使用顶点缓冲对象、着色器程序和纹理等来设置渲染的属性和效果。可以使用glDrawArrays或glDrawElements函数来执行渲染操作。 渲染完成后,可以将离屏帧缓冲对象中的渲染结果存储在纹理中,或者将其拷贝到其他地方,如内存或文件中。可以使用glFramebufferTexture2D或glReadPixels函数来实现这些操作。 最后,需要清除和释放离屏渲染所使用的资源,如离屏帧缓冲对象、纹理和顶点缓冲对象等。可以使用glDeleteFramebuffers、glDeleteTextures和glDeleteBuffers函数来释放相应的资源。 通过以上步骤,就可以使用OpenGLES 3.0实现为视频数据进行离屏渲染。这样可以将渲染结果用于后续的处理、保存或显示等用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值