OpenGL ES 在Android平台的应用实践(2)

       第一篇文章讲述了OpenGL ES 涉及到的一些原理,相信初学者应该对OpenGL ES有了一定的初步认识与了解。下面我们讲解如何进行OpenGL ES的编程实现。

       先来一个简单的例子,在手机的屏幕上绘制一个三角形,三角形的三个顶点采用不同的颜色。

1.在AndroidManifest.xml中添加对OpenGL ES 2.0的支持。

<uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

  同时在自定义的Activity中判断手机硬件是否支持OpenGL ES 2.0,创建GLSurfaceView实现对OpenGL ES API的绘制。

public class TriangleActivity extends Activity
{
  private GLSurfaceView mGLSurfaceView;
  @Override
  public void onCreate(Bundle savedInstanceState)
 {
    super.onCreate(savedInstanceState);
    mGLSurfaceView = new GLSurfaceView(this);
    final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
    final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
    if (supportsEs2) 
    {
      mGLSurfaceView.setEGLContextClientVersion(2);
      mGLSurfaceView.setRenderer(new MyRenderer());
    } 
    else 
    {
      return;
    }
    setContentView(mGLSurfaceView);
  }
  @Override
  protected void onResume() 
  {
    super.onResume();
    mGLSurfaceView.onResume();
  }
  @Override
  protected void onPause() 
  {
    super.onPause();
    mGLSurfaceView.onPause();
  }
}

2.实现自定义的渲染器,继承GLSurfaceView.Renderer。

