>>分享Android开发相关的技术 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 19793 个阅读者 刷新本主题
 * 贴子主题:  Android OpenGL ES2.0学习:基础三角形案例 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2020-03-09 15:09:52     消息  查看  搜索  好友  邮件  复制  引用

                                                                                                

Android OpenGL ES2.0学习:基础三角形案例

        OpenGL ES 2.0与OpenGL ES 1.x的一个最大不同就是用可编程渲染管线代替了原有的固定渲染管线。在学习OpenGL ES 2.0之前,有必要先了解一下OpenGL ES 1.x渲染管线的工作原理。

         渲染管线有时也被称为渲染流水线,一般是由显示芯片内部的处理图形信号的并行处理单元组成。与普通应用程序通过CPU串行执行不同,将渲染工作通过渲染管线中多个相互独立的处理单元进行并行处理后,渲染效率可以得到极大提升。这个过程中输入的是待渲染3D物体的相关描述信息数据,经过渲染管线,输出的是一帧想要的图像。该过程的主要工作可以分为:1.基本处理,2.生成顶点缓冲对象,3.变化和光照,4.图元装配,5.光栅化,6.纹理环境和颜色求和,7.雾化,8.Alpha测试,9.剪裁测试,10.深度测试等等。

         OpenGL ES 1.x 只对开发人员开放了其中的一些API接口,在整个渲染管线的运行过程中开发人员不能直接干预,所以留给编程人员发挥的空间不大,很多特效难以开发,OpenGL ES 2.0则用可编程渲染管线(包括顶点着色器和片元着色器)为编程人员提供了更多的发挥空间。2.0中使用“顶点着色器”取代了1.x渲染管线中“变换和光照”的阶段,这使得开发3D场景时对顶点的变换,法向量的计算,纹理坐标的变换,光照与材质的应用等均由开发者使用着色器代码完成,灵活性大大提高;另外,使用“片元着色器”取代了OpenGL ES 1.x渲染管线中“纹理环境和颜色求和”,“雾化”,“Alpha测试”等阶段,这使得纹理处理,颜色求和和雾效果均由开发者自行开发,大大增强了程序对片元的处理能力。

         下面就通过一个基础的绘制三角形的案例来对OpenGL ES 2.0的开发过程有个初步的认识。

         首先,我们开发一个工具类ShaderUtil,对常用的加载着色器的代码进行封装,主要功能就是将着色器(Shader)脚本加载进显卡并进行编译,其中loadFromAssets方法从Assets文件夹下加载着色器代码脚本,checkGlError方法检查过程中每一步是否出错,loadShader方法加载指定着色器,createProgram方加载顶点和片元着色器,创建着色器程序,并返回着色器程序id。        

  import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
* 加载顶点与片元着色器的类
*/

public   class  ShaderUtil {
     /**
     * 加载指定着色器的方法
     *  @param shaderType 着色器的类型
     *  @param source 着色器的脚本字符串
     */

     public  static  int  loadShader(  int shaderType , String source ){

         //创建一个shader,并记录其类型
         int shader = GLES20.glCreateShader(shaderType);
         //若创建成功则加载着色器
         if( shader !=  0 ){
             //加载着色器的源代码
            GLES20.glShaderSource(shader,source);
             //编译
            GLES20.glCompileShader(shader);
             int[] compiled =  new  int[ 1];
             //获取Shader的编译情况
            GLES20.glGetShaderiv(shader,GLES20.GL_COMPILE_STATUS,compiled, 0);
             //若编译失败则显示错误日志并删除此shader
             if( compiled[ 0] ==  0 ){
                Log.e( "ES20_ERROR", "Could not compile shader " + shaderType +  ":");
                Log.e( "ES20_ERROR",GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader =  0 ;
            }
        }
         return shader;
    }

     /**
     * 创建着色器程序的方法
     */

     public  static  int  createProgram(String vertexSource,String fragmentSource){

         //加载顶点着色器
         int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER , vertexSource);
         if( vertexShader ==  0){
             return  0 ;
        }

         //加载片元着色器
         int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
         if( pixelShader ==  0 ){
             return  0 ;
        }
         //创建程序
         int program = GLES20.glCreateProgram();
         //若程序创建成功则向程序中加入顶点着色器与片元着色器
         if( program !=  0 ){
             //向程序中加入顶点着色器
            GLES20.glAttachShader(program,vertexShader);
            checkGlError( "glAttachShader");
             //向程序中加入片元着色器
            GLES20.glAttachShader(program,pixelShader);
            checkGlError( "glAttachShader");
             //链接程序
            GLES20.glLinkProgram(program);
             //存放链接成功program状态值的数组
             int[] linkStatus =  new  int[ 1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS , linkStatus ,  0 ) ;
             //若链接失败则报错并删除程序
             if( linkStatus[ 0] != GLES20.GL_TRUE){
                Log.e( "ES20_ERROR" ,  "Could not link program");
                Log.e( "ES20_ERROR" , GLES20.glGetProgramInfoLog(program));
                 //删除程序
                GLES20.glDeleteProgram(program);
                program =  0 ;
            }
        }
         return program;
    }

