前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【OpenGL ES】OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解

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

作者头像
韩曙亮
发布2023-03-27 12:02:29
1.5K0
发布2023-03-27 12:02:29
举报
文章被收录于专栏:韩曙亮的移动开发专栏

最近开始关注OpenGL ES 2.0 这是真正意义上的理解的第一个3D程序 , 从零开始学习 .

案例下载地址 : http://download.csdn.net/detail/han1202012/6651095 需要SDK-10 版本2.3.3

一. 程序介绍

1. 样例展示 

该程序打开之后会出现一个旋转的三角形, 该三角形一直绕x轴z方向旋转 如图 : 

2. 程序结构

本程序中定义了四个类 : ShaderUtil , Triangle , MyTDView , MainActivity . 

在Activity中加载myTDView对象, MyTDView对象中绘制Triangle 三角形图形, Triangle调用ShaderUtil加载着色脚本并创建着色程序.

四个类之间的关系

3. 方法介绍

(1) ShaderUtil方法

a. 加载着色器方法 : 根据着色器类型 和 着色器脚本字符串获取着色器

代码语言:javascript
复制
public static int loadShader(int shaderType , String source)

流程 : 创建着色器 -> 加载着色器脚本 -> 编译着色器 -> 获取着色器编译结果

b. 检查错误方法 : 检查每一步是否出现错误

代码语言:javascript
复制
public static void checkGLError(String op)

流程 : 循环获取错误信息, 知道出现异常将异常信息打印出来

c. 创建着色器方法 : 根据顶点着色器和片元着色器创建着色程序

代码语言:javascript
复制
public static int createProgram(String vertexSource , String fragmentSource)

流程 : 调用loadShader()加载顶点,片元着色器 -> 创建着色程序 -> 向着色程序中加载顶点,片元着色器 -> 连接程序 -> 获取链接结果

d. 获取着色脚本 : 从assets目录中的着色脚本中获取着色脚本的字符串信息

代码语言:javascript
复制
public static String loadFromAssetsFile(String fileName, Resources resources)

流程 : 打开assets输入流 -> 创建带缓冲区的输出流 -> 读取输入流信息放入缓冲区 -> 将缓冲区数据转为字符

二  ShaderUtils类介绍  

1.安卓基本API

(1) 创建assets目录中文件的输入流

代码语言:javascript
复制
InputStream is = resources.getAssets().open(fileName);

参数 : assets目录中着色脚本的文件名;

返回值 : 着色脚本文件的输入流;

作用 : 使用该输入流可以读取着色脚本信息

(2)带缓冲区的输出流

代码语言:javascript
复制
			创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			逐个字节读取数据, 并将读取的数据放入缓冲器中
			while((ch = is.read()) != -1){
				baos.write(ch);
			}
			将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串
			byte[] buffer = baos.toByteArray();

读写单位 : 这个输出流读取字节的单位是int, 这里要特别注意;

读取方法 : read()方法每次读取一个字节, 并返回读取到的字节;  

写出方法 : write()方法将一个字节写入到ByteArrayOutputStream的缓冲区中;

导出数据 : 调用toByteArray()方法可以将缓冲区中的数据转为字节数组, 并返回这个数组;

2.着色器相关API介绍

加载着色器流程 : 创建着色器 --> 加载着色器脚本 --> 编译着色器 --> 获取着色器编译结果

(1)创建着色程器

代码语言:javascript
复制
int shader = GLES20.glCreateShader(shaderType);

参数 : 这个函数的作用根据着色器类型 , 着色器的类型有两种 , GLES20.GL_VERTEX_SHADER 顶点着色器 , GLES20.GL_FRAGMENT_SHADER 片元着色器

返回值 : 该方法返回的是着色器的引用

(2)加载着色器源代码

代码语言:javascript
复制
GLES20.glShaderSource(shader, source);

参数 : shader是着色器的引用 , 是glCreateShader()方法的返回值 ;  source是着色器脚本的字符串形式 .

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

这样就相当于将代码添加到了着色器中, 注意此时着色器还不能使用 , 还要编译之后才能使用.

