【OpenGL ES】OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解(三)

4. 矩阵计算相关api

Matrix.multiplyMM(float[] result, int resultOffset, float[] lhs, int lhsOffset, float[] rhs, int rhsOffset)

参数 : 三组, 一个矩阵带着一个起始位置.

作用 : 计算投影变换矩阵, 将 前两个矩阵计算结果存入第三个矩阵;

5. 源码

package shuliang.han.rotatetriangle;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.opengl.GLES20;
import android.opengl.Matrix;
public class Triangle {
    public static float[] mProjMatrix = new float[16];  //4 * 4 投影矩阵
    public static float[] mVMatrix = new float[16];  //摄影机位置朝向参数矩阵
    public static float[] mMVPMatrix;       //最后起作用的总变换矩阵
    int mProgram;          //自定义渲染管线着色程序id
    /*
  * 下面的三个变量是顶点着色器中定义的三个变量
  * 其中的总变换矩阵属性 是 一致变量
  * 顶点位置 和 颜色属性 是 属性变量
  */
    int muMVPMatrixHandle;        //总变换矩阵的引用
    int maPositionHandle;        //顶点位置属性引用
    int maColorHandle;          //顶点颜色属性引用
    String mVertexShader;        //顶点着色器脚本代码
    String mFragmentShader;        //片元着色器脚本代码
    /*
  * 这个变换矩阵 在设置变换 , 位移 , 旋转的时候 将参数设置到这个矩阵中去
  */
    static float[] mMMatrix = new float[16];    //具体物体的3D变换矩阵, 包括旋转, 平移, 缩放
    /*
  * 这两个缓冲获得方法
  * ①创建ByteBuffer, 创建时赋予大小 设置顺序
  * ②将ByteBuffer 转为FloatBuffer
  * ③给FloatBuffer设置值, 设置起始位置
  */
    FloatBuffer mVertexBuffer;          //顶点坐标数据缓冲
    FloatBuffer mColorBuffer;       //顶点着色数据缓冲
    int vCount = 0;             //顶点数量
    float xAngle = 0;          //绕x轴旋转角度
    /**
  * 构造方法
  * @param mv GLSurfaceView子类对象, 显示3D画面的载体
  */
    public Triangle(MyTDView mv){
  initVertexData();
  initShader(mv);
    }
    /**
  * 初始化顶点数据
  * 
  * 该方法制定顶点坐标和颜色数据, 并将数据输入到缓冲区
  * 
  * 创建一个ByteBuffer缓冲区, 然后将ByteBuffer缓冲区转为FloatBuffer缓冲区
  * a. 创建float数组, 将对应的顶点(颜色)数据放到数组中去;
  * b. 创建ByteBuffer对象, 根据之前创建的float数组的字节大小创建这个ByteBuffer对象,使用allocateDirect(int)分配大小
  * c. 设置ByteBuffer对象的顺序, 调用order(ByteOrder.nativeOrder),设置为本地操作系统顺序
  * d. 将ByteBuffer对象转为FloatBuffer对象, 调用asFloatBuffer()方法;
  * e. 给FloatBuffer对象设置数组, 将开始创建的float数组设置给FloatBuffer对象;
  * f. 设置FloatBuffer对象缓冲区的起始位置为0
  */
    public void initVertexData() {
  //设置定点数为3
  vCount = 3; 
  //计算三角形顶点的单位
  final float UNIT_SIZE = 0.2f;
  /*
   * 这个float数组9个浮点数, 每3个为一个顶点的坐标
   */
  float vertices[] = new float[]{
    -4 * UNIT_SIZE, 0 , 0,  //x轴左边的坐标 
    0, -4 * UNIT_SIZE, 0,   //y轴坐标
    4 * UNIT_SIZE, 0, 0  //x轴右边的坐标
  };
  /*
   * 创建一个ByteBuffer对象, 这个对象中缓冲区大小为vertices数组大小的4倍
   * 因为每个float占4个字节, 创建的缓冲区大小正好将vertices装进去
   */
  ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
  //设置字节顺序为本地操作系统顺序
  vbb.order(ByteOrder.nativeOrder());
  //将该缓冲区转换为浮点型缓冲区
  mVertexBuffer = vbb.asFloatBuffer();
  //将顶点的位置数据写入到顶点缓冲区数组中
  mVertexBuffer.put(vertices);
  //设置缓冲区的起始位置为0
  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);//创建ByteBuffer
  cbb.order(ByteOrder.nativeOrder());//设置字节顺序
  mColorBuffer = cbb.asFloatBuffer();//将字节缓冲转为浮点缓冲
  mColorBuffer.put(colors);
  mColorBuffer.position(0);
    }
    /**
  * 初始化着色器
  * 
  * 流程 : 
  *     ① 从资源中获取顶点 和 片元着色器脚本
  *     ② 根据获取的顶点 片元着色器脚本创建着色程序
  *     ③ 从着色程序中获取顶点位置引用 , 顶点颜色引用,  总变换矩阵引用
  * 
  * @param mv MyTDView对象, 是GLSurfaceView对象
  */
    public void initShader(MyTDView mv){
  /*
   * mVertextShader是顶点着色器脚本代码
   * 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中
   * 传入的两个参数是 脚本名称 和 应用的资源
   * 应用资源Resources就是res目录下的那写文件
   */
  mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
  mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
 
  /*
   * 创建着色器程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错
   */
  mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
 
  /*
   * 从着色程序中获取 属性变量 顶点坐标(颜色)数据的引用
   * 其中的"aPosition"是顶点着色器中的顶点位置信息
   * 其中的"aColor"是顶点着色器的颜色信息
   */
  maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
  maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
 
  /*
   * 从着色程序中获取一致变量  总变换矩阵
   * uMVPMatrix 是顶点着色器中定义的一致变量
   */
  muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
 
    }
    /**
  * 绘制三角形方法
  * 
  * 绘制流程 : 
  *     ① 指定着色程序
  *     ② 设置变换矩阵
  *     ③ 将顶点位置 颜色 数据传进渲染管线
  *     ④ 启动顶点位置 颜色 数据
  *     ⑤ 执行绘制
  */
    public void drawSelf(){
  //根据着色程序id 指定要使用的着色器
  GLES20.glUseProgram(mProgram);
  /*
   * 设置旋转变化矩阵 
   * 参数介绍 : ① 3D变换矩阵 ② 矩阵数组的起始索引 ③旋转的角度 ④⑤⑥
   */
  Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);
  /*
   * 设置沿z轴正方向位移
   * 参数介绍 : ① 变换矩阵 ② 矩阵索引开始位置 ③④⑤设置位移方向z轴
   */
  Matrix.translateM(mMMatrix, 0, 0, 0, 1);
  /*
   * 设置绕x轴旋转
   * 参数介绍 : ① 变换矩阵 ② 索引开始位置 ③ 旋转角度 ④⑤⑥ 设置绕哪个轴旋转
   */
  Matrix.rotateM(mMMatrix, 0, xAngle, 1, 0, 0);
  /*
   * 应用投影和视口变换
   */
  GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFianlMatrix(mMMatrix), 0);
  /*
   * 将顶点位置数据传送进渲染管线, 为画笔指定定点的位置数据
   */
  GLES20.glVertexAttribPointer(
    maPositionHandle,  //顶点位置数据引用
    3,      //每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);
    }
    /**
  * 计算最终投影的矩阵
  * @param spec
  * @return
  */
    public static float[] getFianlMatrix(float[] spec){
  mMVPMatrix = new float[16];
  /*
   * 计算矩阵变换投影
   * 
   * 参数介绍 : 
   *  ① 总变换矩阵     ② 总变换矩阵起始索引
   *  ③ 摄像机位置朝向矩阵   ④ 摄像机朝向矩阵起始索引
   *  ⑤ 投影变换矩阵     ⑥ 投影变换矩阵起始索引
   */
  Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
  Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
  return mMVPMatrix;
    }
}
四. GLSurfaceView相关api
GLSurfaceView主要是创建渲染器, 实现其中的三个方法 onSurfaceCreated(), onSurfaceChanged(), onDrawFrame();
1. 相关api
(1) 设置OpenGL版本
GLSurfaceView.setEGLContextClientVersion(int version)
作用 : 设置OPenGL的版本号, version 是 2 , 就是设置OpenGLES2.0;
(2) 设置背景颜色
GLES20.glClearColor(0, 0, 0, 1.0f);
(3) 设置视口大小
GLES20.glViewport(int x, int y, int width, int height)
(4) 设置透视矩阵
Matrix.frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far)
参数 : m 投影矩阵; offset 投影矩阵起始位置; 剩下的参数为 左 右 下 上 近视点 远视点;
左 右 的值是宽高比, 左边为负数, 右边为正数;
(5) 设置摄像机参数
Matrix.setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)
参数 : rm 摄像机参数矩阵; rmOffset 摄像机参数矩阵起始位置; 剩下的三个一组, 分别是   摄像机位置  摄像机朝向摄像机上方朝向 ;
(6) 清除深度缓冲与颜色缓冲
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
2. 源码
package shuliang.han.rotatetriangle;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
public class MyTDView extends GLSurfaceView {
    private final float ANGLE_SPAN = 0.375f;  //三角形每次旋转的角度
    private RotateThread mRotateThread;  //该线程用来改变图形角度
    private SceneRenderer mSceneRender;  //渲染器
    public MyTDView(Context context) {
  super(context);
  //设置OpenGLES版本为2.0
  this.setEGLContextClientVersion(2);   
 
  //设置渲染器 渲染模式
  mSceneRender = new SceneRenderer();
  this.setRenderer(mSceneRender);
  this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }
    /**
  * 渲染器
  * 实现了下面三个方法 : 
  *     界面创建 : 
  *     界面改变 : 
  *     界面绘制 : 
  * @author HanShuliang
  *
  */
    private class SceneRenderer implements Renderer{
  Triangle triangle;
 