     /**
     * 检查每一步操作是否有错误的方法
     *  @param op 发生错误的方法名
     */

     public  static  void  checkGlError( String op){
         int error ;
         while( ( error = GLES20.glGetError()) != GLES20.GL_NO_ERROR){
             //后台打印错误
            Log.e( "ES20_ERROR", op +  ": glError " + error);
             //抛出异常
             throw  new RuntimeException( op +  ": glError " + error) ;

        }
    }

     /**
     * 从sh 脚本中加载着色器内容的方法
     *  @param fname 文件名
     *  @param r 资源文件
     *  @return 结果字符串
     */

     public  static String  loadFromAssetsFile(String fname , Resources r){
        String result =  null ;
         try {
             //从assets文件夹中读取信息
            InputStream in = r.getAssets().open(fname);
             //定义一个int型变量
             int ch =  0 ;
            ByteArrayOutputStream baos =  new ByteArrayOutputStream();
             while( ( ch = in.read()) != - 1 ){
                baos.write(ch);
            }
             byte[] buff = baos.toByteArray();
             //关闭输出流
            baos.close();
            in.close();
             //转换为UTF-8编码
            result =  new String( buff,  "UTF-8");
            result = result.replaceAll( "\\r\
", "
");

        } catch ( Exception e){
            e.printStackTrace();
        }

         return result;
    }

}

     下面创建一个类继承GLSurfaceView,并实现GLSurfaceView.Renderer接口。        

  import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;

     import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
* Created by Administrator on 2017/9/6.
*/


public   class  MyGLSurfaceView  extends GLSurfaceView {
     //每次三角形旋转的角度
     final  float ANGLE_SPAN =  0.375f ;
     //自定义线程类RotateThread的引用
    SceneRenderer.RotateThread rthread ;
     //自定义渲染器的引用
    SceneRenderer mRenderer ;
     //构造器
     public  MyGLSurfaceView(Context context){
         super(context);
         //使用OpenGL ES 2.0 需设置该值为2
         this.setEGLContextClientVersion( 2);
         //创建SceneRenderer类的对象
        mRenderer =  new SceneRenderer();
         //设置渲染器
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

     public   class  SceneRenderer  implements  GLSurfaceView. Renderer{

         //声明Triangle类的引用
        Triangle tle ;

         //重写onDrawFrame方法
         @Override
         public  void  onDrawFrame(GL10 gl) {
            GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
            tle.drawSelf();
        }

         @Override
         public  void  onSurfaceChanged(GL10 gl,  int width,  int height) {
             //设置视口
            GLES20.glViewport( 0, 0,width,height);
             //计算屏幕的宽度和高度比例
             float ratio = ( float) width/height;
             //设置透视投影
            android.opengl.Matrix.frustumM(Triangle.mProjMatrix, 0,-ratio,ratio,- 1, 1, 1, 10);
             //设置摄像机
            android.opengl.Matrix.setLookAtM(Triangle.mVMatrix, 0, 0, 0, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        }

         @Override
         public  void  onSurfaceCreated(GL10 gl, EGLConfig config) {
             //设置屏幕背景色
            GLES20.glClearColor( 0, 0, 0, 1.0f);
             //创建Triangle类的对象
            tle =  new Triangle(MyGLSurfaceView. this);
            GLES20.glEnable(GLES20.GL_DEPTH_TEST);
             //创建RotateThread类的对象
            rthread =  new RotateThread();
             //开启线程
            rthread.start();

        }
         //自定义的内部类线程
         public   class  RotateThread  extends Thread{

             public  boolean flag =  true ;

             @Override
             public  void  run() {
                 while (flag){
                    mRenderer.tle.xAngle = mRenderer.tle.xAngle + ANGLE_SPAN ;
                     try {
                        Thread.sleep( 20);
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }

                }
}

     下面编写代表三角形的类Triangle        

  import android.opengl.GLES20;
import android.opengl.Matrix;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
* Created by Administrator on 2017/9/6.
*/


public   class  Triangle {
     //4x4投影矩阵
     public  static  float[] mProjMatrix =  new  float[ 16] ;
     //摄像机位置朝向的参数矩阵
     public  static  float[] mVMatrix =  new  float[ 16] ;
     //总变换矩阵
     public  static  float[] mMVPMatrix ;
     //自定义渲染管线着色器程序id
     int mProgram ;
     //总变换矩阵引用
     int muMVPMatrixHandle ;
     //顶点位置属性引用
     int maPositionHandle ;
     //顶点颜色属性引用
     int maColorHandle ;
     //顶点着色器代码脚本
    String mVertexShader ;
     //片元着色器代码脚本
    String mFragmentShader ;
     //具体物体的3D变换矩阵,包括旋转,平移,缩放
     static  float[] mMMatrix =  new  float[ 16];
     //顶点坐标数据缓冲
    FloatBuffer mVertexBuffer ;
     //顶点着色数据缓冲
    FloatBuffer mColorBuffer ;
     //顶点数量
     int vCount ;
     //绕x轴旋转的角度
     float xAngle =  0 ;
     //构造函数
     public  Triangle(MyGLSurfaceView mv ){
         //初始化顶点数据
        initVertexData();
         //初始化着色器
        initShader(mv);
    }
     //自定义的初始化顶点数据的方法
     public  void  initVertexData(){
         //顶点数量为3
        vCount =  3 ;
         //设置单位长度
         final  float UNIT_SIZE =  0.2f ;
         //顶点坐标数组
         float vertices[] =  new  float[]{
                - 4*UNIT_SIZE, 0, 0, 0,- 4*UNIT_SIZE, 0, 4*UNIT_SIZE, 0, 0
        };
         //开辟缓冲
        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length* 4);
         //设置字节顺序为本地操作系统顺序
        vbb.order(ByteOrder.nativeOrder());
         //转换为float型缓冲
        mVertexBuffer = vbb.asFloatBuffer();
         //在缓冲区内写入数据
        mVertexBuffer.put(vertices);
         //设置缓冲区起始位置
        mVertexBuffer.position( 0);

         //顶点颜色数组
         float colors[] =  new  float[]{
                 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0
        };
        ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length* 4);
        cbb.order(ByteOrder.nativeOrder());
        mColorBuffer = cbb.asFloatBuffer();
        mColorBuffer.put(colors);
        mColorBuffer.position( 0);

    }
     //产生最终变换矩阵的方法
     public  static  float[]  getFinalMatrix(  float[] spec ){
        mMVPMatrix =  new  float[ 16];
        Matrix.multiplyMM(mMVPMatrix, 0,mVMatrix, 0,spec, 0);
        Matrix.multiplyMM(mMVPMatrix, 0,mProjMatrix, 0,mMVPMatrix, 0);
         //返回总变换矩阵
         return mMVPMatrix ;
    }

     public  void  initShader(MyGLSurfaceView mv ){
        mVertexShader = ShaderUtil.loadFromAssetsFile( "vertex.sh",mv.getResources());
        mFragmentShader = ShaderUtil.loadFromAssetsFile( "frag.sh" , mv.getResources());
        mProgram = ShaderUtil.createProgram(mVertexShader,mFragmentShader);
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

    }

     public  void  drawSelf(){
        GLES20.glUseProgram(mProgram);
         //初始化变换矩阵
        Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);
         //设置沿z轴正向位移
        Matrix.translateM(mMMatrix, 0, 0, 0, 1);
         //设置绕x轴旋转
        Matrix.rotateM(mMMatrix, 0,xAngle, 1, 0, 0);
        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false,Triangle.getFinalMatrix(mMMatrix), 0);
         //将顶点数据传送进渲染管线
        GLES20.glVertexAttribPointer(maPositionHandle, 3,GLES20.GL_FLOAT, false, 3* 4,mVertexBuffer);
         //将顶点着色数据传送进渲染管线
        GLES20.glVertexAttribPointer(maColorHandle, 4,GLES20.GL_FLOAT, false, 4* 4,mColorBuffer);
         //启用顶点位置数据
        GLES20.glEnableVertexAttribArray(maPositionHandle);
         //启用着色数据
        GLES20.glEnableVertexAttribArray(maColorHandle);
         //执行绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,vCount);
    }

    }