(3)编译着色器

代码语言:javascript
复制
GLES20.glCompileShader(shader);

参数 : shader是着色器的引用 

返回值 : 该方法没有返回值类型

执行这个方法的前提是 , 该着色器已经加载了着色器脚本字符串, 否则会编译错误

(4)获取着色器编译情况

代码语言:javascript
复制
int complied[] = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, complied, 0);

参数 : 

  • shader : 着色器引用 , 这个着色器已经加载了着色脚本字符串以及经过了编译 ;
  • pname : GLES20.GL_COMPILE_STATUS : 获取信息类型代码 : 我们要获取编译情况 , 这里是编译状态的代码
  • params[] : compile : 存放结果数组
  • index : 存放结果索引 , 将编译成功的脚本数放在数组的哪个索引下 

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

(5)删除着色器

代码语言:javascript
复制
GLES20.glDeleteShader(shader);

参数 : shader 是着色器的引用

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

如果着色器编译没有通过 , 那么就删除这个着色器

3.着色程序相关的API

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

(1)创建OpenGL程序

代码语言:javascript
复制
int program = GLES20.glCreateProgram();

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

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

代码语言:javascript
复制
GLES20.glGetError();

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

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

代码语言:javascript
复制
GLES20.glAttachShader(program, vertextShader);

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

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

(4)连接程序

代码语言:javascript
复制
GLES20.glLinkProgram(program);

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

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

(5)获取链接程序结果

代码语言:javascript
复制
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);

参数 : program , 程序的引用 ;

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

linkStatus , 存放结果的数组;

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

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

(6)删除着色程序

代码语言:javascript
复制
GLES20.glDeleteProgram(program);

参数 : 着色程序的引用;

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

4. 源码

代码语言:javascript
复制
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对象

代码语言:javascript
复制
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4)

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

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

代码语言:javascript
复制
vbb.order(ByteOrder.nativeOrder());

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

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

代码语言:javascript
复制
mVertexBuffer = vbb.asFloatBuffer();

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

代码语言:javascript
复制
mColorBuffer.put(colors);

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

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

代码语言:javascript
复制
mColorBuffer.position(0);

2. 初始化着色器相关api

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

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

代码语言:javascript
复制
int maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");

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

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

代码语言:javascript
复制
float[] muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

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

3. 绘制3D图形相关api

绘制三角形流程 : 

(1) 指定着色器程序

代码语言:javascript
复制
GLES20.glUseProgram(mProgram);

参数 : 着色程序的引用id

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

(2) 设置旋转初始情况

代码语言:javascript
复制
Matrix.setRotateM(float[] rm, int rmOffset, float a, float x, float y, float z)

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

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

(3) 设置位移

代码语言:javascript
复制
Matrix.translateM(float[] m, int mOffset, float x, float y, float z)

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

(4) 设置旋转矩阵

代码语言:javascript
复制
Matrix.rotateM(float[] m, int mOffset, float a, float x, float y, float z)

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

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

代码语言:javascript
复制
GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset)

参数 : 

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

代码语言:javascript
复制
		GLES20.glVertexAttribPointer(
				maPositionHandle, 
				3, 
				GLES20.GL_FLOAT, 
				false, 
				3 * 4, 
				mVertexBuffer
		);

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

(7) 启用传入的数据

代码语言:javascript
复制
GLES20.glEnableVertexAttribArray(maPositionHandle);

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

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

(8) 执行绘制方法

代码语言:javascript
复制
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);

该方法绘制三角形

4. 矩阵计算相关api

代码语言:javascript
复制
Matrix.multiplyMM(float[] result, int resultOffset, float[] lhs, int lhsOffset, float[] rhs, int rhsOffset)

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

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

5. 源码

代码语言:javascript
复制
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版本

代码语言:javascript
复制
GLSurfaceView.setEGLContextClientVersion(int version)

作用 : 设置OPenGL的版本号, version 是 2 , 就是设置OpenGLES2.0;

(2) 设置背景颜色

代码语言:javascript
复制
GLES20.glClearColor(0, 0, 0, 1.0f);

