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

3.着色程序相关的API

创建着色程序流程 : 加载顶点着色器 --> 加载片元着色器 --> 创建着色程序 --> 将顶点着色器加入着色程序 --> 将片元着色器加入着色程序 --> 链接着色程序 --> 获取链接着色程序结果

(1)创建OpenGL程序

int program = GLES20.glCreateProgram();

调用GLES20.glCreateProgram()方法 , 可以创建一个3D程序 , 返回程序的引用 , 如果不返回0 , 说明没有创建成功.

(2)获取OpenGL中的错误信息

GLES20.glGetError();

返回一个int类型的错误码 , 如果没有错误 , 就会返回 GLES20.GL_NO_ERROR 常量.

(3)向程序中加入着色器

GLES20.glAttachShader(program, vertextShader);

参数 : program 是调用GLES20.glCreateProgram()方法创建程序的返回值 , 这是程序的引用 .   vertextShader是着色器的引用 , 注意 这个着色器是加载了着色脚本并且成功编译的着色器引用 .

返回值 : 该方法没有返回值;

(4)连接程序

GLES20.glLinkProgram(program);

参数 : 需要链接的程序的引用, 即着色程序容器的句柄;

作用 : 着色程序中存放定点着色器与片元着色器;

(5)获取链接程序结果

int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);

参数 : program , 程序的引用 ;

GLES20.GL_LINK_STATUS , 想要获取的信息的类别;

linkStatus , 存放结果的数组;

index , 将结果存放的数组的下标;

作用 : 这个方法可以获取到链接程序操作是否成功, 如果结果不为1, 说明链接程序失败;

(6)删除着色程序

GLES20.glDeleteProgram(program);

参数 : 着色程序的引用;

作用 : 删除链接失败的着色程序;

4. 源码

package shuliang.han.rotatetriangle;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;
/*
 * 这个工具类用来加载定点着色器与片元着色器
 */
public class ShaderUtil {
    /**
  * 加载着色器方法
  * 
  * 流程 : 
  * 
  * ① 创建着色器
  * ② 加载着色器脚本
  * ③ 编译着色器
  * ④ 获取着色器编译结果
  * 
  * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER)
  * @param source 着色脚本字符串
  * @return 返回的是着色器的引用, 返回值可以代表加载的着色器
  */
    public static int loadShader(int shaderType , String source){
  //1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败
  int shader = GLES20.glCreateShader(shaderType);
  if(shader != 0){
    //2.如果着色器创建成功, 为创建的着色器加载脚本代码
    GLES20.glShaderSource(shader, source);
    //3.编译已经加载脚本代码的着色器
    GLES20.glCompileShader(shader);
    int[] compiled = new int[1];
    //4.获取着色器的编译情况, 如果结果为0, 说明编译失败
    GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
    if(compiled[0] == 0){
     Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
              Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
              //编译失败的话, 删除着色器, 并显示log
              GLES20.glDeleteShader(shader);
              shader = 0;
    }
  }
  return shader;
    }
    /**
  * 检查每一步的操作是否正确
  * 
  * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误
  * 
  * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器, 
  *     使glAttachShader()方法, 那么这个参数就是"glAttachShader"
  */
    public static void checkGLError(String op){
  int error;
  //错误代码不为0, 就打印错误日志, 并抛出异常
  while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){
    Log.e("ES20_ERROR", op + ": glError " + error);
          throw new RuntimeException(op + ": glError " + error);
  }
    }
    /**
  * 创建着色程序
  * 
  * ① 加载顶点着色器
  * ② 加载片元着色器
  * ③ 创建着色程序
  * ④ 向着色程序中加入顶点着色器
  * ⑤ 向着色程序中加入片元着色器
  * ⑥ 链接程序
  * ⑦ 获取链接程序结果
  * 
  * @param vertexSource  定点着色器脚本字符串
  * @param fragmentSource   片元着色器脚本字符串
  * @return
  */
    public static int createProgram(String vertexSource , String fragmentSource){
  //1. 加载顶点着色器, 返回0说明加载失败
  int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
  if(vertexShader == 0)
    return 0;
  //2. 加载片元着色器, 返回0说明加载失败
  int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
  if(fragShader == 0)
    return 0;
  //3. 创建着色程序, 返回0说明创建失败
  int program = GLES20.glCreateProgram();
  if(program != 0){
    //4. 向着色程序中加入顶点着色器
    GLES20.glAttachShader(program, vertexShader);
    checkGLError("glAttachShader");
    //5. 向着色程序中加入片元着色器
    GLES20.glAttachShader(program, fragShader);
    checkGLError("glAttachShader");
    
    //6. 链接程序
    GLES20.glLinkProgram(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", "链接程序失败 : ");
    Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program));
    //如果链接程序失败删除程序
    GLES20.glDeleteProgram(program);
    program = 0;
    }   
  }
  return program;
    }
    /**
  * 从assets中加载着色脚本
  * 
  * ① 打开assets目录中的文件输入流
  * ② 创建带缓冲区的输出流
  * ③ 逐个字节读取文件数据, 放入缓冲区
  * ④ 将缓冲区中的数据转为字符串
  * 
  * @param fileName assets目录中的着色脚本文件名
  * @param resources    应用的资源
  * @return
  */
    public static String loadFromAssetsFile(String fileName, Resources resources){
  String result = null;
  try {
    //1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流
    InputStream is = resources.getAssets().open(fileName);
    int ch = 0;
    //2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    //3. 逐个字节读取数据, 并将读取的数据放入缓冲器中
    while((ch = is.read()) != -1){
    baos.write(ch);
    }
    //4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串
    byte[] buffer = baos.toByteArray();
    baos.close();
    is.close();
    result = new String(buffer, "UTF-8");
    result = result.replaceAll("\\r\\n", "\n");
  } catch (Exception e) {
    e.printStackTrace();
  }
  return result;
    }
}