public class MyRenderer implements GLSurfaceView.Renderer
{
  private float[] mModelMatrix = new float[16];//模型矩阵
  private float[] mViewMatrix = new float[16];//视图矩阵
  private float[] mProjectionMatrix = new float[16];//投影矩阵
  private float[] mMVPMatrix = new float[16];//总矩阵
  private final FloatBuffer mVertices;//存储顶点坐标数据
  private int mMVPMatrixHandle;//对应顶点着色器中的总矩阵句柄
  private int mPositionHandle;//坐标句柄
  private int mColorHandle;//颜色句柄
  private final int mBytesPerFloat = 4;//每一个float占4个字节
  private final int mStrideBytes = 7 * mBytesPerFloat;//每一个顶点的步长,下文中一个顶点包括X,Y,Z,R,G,B,A所以这里是7
  private final int mPositionOffset = 0;//顶点偏移量
  private final int mPositionDataSize = 3;//一个顶点数据大小
  private final int mColorOffset = 3;//颜色偏移量,即一个顶点数据,位置为3开始表示的是颜色值
  private final int mColorDataSize = 4;//颜色占据的数据大小
  public MyRenderer()
{
    final float[] verticesData = {
        // X, Y, Z, 
        // R, G, B, A
        -0.5f, -0.25f, 0.0f,
        1.0f, 0.0f, 0.0f, 1.0f,//第一个顶点

        0.5f, -0.25f, 0.0f,
        0.0f, 0.0f, 1.0f, 1.0f,//第二个顶点

        0.0f, 0.5f, 0.0f,//第三个顶点
        0.0f, 1.0f, 0.0f, 1.0f
    };
    //GPU不能直接访问java虚拟机内存,所以需要将java内存转化为本地系统内存,还要考虑存储时的大小端模式,这里直接指定采用本地存储顺序即可
    mVertices = ByteBuffer.allocateDirect(verticesData.length * mBytesPerFloat)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
    mVertices.put(verticesData).position(0);//位置置0,从开始的位置取数据
  }
  @Override
  public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{
    //这里可以调用OpenGL ES API进行绘制操作了,下面一行仅为清屏操作,设置屏幕为某一颜色
    GLES20.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
    //设置观察点的位置,即摄像机的位置
    final float eyeX = 0.0f;
    final float eyeY = 0.0f;
    final float eyeZ = 1.5f;
    //设置观察物体的位置
    final float lookX = 0.0f;
    final float lookY = 0.0f;
    final float lookZ = -5.0f;
    //设置摄像机的上方向,类比人看某一物体时,头顶方向即为上方向
    final float upX = 0.0f;
    final float upY = 1.0f;
    final float upZ = 0.0f;
    //通过下面这个函数设置视图矩阵
    Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
    //编写顶点着色器,一个顶点执行一次
    final String vertexShader =
      "uniform mat4 u_MVPMatrix;      \n" //总矩阵,与mMVPMatrixHandle对应
      + "attribute vec4 a_Position;     \n" //顶点坐标,与mPositionHandle对应
      + "attribute vec4 a_Color;        \n" //颜色值,与mColorHandle对应
      + "varying vec4 v_Color;          \n" //由顶点着色器向片元着色器传递颜色值的中间变量
      + "void main()                    \n"
      + "{                              \n"
      + "   v_Color = a_Color;          \n"
      + "   gl_Position = u_MVPMatrix* a_Position;\n" //计算最终的顶点坐标
      + "}                              \n";
      //编写片元着色器,一个片元执行一次
    final String fragmentShader =
      "precision mediump float;       \n" //指定为中等精度
      + "varying vec4 v_Color;          \n" //接收来自顶点着色器传递过来的颜色变量
      + "void main()                    \n"
      + "{                              \n"
      + "   gl_FragColor = v_Color;     \n" //设置片元最后的颜色
      + "}                              \n";                        
    //创建顶点着色器
    int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
    if (vertexShaderHandle != 0) 
    {
      //加载顶点着色器的脚本到创建的顶点着色器中,并进行编译
      GLES20.glShaderSource(vertexShaderHandle, vertexShader);
      GLES20.glCompileShader(vertexShaderHandle);
      final int[] compileStatus = new int[1];
      //检查编译的结果是否正确,因为GPU的执行无法像CPU执行一样通过打印日志来获取执行结果,所以提供了获取执行结果的函数
      GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
      if (compileStatus[0] == 0) 
      {        
        GLES20.glDeleteShader(vertexShaderHandle);
        vertexShaderHandle = 0;
      }
    }
    if (vertexShaderHandle == 0)
    {
      throw new RuntimeException("创建顶点着色器错误");
    }
    //创建片元着色器
    int fragmentShaderHandle = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
    if (fragmentShaderHandle != 0) 
    {
      //加载片元着色器的脚本到创建的片元着色器中,并进行编译
      GLES20.glShaderSource(fragmentShaderHandle, fragmentShader);
      GLES20.glCompileShader(fragmentShaderHandle);
      final int[] compileStatus = new int[1];
      GLES20.glGetShaderiv(fragmentShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
      if (compileStatus[0] == 0) 
      {        
        GLES20.glDeleteShader(fragmentShaderHandle);
        fragmentShaderHandle = 0;
      }
    }
    if (fragmentShaderHandle == 0)
    {
      throw new RuntimeException("创建片元着色器错误");
    }
    //创建一个着色器程序
    int programHandle = GLES20.glCreateProgram();
    if (programHandle != 0) 
    {
      //将顶点着色器和片元着色器链接到执行程序中
      GLES20.glAttachShader(programHandle, vertexShaderHandle);
      GLES20.glAttachShader(programHandle, fragmentShaderHandle);
      GLES20.glLinkProgram(programHandle);
      final int[] linkStatus = new int[1];
      //检查链接的结果是否出错
      GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);
      if (linkStatus[0] == 0) 
      {        
        GLES20.glDeleteProgram(programHandle);
        programHandle = 0;
      }
    }
    if (programHandle == 0)
    {
      throw new RuntimeException("创建程序失败");
    }
    //获取顶点着色器中相应属性的句柄值
    mMVPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix");
    mPositionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position");
    mColorHandle = GLES20.glGetAttribLocation(programHandle, "a_Color");
    //需要指定使用哪一个着色器程序来执行绘制操作,一个应用程序中可以有多个着色器程序
    GLES20.glUseProgram(programHandle);
  }
  @Override
  public void onSurfaceChanged(GL10 glUnused, int width, int height)
{
    //指定视图窗口的大小,即在手机上需要显示的大小
    GLES20.glViewport(0, 0, width, height);
    //计算宽度比例,避免屏幕方向变化,引起比例失调,导致失真
    final float ratio = (float) width / height;
    //指定近平面的值
    final float left = -ratio;
    final float right =ratio;
    final float bottom = -1.0f;
    final float top = 1.0f;
    final float near = 1.0f;
    final float far = 10.0f;
    //设置透视投影矩阵,设置正交投影矩阵函数为Matrix.orthoM()
    Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
  }
  @Override
  public void onDrawFrame(GL10 glUnused)
{
    //调用OpenGL ES API 清除深度缓存和颜色缓存,避免之前的数据对结果造成影响
    GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        long time = SystemClock.uptimeMillis() % 10000L;
        float angle = (360.0f / 10000.0f) * ((int) time);
        Matrix.setIdentityM(mModelMatrix, 0);
        //随着时间的流逝,变换三角形绕着X轴的旋转角度,三角形会绕着X轴不断的旋转
        Matrix.rotateM(mModelMatrix, 0, angle, 1, 0, 0);
        drawTriangle(mVertices);
  }
  private void drawTriangle(final FloatBuffer triangleData)
{
    triangleData.position(mPositionOffset);
    //将三角形的顶点数据传给着色器中相应的顶点句柄
    GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false, mStrideBytes, triangleData);
    GLES20.glEnableVertexAttribArray(mPositionHandle);//使能顶点数组
    
    triangleData.position(mColorOffset);
    //将颜色的值传给顶点着色器中颜色对应的句柄
    GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize, GLES20.GL_FLOAT, false, mStrideBytes, triangleData);
    GLES20.glEnableVertexAttribArray(mColorHandle);
    //通过矩阵相乘,计算最终的总矩阵
    Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
    //将计算后的总矩阵传给顶点着色器中总矩阵对应的句柄
    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
    //开始绘制三角形,绘制方式为GLES20.GL_TRIANGLES模式,第一篇文章已经指出
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
  }
}

 以上每一行的作用我都在后面做了注释,希望可以说的清楚,不明白的朋友可以留言。

   

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值