(3) 设置视口大小

代码语言:javascript
复制
GLES20.glViewport(int x, int y, int width, int height)

(4) 设置透视矩阵

代码语言:javascript
复制
Matrix.frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far)

参数 : m 投影矩阵; offset 投影矩阵起始位置; 剩下的参数为 左 右 下 上 近视点 远视点;

左 右 的值是宽高比, 左边为负数, 右边为正数;

(5) 设置摄像机参数

代码语言:javascript
复制
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) 清除深度缓冲与颜色缓冲

代码语言:javascript
复制
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

2. 源码

代码语言:javascript
复制
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) 设置界面为竖屏

代码语言:javascript
复制
 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

(2) 界面获取焦点

代码语言:javascript
复制
View.requestFocus()

(3) 设置可获取焦点

代码语言:javascript
复制
View.setFocusableInTouchMode(boolean focusableInTouchMode)

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

2. 源码

代码语言:javascript
复制
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();
    }
    
}

六. 着色器脚本

顶点着色器 : 

代码语言:javascript
复制
uniform mat4 uMVPMatrix; //总变换矩阵
attribute vec3 aPosition;  //顶点位置
attribute vec4 aColor;    //顶点颜色
varying  vec4 vColor;  //用于传递给片元着色器的变量

void main()     
{                            		
   gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
   vColor = aColor;//将接收的颜色传递给片元着色器 
}                      

片元着色器 : 

代码语言:javascript
复制
precision mediump float;
varying  vec4 vColor; //接收从顶点着色器过来的参数

void main()                         
{                       
   gl_FragColor = vColor;//给此片元颜色值
}

.

作者 :韩曙亮

转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835

案例下载地址 :  http://download.csdn.net/detail/han1202012/6651095 需要SDK-10 版本2.3.3

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2013-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 程序介绍
    • 1. 样例展示 
      • 2. 程序结构
        • 3. 方法介绍
          • (1) ShaderUtil方法
      • 二  ShaderUtils类介绍  
        • 1.安卓基本API
          • (1) 创建assets目录中文件的输入流
          • (2)带缓冲区的输出流
        • 2.着色器相关API介绍
          • (1)创建着色程器
          • (2)加载着色器源代码
          • (3)编译着色器
          • (4)获取着色器编译情况
          • (5)删除着色器
        • 3.着色程序相关的API
          • (1)创建OpenGL程序
          • (2)获取OpenGL中的错误信息
          • (3)向程序中加入着色器
          • (4)连接程序
          • (5)获取链接程序结果
          • (6)删除着色程序
        • 4. 源码
        • 三. Triangle 3D三角形数据
          • 1. 顶点数据容器相关api
            • (1) 创建ByteBuffer对象
            • (2) 设置字节缓冲区顺序
            • (3) 将字节缓冲区转为浮点缓冲区
            • (4) 向字节缓冲区中存入数据
            • (5)指定浮点型缓冲区起始位置
          • 2. 初始化着色器相关api
            • (1) 获取着色器属性变量引用
            • (2) 获取着色器一直变量引用
          • 3. 绘制3D图形相关api
            • (1) 指定着色器程序
            • (2) 设置旋转初始情况
            • (3) 设置位移
            • (4) 设置旋转矩阵
            • (5) 应用投影和视口变换
            • (6) 将顶点数据传进渲染管线
            • (7) 启用传入的数据
            • (8) 执行绘制方法
          • 4. 矩阵计算相关api
            • 5. 源码
            • 四. GLSurfaceView相关api
              • 1. 相关api
                • (1) 设置OpenGL版本
                • (2) 设置背景颜色
                • (3) 设置视口大小
                • (4) 设置透视矩阵
                • (5) 设置摄像机参数
                • (6) 清除深度缓冲与颜色缓冲
              • 2. 源码
              • 五 MainActivity相关
                • 1. 相关api
                  • (1) 设置界面为竖屏
                  • (2) 界面获取焦点
                  • (3) 设置可获取焦点
                • 2. 源码
                • 六. 着色器脚本
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档