三. Triangle 3D三角形数据

1. 顶点数据容器相关api

初始化顶点数据流程 : 创建ByteBuffer对象 -> 设置ByteBuffer对象顺序 -> 将ByteBuffer对象转为FloatBuffer对象 -> 设置FloatBuffer对象值 -> 设置FloatBuffer对象起始位置

(1) 创建ByteBuffer对象

ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4)

allocateDirect()方法创建ByteBuffer对象, 同时分配该字节缓冲去的大小, 注意这个对象最终要转为FloatBuffer对象, 每个float占4个字节, 一共有vertices.length个浮点数, 因此要分配vertices.length * 4 个字节大小.

(2) 设置字节缓冲区顺序

vbb.order(ByteOrder.nativeOrder());

设置字节缓冲区的顺序为本地顺序.

(3) 将字节缓冲区转为浮点缓冲区

mVertexBuffer = vbb.asFloatBuffer();

(4) 向字节缓冲区中存入数据

mColorBuffer.put(colors);

直接调用put方法, 将浮点型数组放入缓冲区.

(5)指定浮点型缓冲区起始位置

mColorBuffer.position(0);

2. 初始化着色器相关api

初始化着色器流程 : 获取顶点,片元着色器 -> 创建着色程序 -> 从着色程序中的顶点着色器获取顶点位置,颜色,投影矩阵引用

(1) 获取着色器属性变量引用

float[] muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

该方法从着色程序中的顶点着色器中获取属性变量(Attribute) aPosition.

(2) 获取着色器一直变量引用

float[] muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

该方方法从着色程序中的顶点着色器获取一致变量

3. 绘制3D图形相关api

绘制三角形流程 :

(1) 指定着色器程序

GLES20.glUseProgram(mProgram);

参数 : 着色程序的引用id

作用 : 该方法的作用是指定程序中要使用的着色器

(2) 设置旋转初始情况

Matrix.setRotateM(float[] rm, int rmOffset, float a, float x, float y, float z)

参数 : rm 变换矩阵; rmOffset 变换矩阵的索引; a 旋转角度; 剩下的三个是旋转的轴

这个方法的作用是设置旋转变化矩阵

(3) 设置位移

Matrix.translateM(float[] m, int mOffset, float x, float y, float z)

参数 : m 变换矩阵; mOffset 变换矩阵的起始位置; 剩下的三个是位移向量.

(4) 设置旋转矩阵

Matrix.rotateM(float[] m, int mOffset, float a, float x, float y, float z)

参数 : m 变换矩阵; mOffset 变换矩阵起始位置; a 旋转的角度; 剩下的三个参数是旋转的轴;

(5) 应用投影和视口变换

GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset)

参数 :

(6) 将顶点数据传进渲染管线

GLES20.glVertexAttribPointer(
    maPositionHandle, 
    3, 
    GLES20.GL_FLOAT, 
    false, 
    3 * 4, 
    mVertexBuffer
  );

参数 : 顶点位置数据引用 几个一组 单位 false 个数 数据缓冲区.

(7) 启用传入的数据

GLES20.glEnableVertexAttribArray(maPositionHandle);

参数 : 从着色程序中获取的数据引用

作用 : 将刚才传入渲染管线的数据启用;

(8) 执行绘制方法

GLES20.glEnableVertexAttribArray(maPositionHandle);

该方法绘制三角形


上一篇:【Android 应用开发】Activity 状态保存 OnSaveInstanceState参数解析


下一篇:【IOS 开发】Objective - C 入门 之 数据类型详解(一)