美文网首页Android知识Android技术知识亲好鉴定
NDK开发OpenGL ES 3.0(二)——初见GLES,第一

NDK开发OpenGL ES 3.0(二)——初见GLES,第一

作者: 798fe2ac8685 | 来源:发表于2016-10-25 12:15 被阅读1500次

    标签(空格分隔): OpenGL-ES

    版本:1
    作者:陈小默
    版权声明:禁止商用,禁止转载
    交流群:572027378
    

    该文章仅被发布于作业部落(原)简书


    上一篇:NDK开发OpenGL ES 3.0(一)——OpenGL-ES 3.0介绍以及NDK环境搭建


    参考书目:
    [1]Donald Hearn,M.Pauline Barker.计算机图形学 第四版(蔡士杰 译).北京:电子工业出版社
    [2]Dave Shreiner,Graham Sellers.OpenGL编程指南 第八版(王锐 译).北京:机械工业出版社
    [3]Dan Ginsburg,Budirjanto Purnomo.OpenGL ES 3.0 编程指南 第二版(姚军 译).北京:机械工业出版社

    参考示例:
    [1]OpenGL ES 3.0 编程指南 第二版(姚军 译).北京:机械工业出版社.18~23
    [2]GoogleSamples - Android NDK


    [toc]

    四、第一个三角形


    图4.1-1图4.1-1

    在开始书写程序之前,我们先看一下目录结构是什么样的(如下图):


    图4.1-2图4.1-2

    4.1 TriabgleLib

    该文件是Java文件,其内容如下,不再详细解释。

    /**
     * 陈小默 16/10/24.
     */
    public class TriangleLib {
        static {
            System.loadLibrary("triangle-lib");
        }
        //初始化本地GLES
        public static native boolean init();
        //为本地GLES设置宽和高
        public static native void resize(int width, int height);
        //用来绘制图形
        public static native void step();
    }
    

    4.2 TriangleView

    这里是一个Kotlin文件,我们在Android Studio右键New中可以找到。

    /**
     * 陈小默 16/10/24.
     */
    class TriangleView(context: Context):GLSurfaceView(context) {
    
        init {
            setEGLConfigChooser(8,8,8,0,16,0)
            setEGLContextClientVersion(3)
            setRenderer(TriangleRender())
        }
        class TriangleRender:GLSurfaceView.Renderer{
            override fun onDrawFrame(gl: GL10?) {
                TriangleLib.step()
            }
    
            override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
                TriangleLib.resize(width, height)
            }
    
            override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
                TriangleLib.init()
            }
        }
    }
    

    在这个文件中,我们创建一个View继承GLSurfaceView,然后调用其三个方法用来启用OpenGL,
    如果没有调用setEGLConfigChooser方法,则默认设置颜色模式为RGB_888,色彩深度16位。setEGLContextClientVersion方法指定了EL的版本,这里指定为了3,如果你的手机不支持OpenGL ES 3.0 则需要设置为2。接下来,我们创建了一个内部类TriangleRender,该类继承自GLSurfaceView.Renderer,这是Android SDK 中实现 OpenGL ES的方式,但是这里我们需要借助它的着色器进入本地环境。我们可以从实现中看到,我们调用的TriangleLib中的方法并没有跟这个Render产生任何交互,那么我们为什么还要通过Render去启动我们的本地方法呢?这涉及到了OpenGL ES的实现其实是基于管线的,整个操作过程就像水流一样分为上游、下游等,Render中的各个方法就是水流的不同阶段,我们的native方法是需要在特定的阶段调用的,所以才需要借助Render来标志当前进行到哪一个阶段了。但实际上,我们也可以在本地方法自己判断水流并进行相应的操作。在这里我们尽量不要使用Java中面向对象的思想去思考这些问题,应该将思想转变为面向过程。具体详细我们将会在后续章节中介绍。

    Kotlin小贴士:

    • 跟随类名的括号表示主构造器,如果不提供默认以外的构造方法可以省略括号
    • 冒号表示继承关系
    • init为对象创建时的初始化函数,在构造器之后被调用

    4.3 TriangleActivity

    这个Activity没有别的功能,不解释

    class TriangleActivity : AppCompatActivity() {
        lateinit var triangleView :TriangleView
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            triangleView = TriangleView(this)
            setContentView(triangleView)
        }
    
        override fun onPause() {
            super.onPause()
            triangleView.onPause()
        }
    
        override fun onResume() {
            super.onResume()
            triangleView.onResume()
        }
    }
    

    Kotlin小贴士:

    • var 关键字表示这是一个可读可写的方法集(如果不好理解可以当做Java中实现了Getter/Setter方法的属性),当然还有一个val关键字表示一个可读不可写的方法集(Java中只实现了Getter的属性)。在Kotlin中域被封装到了val/var的内部,对外是不可见的(包括类本身)。
    • lateinit 由于Kotlin中的安全检查机制,当你声明一个属性时,必须对其进行初始化以保证在任何位置访问其值的操作都是安全的,但是这回带来一些不便,于是其提供了此关键字用来说明我现在不想初始化。注意:访问任何未初始化的属性都是非法的。

    4.4 MainActivity

    我们在MainActivity的布局文件中增加一个按钮

        <Button
            android:text="绘制三角形"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/btn_drawTriangle" />
    

    然后在MainActivity中给按钮添加事件监听,使其能够打开TriangleActivity。

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            btn_drawTriangle.setOnClickListener {
                val intent = Intent(this,TriangleActivity::class.java)
                startActivity(intent)
            }
        }
    }
    

    Kotlin小贴士:
    如果你按照上一章的教程进行过配置的话,是可以在Activity中使用布局ID的。另外这里使用了Lambda语法,如果你对Lambda表达式不了解的话,可以查阅Kotlin Lambda 说明Kotlin 函数式接口

    4.5 CMakeList.txt配置

    cmake_minimum_required(VERSION 3.4.1)
    
    #设置编译指令
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-rtti -fno-exceptions -Wall")
    
    #如果当前系统版本过低设置拒绝编译
    if (${ANDROID_PLATFORM_LEVEL} LESS 11)
      message(FATAL_ERROR "OpenGL 2 is not supported before API level 11 (currently using ${ANDROID_PLATFORM_LEVEL}).")
      return()
    elseif (${ANDROID_PLATFORM_LEVEL} LESS 18)
      add_definitions("-DDYNAMIC_ES3")
      set(OPENGL_LIB GLESv2)
    else ()
      set(OPENGL_LIB GLESv3)
    endif (${ANDROID_PLATFORM_LEVEL} LESS 11)
    
    
    # Include libraries needed for Triangle lib
    add_library( triangle-lib
                 SHARED
                 src/main/cpp/triangle-lib.cpp)
    
    target_link_libraries(triangle-lib
                          ${OPENGL_LIB}
                          android
                          EGL
                          log
                          m)
    

    4.6 triangle-lib.cpp

    这里才是重中之重的重点,接下来我们会使用C代码实现一个在屏幕上绘制三角形的程序。在开始之前我们先进行一些说明。首先是着色器,这里的着色器跟Android中的不一样,这里的着色器是一段符合GLSL规范的字符串,其会在程序运行时被编译为GPU(图形处理器-显卡)能够识别的代码,也可以说这是一种独立的编程语言,这些着色器的目标是GPU而不是CPU。这是符合设计规范的,首先GPU是专为图像处理而设计的,其次如果将所有处理交给CPU后果可想而知。所以我们在编写着色器代码的时候可能会对其语法感到困惑,这是正常的,毕竟我们之前写的代码都是面向内存和CPU的。如果你对其中的流程和各种着色器仍有疑问,不用担心,我们会在后续章节中继续学习。接下来是我们的代码部分:

    #include <GLES3/gl3.h>
    
    #include <android/log.h>
    
    #define LOG_TAG "TRIANGLE-LIB"
    #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
    
    #include <jni.h>
    #include <stdlib.h>
    
    static const char VERTEX_SHADER[]=
            "#version 300 es\n"
                    "layout(location = 0) in vec4 vPosition;\n"
                    "void main(){\n"
                    "gl_Position = vPosition;\n"
                    "}\n";
    
    static const char FRAGMENT_SHADER[]=
            "#version 300 es\n"
                    "precision mediump float;\n"
                    "out vec4 fragColor;\n"
                    "void main(){\n"
                    "fragColor = vec4(1.0,0.0,0.0,1.0);\n"
                    "}\n";
    
    static const GLfloat VERTEX[]={
            0.0f,0.5f,0.0f,
            -0.5f,-0.5f,0.0f,
            0.5f,-0.5f,0.0f
    };
    
    bool checkGlError(const char* funcName) {
        GLint err = glGetError();
        if (err != GL_NO_ERROR) {
            ALOGE("GL error after %s(): 0x%08x\n", funcName, err);
            return true;
        }
        return false;
    }
    
    GLuint createShader(GLenum shaderType, const char* src) {
        GLuint shader = glCreateShader(shaderType);
        if (!shader) {
            checkGlError("glCreateShader");
            return 0;
        }
        glShaderSource(shader, 1, &src, NULL);
    
        GLint compiled = GL_FALSE;
        glCompileShader(shader);
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint infoLogLen = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
            if (infoLogLen > 0) {
                GLchar* infoLog = (GLchar*)malloc(infoLogLen);
                if (infoLog) {
                    glGetShaderInfoLog(shader, infoLogLen, NULL, infoLog);
                    ALOGE("Could not compile %s shader:\n%s\n",
                          shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment",
                          infoLog);
                    free(infoLog);
                }
            }
            glDeleteShader(shader);
            return 0;
        }
    
        return shader;
    }
    
    GLuint createProgram(const char* vtxSrc, const char* fragSrc) {
        GLuint vtxShader = 0;
        GLuint fragShader = 0;
        GLuint program = 0;
        GLint linked = GL_FALSE;
    
        vtxShader = createShader(GL_VERTEX_SHADER, vtxSrc);
        if (!vtxShader)
            goto exit;
    
        fragShader = createShader(GL_FRAGMENT_SHADER, fragSrc);
        if (!fragShader)
            goto exit;
    
        program = glCreateProgram();
        if (!program) {
            checkGlError("glCreateProgram");
            goto exit;
        }
        glAttachShader(program, vtxShader);
        glAttachShader(program, fragShader);
    
        glLinkProgram(program);
        glGetProgramiv(program, GL_LINK_STATUS, &linked);
        if (!linked) {
            ALOGE("Could not link program");
            GLint infoLogLen = 0;
            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
            if (infoLogLen) {
                GLchar* infoLog = (GLchar*)malloc(infoLogLen);
                if (infoLog) {
                    glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
                    ALOGE("Could not link program:\n%s\n", infoLog);
                    free(infoLog);
                }
            }
            glDeleteProgram(program);
            program = 0;
        }
    
        exit:
        glDeleteShader(vtxShader);
        glDeleteShader(fragShader);
        return program;
    }
    
    GLuint program;
    
    extern "C"{
    JNIEXPORT jboolean JNICALL Java_com_github_cccxm_gles_model_TriangleLib_init(JNIEnv* env, jobject obj);
    JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_resize(JNIEnv* env, jobject obj, jint width, jint height);
    JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_step(JNIEnv* env, jobject obj);
    }
    
    JNIEXPORT jboolean JNICALL Java_com_github_cccxm_gles_model_TriangleLib_init(JNIEnv* env, jobject obj){
        program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
        if (!program){
            ALOGE("程序创建失败");
            return JNI_FALSE;
        }
        glClearColor(0,0,0,0);
        return JNI_TRUE;
    }
    JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_resize(JNIEnv* env, jobject obj, jint width, jint height){
        glViewport(0, 0, width, height);
        glClear(GL_COLOR_BUFFER_BIT);
    }
    JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_TriangleLib_step(JNIEnv* env, jobject obj){
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(program);
        glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,VERTEX);
        glEnableVertexAttribArray(0);
        glDrawArrays(GL_TRIANGLES,0,3);
    }
    

    到现在为止我们的第一个三角形程序就完成了,现在运行程序就能看到开篇图片上的三角形了。


    下一篇:NDK开发OpenGL ES 3.0(三)——着色器基础

    相关文章

      网友评论

        本文标题:NDK开发OpenGL ES 3.0(二)——初见GLES,第一

        本文链接:https://www.haomeiwen.com/subject/rveruttx.html