     最后在MainActivity中设置显示的视图为自定义的GLSurfaceview,并设置Renderer渲染器即可。        

  import android.content.pm.ActivityInfo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public   class  MainActivity  extends AppCompatActivity {

    MyGLSurfaceView mView ;

     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
           setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         //创建MySurfaceView对象
        mView =  new MyGLSurfaceView( this);
         //获取焦点
        mView.requestFocus();
         //设为可触控
        mView.setFocusableInTouchMode( true);
        setContentView(mView);
    }

     @Override
     protected  void  onResume() {
         super.onResume();
         //调用GLSurfaceview的onResume方法
        mView.onResume();
    }

     @Override
     protected  void  onPause() {
         super.onPause();
         //调用GLSurfaceview的onPause方法
        mView.onPause();
    }
}

     最终运行效果如下

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
                                    
                                                                    
----------------------------
原文链接:https://blog.csdn.net/SakuraMashiro/article/details/78252279

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-03-11 10:59:47 重新编辑]
  Java面向对象编程-->Java常用类(下)
  JavaWeb开发-->在Web应用中访问Web服务
  JSP与Hibernate开发-->Spring、JPA与Hibernate的整合
  Java网络编程-->RMI框架
  精通Spring-->通过Axios访问服务器
  Vue3开发-->组合(Composition)API
  Android Gallery实现循环显示图像
  Android定义的路径全局变量汇总
  Android中的几个布局
  android异步更新UI
  Android DownloadManager用法举例
  Android网络编程之WebKit应用
  Android忽略HTTPS安全验证的解决
  Android ListView高度问题
  Android学习建议
  Android性能优化之视图篇(渲染机制)_移动开发_Applicaton的...
  Android内存优化—dumpsys meminfo详解
  Android 自定义九宫格手势锁
  Android Camera2.0 API实现摄像头预览并获取人脸关键坐标
  Android 碎片(Fragment)
  Android开发学习笔记:Intent的简介以及属性的详解-IT的点点...
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。