  @Override
  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    //设置屏幕背景色
    GLES20.glClearColor(0, 0, 0, 1.0f);
    //创建三角形对象
    triangle = new Triangle(MyTDView.this);
    //打开深度检测
    GLES20.glEnable(GLES20.GL_DEPTH_TEST);
    mRotateThread = new RotateThread();
    mRotateThread.start();
  }
  @Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
    //设置视窗大小及位置
    GLES20.glViewport(0, 0, width, height);
    //计算GLSurfaceView的宽高比
    float ratio = (float)width/height;
    /*
    * 产生透视矩阵
    * 参数介绍 : 
    * ① 4 * 4 投影矩阵
    * ② 投影矩阵的起始位置
    * 后面的四个参数分别是 左 右 下 上 的距离
    * 最后两个参数是 近视点 和 远视点 距离
    */
    Matrix.frustumM(Triangle.mProjMatrix, 0, 
        -ratio, ratio, 
        -1, 1, 
        1, 10);
    /*
    * 设置摄像机参数矩阵
    * 参数介绍 : 
    * 前两个参数是摄像机参数矩阵 和 矩阵数组的起始位置
    * 后面三个一组是三个空间坐标 先后依次是 摄像机的位置  看的方向 摄像机上方朝向
    */
    Matrix.setLookAtM(Triangle.mVMatrix, 0, 
        0f,0f,3f,
        0f,0f,0f,
        0f,1.0f,0.0f);
  }
  @Override
  public void onDrawFrame(GL10 gl) {
    //清除深度缓冲与颜色缓冲
    GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    //绘制三角形
    triangle.drawSelf();
  }
    }
    /**
  * 这个线程是用来改变三角形角度用的
  */
    public class RotateThread extends Thread{
 
  public boolean flag = true;
 
  @Override
  public void run() {
    while(flag){
    mSceneRender.triangle.xAngle = mSceneRender.triangle.xAngle + ANGLE_SPAN;
    try {
        Thread.sleep(20);
    } catch (Exception e) {
        e.printStackTrace();
    }
    }
  }
 
    } 
}


