美文网首页OpenGLAndroid---视频
Android OpenGL ES(一)-开始描绘一个平面三角形

Android OpenGL ES(一)-开始描绘一个平面三角形

作者: deep_sadness | 来源:发表于2018-05-04 11:47 被阅读336次
    image.png

    关于OpenGL ES Android的介绍这里略过

    OpenGL ES世界的基本元素

    1. 着色器
    2. 坐标系。矩阵
    3. 纹理
      ...

    本文主要涉及的部分是着色器的使用。

    直接开始


    创建GLSurfaceView

    今天的目标是做一个OpenGL ES学习的开端。就是画一个简单的三角形。暂时不考虑坐标系的矩阵变换和纹理等。只需要用顶点着色器简单的来进行描述。
    这一节需要使用和认识的关键类是
    GLSurfaceViewGLSurfaceView.Render
    一句话来描述就是,我们会在GLSurfaceView.Render上进行描绘,在GLSurfaceView中显示出来。

    判断是否支持OpenGL Es2

     /**
         * 判断是否支持es2.0
         *
         * @param context
         * @return
         */
        public static boolean isSupportEs2(Context context) {
            //检查是否支持2.0
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            if (activityManager != null) {
                ConfigurationInfo deviceConfigurationInfo = activityManager.getDeviceConfigurationInfo();
                int reqGlEsVersion = deviceConfigurationInfo.reqGlEsVersion;
                return reqGlEsVersion >= GLES_VERSION_2 || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
                        && (Build.FINGERPRINT.startsWith("generic")
                        || Build.FINGERPRINT.startsWith("unknown")
                        || Build.MODEL.contains("google_sdk")
                        || Build.MODEL.contains("Emulator")
                        || Build.MODEL.contains("Android SDK build for x86")));
            } else {
                return false;
            }
    
        }
    

    创建GLSurfaceView

    接着创建一个GLSurfaceView。并且设置其版本为2。同时设置我们自己的Render

               //创建一个GLSurfaceView
                glSurfaceView = new GLSurfaceView(this);
                glSurfaceView.setEGLContextClientVersion(2);
                //设置自己的Render.Render 内进行图形的绘制
                glSurfaceView.setRenderer(new TriangleShapeRender(this));
                isRenderSet = true;
                setContentView(glSurfaceView);
    

    还需要在Activity对应的生命周期内,来调用我们的GLSurfaceView的方法

     @Override
        protected void onPause() {
            super.onPause();
            if (isRenderSet) {
                glSurfaceView.onPause();
            }
    
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if (isRenderSet) {
                glSurfaceView.onResume();
            }
    
        }
    

    绘制三角形-Render的实现类

    绘制的基础知识


    GLThread

    因为Android中的GLSurfaceView的操作,其实都是在GLThread中进行。所以生命周期方法的回调也都在GLThread线程中。所有OpenGL的操作也都需要在该线程中。

    基础的生命周期方法

    接下来转到Render的实现类里面来。先关注需要实现的生命周期方法。

    /**
     * 注意:
     * 因为我们是使用g20来进行编程,需要要注意导包
     *
     * 主要在Render类内,完成对应的绘制.
     * 对应的生命周期的回调
     * -> onSurfaceCreated
     * -> onSurfaceChanged
     * -> onDrawFrame
     *
     * Created by a2957 on 2018/5/3.
     */
    public class ViewGLRender implements GLSurfaceView.Renderer {
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            //0.简单的给窗口填充一种颜色
            GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
    
            //在创建的时候,去创建这些着色器
    
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            //在窗口改变的时候调用
            GLES20.glViewport(0,0,width,height);
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            //0.glClear()的唯一参数表示需要被清除的缓冲区。当前可写的颜色缓冲
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        }
    }
    

    如上面代码所示,对应的生命周期方法,对应了各个状态

    • onSurfaceCreated
      GLSurfaceView创建的时机。按照惯用思维,在这里进行一些初始化的操作。
    • onSurfaceChanged
      GLSurfaceView改变的时机。如代码所示,初始化GL的ViewPort
    • onDrawFrame
      这个生命周期方法会不断的回调。不断的绘制。

    开始绘制三角形


    着色器代码的套路

    我们需要熟悉编写着色器代码的套路。
    之所以说是套路,因为这些步骤都是类似的。

    1. 编写着色器的glsl

    我们先简单的写一下顶点着色器和片元着色器的代码。这些代码具体是为什么这样写。我们在这里先不关注。我们先熟悉一下流程。
    assets中创建对应的文件。代码内容如下

    • 顶点着色器

      //定义一个attribute aPosition ,类型为vec4。4个方向的向量
      attribute vec4 aPosition;
      
      void main() {
      //这里的gl_Position是OpenGL内置的变量。
      gl_Position = aPosition;
      }
      

      简单的描述一下,就是我们自己定义一个属性aPosition来描述位置。(ps:像是废话)

    • 片元着色器

      //设置片元着色器的精度。这里值要兼容性能和效率。通常都是选择mediump
      precision mediump float;
      
      //定义个常量 uColor
      uniform vec4 uColor;
      void main(){
      //同样。这里的gl_FragColor是内置的变量
      gl_FragColor = uColor;
      }
      

      废话同上。

    2. 在onSurfaceCreated方法内初始化

    注意OpenGL的操作,都必须在GLThread中进行。生命周期方法的回调也都在这个线程中

    • 编译着色器代码,得到代表着色的Id(类似于指针的感觉)

       /**
       * 对ShaderCode进行编译
       *
       * @param type       shader的type
       * @param shaderCode 进行编译的Shader代码
       * @return shaderObjectId
       */
      public static int compileShaderCode(int type, String shaderCode) {
          //得到一个着色器的ID。主要是对ID进行操作
          int shaderObjectId = GLES20.glCreateShader(type);
      
          //如果着色器的id不为0,则表示是可以用的
          if (shaderObjectId != 0) {
              //0.上传代码
              GLES20.glShaderSource(shaderObjectId, shaderCode);
              //1.编译代码.根据刚刚和代码绑定的ShaderObjectId进行编译
              GLES20.glCompileShader(shaderObjectId);
      
              //2.查询编译的状态
              int[] status = new int[1];
              //调用getShaderIv ,传入GL_COMPILE_STATUS进行查询
              GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, status, 0);
      
              if (status[0] == 0) { //等于0。则表示失败
                  //失败的话,需要释放资源,就是删除这个引用
                  GLES20.glDeleteShader(shaderObjectId);
                  Log.w("OpenGL Utils", "compile failed!");
                  return 0;
              }
          }
          //最后都会去返回这个shader的引用id
          return shaderObjectId;
      }
      

      这段基本的流程就是

      根据着色器的类型,创建一个shaderObjectId=>
      使用GLES20将我们的代码和ID进行绑定=>
      编译我们绑定的代码=>
      查询编译的状态。如果失败的话,就需要释放资源。delete=>
      成功返回我们绑定好编译后代码的shaderObjectId
      
    • 创建program,将得到的id绑定上,并链接program

        @Override
      public void onSurfaceCreated(GL10 gl, EGLConfig config) {
          super.onSurfaceCreated(gl, config);
          //0.先从Asset中得到着色器的代码
          String vertexShaderCode = GLESUtils.readAssetShaderCode(context, VERTEX_SHADER_FILE);
          String fragmentShaderCode = GLESUtils.readAssetShaderCode(context, FRAGMENT_SHADER_FILE);
          //1.得到之后,进行编译。得到id
          int vertexShaderObjectId = GLESUtils.compileShaderCode(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
          int fragmentShaderObjectId = GLESUtils.compileShaderCode(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
      
          //3.继续套路。取得到program
          mProgramObjectId = GLES20.glCreateProgram();
          //将shaderId绑定到program当中
          GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
          GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);
          //4.最后,启动GL link program
          GLES20.glLinkProgram(mProgramObjectId);
      }
      

      整体的流程就如上面代码注释的一样。

      创建program=>
      将shaderId绑定到program当中=>
      最后,启动GL link program
      

    这样着色器的套路就基本确定下来了。

    三角形的形状

    上面编写的顶点着色器中,我们定义了aPosition的属性。就相当于我们将在OpenGL中定义了一个存储的点。接下来,我们就会将这个点来存储我们定义的形状信息。来显示出形状。

    • 三角形的坐标系

    OpenGL中的坐标系是从[-1,1]。
    我们先用一组数组的坐标系,来描述我们的三角形

      //顶点的坐标系
      private static float TRIANGLE_COORDS[] = {
              //Order of coordinates: X, Y, Z
              0.5f, 0.5f, 0.0f, // top
              -0.5f, -0.5f, 0.0f, // bottom left
              0.5f, -0.5f, 0.0f   // bottom right
      };
    
    三角形的归一化坐标.png

    我们还会定义一些常量,来帮助我们操作。常量就如注释说明

     //在数组中,一个顶点需要3个来描述其位置,需要3个偏移量
      private static final int COORDS_PER_VERTEX = 3;
      private static final int COORDS_PER_COLOR = 0;
    
      //在数组中,描述一个顶点,总共的顶点需要的偏移量。这里因为只有位置顶点,所以和上面的值一样
      private static final int TOTAL_COMPONENT_COUNT = COORDS_PER_VERTEX+COORDS_PER_COLOR;
      //一个点需要的byte偏移量。
      private static final int STRIDE = TOTAL_COMPONENT_COUNT * Constant.BYTES_PER_FLOAT;
    
    • 给三角形分配内存空间
      需要注意的是,
      调用GLES20的包的方法时,其实就是调用JNI的方法。
      所以分配本地的内存块,将java数据复制到本地内存中,而本地内存可以不受垃圾回收的控制。可以使用nio中的ByteBuffer来创建内存区域。

       /*
            1. 使用nio中的ByteBuffer来创建内存区域。
            2. ByteOrder.nativeOrder()来保证,同一个平台使用相同的顺序
            3. 然后可以通过put方法,将内存复制过去。
      
            因为这里是Float,所以就使用floatBuffer
             */
            mVertexFloatBuffer = ByteBuffer
                    .allocateDirect(TRIANGLE_COORDS.length * Constant.BYTES_PER_FLOAT)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer()
                    .put(TRIANGLE_COORDS);
          //因为是从第一个点开始,就表示三角形的,所以将position移动到0
            mVertexFloatBuffer.position(0);
      
    • 整体上

      1. 通过数组来描述三角形的坐标系。因为我们没有考虑空间转换,所以就不需要进行矩阵变化,暂时就直接使用三角形在OpenGl中的坐标系就可以。
      2. 给定义的数组,分配对应的本地内存的空间。ByteBuffer .allocateDirect方法
    onDrawFrame中调用绘制
    @Override
        public void onDrawFrame(GL10 gl) {
            super.onDrawFrame(gl);
    
            //0.先使用这个program?这一步应该可以放到onCreate中进行
            GLES20.glUseProgram(mProgramObjectId);
    
            //1.根据我们定义的取出定义的位置
            int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, A_POSITION);
            //2.开始启用我们的position
            GLES20.glEnableVertexAttribArray(vPosition);
            //3.将坐标数据放入
            GLES20.glVertexAttribPointer(
                    vPosition,  //上面得到的id
                    COORDS_PER_VERTEX, //告诉他用几个偏移量来描述一个顶点
                    GLES20.GL_FLOAT, false,
                    STRIDE, //一个顶点需要多少个字节的偏移量
                    mVertexFloatBuffer);
    
            //取出颜色
            int uColor = GLES20.glGetUniformLocation(mProgramObjectId, U_COLOR);
    
            //开始绘制
            //设置绘制三角形的颜色
            GLES20.glUniform4fv(
                    uColor,
                    1,
                    TRIANGLE_COLOR,
                    0
            );
    
            //绘制三角形.
            //draw arrays的几种方式 GL_TRIANGLES三角形 
            //GL_TRIANGLE_STRIP三角形带的方式(开始的3个点描述一个三角形,后面每多一个点,多一个三角形) 
            //GL_TRIANGLE_FAN扇形(可以描述圆形)
            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_COUNT);
            //禁止顶点数组的句柄
            GLES20.glDisableVertexAttribArray(vPosition);
    
        }
    
    • 整体上
      1. use我们之前得到的programObjectId
      2. 启用我们定义的属性和变量,并设置数据
      3. 用绘制的命令开始对应的绘制

    最后的效果

    image.png

    总结一下,我们从这第一章节的内容了解到了下面这些使用的知识点:

    1. 运行在GLThread中
    2. 着色器编译和使用的套路
    3. 使用数组的方式来描述图形,使用本地内存分配的方式来分配对应的内存。
    4. 绘制图形的过程中,启用我们设置的属性和变量,并且绘制的套路

    未知道的:
    坐标矩阵的变化。和纹理等。

    整体的代码位置:https://github.com/deepsadness/OpenGLDemo5

    系列文章地址
    Android OpenGL ES(一)-开始描绘一个平面三角形
    Android OpenGL ES(二)-正交投影
    Android OpenGL ES(三)-平面图形
    Android OpenGL ES(四)-为平面图添加滤镜
    Android OpenGL ES(五)-结合相机进行预览/录制及添加滤镜
    Android OpenGL ES(六) - 将输入源换成视频
    Android OpenGL ES(七) - 生成抖音照片电影

    相关文章

      网友评论

        本文标题:Android OpenGL ES(一)-开始描绘一个平面三角形

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