五 MainActivity相关

1. 相关api

(1) 设置界面为竖屏

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

(2) 界面获取焦点

View.requestFocus()

(3) 设置可获取焦点

View.setFocusableInTouchMode(boolean focusableInTouchMode)

作用 : 在触摸的时候获取焦点

2. 源码

package shuliang.han.rotatetriangle;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
public class MainActivity extends Activity {
    private MyTDView myTDView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置界面显示为竖屏
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        //创建OpenGL的显示界面
        myTDView = new MyTDView(this);
        myTDView.requestFocus();
        myTDView.setFocusableInTouchMode(true);
        //将OpenGL显示界面设置给Activity
        setContentView(myTDView);
    }
   
    @Override
    public void onResume() {
        super.onResume();
        myTDView.onResume();
    }
   
    @Override
    public void onPause() {
        super.onPause();
        myTDView.onPause();
    }
   
}

六. 着色器脚本

顶点着色器 :

uniform mat4 uMVPMatrix; //总变换矩阵
attribute vec3 aPosition;  //顶点位置
attribute vec4 aColor;    //顶点颜色
varying  vec4 vColor;  //用于传递给片元着色器的变量
void main()     
{                              
   gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
   vColor = aColor;//将接收的颜色传递给片元着色器 
}                      
片元着色器 : 
precision mediump float;
varying  vec4 vColor; //接收从顶点着色器过来的参数
void main()                         
{                       
   gl_FragColor = vColor;//给此片元颜色值
}
上一篇:【Android 应用开发】Android - TabHost 选项卡功能用法详解


下一篇:【UML 建模】UML建模语言入门 -- 用例视图详解 用例视图建模实战(三)