Android OpenGL顶点着色器
首先申明下,本文为笔者学习《OpenGL ES应用开发实践指南》的笔记,并加入笔者自己的理解和归纳总结。
1、OpenGL坐标
OpenGL会把屏幕映射到[-1, 1]的范围内。在OpenGL里,只能绘制点、直线以及三角形。三角形一般以逆时针顺序排列顶点。定义一个长方形,可以用两个三角形拼接而成。
2、数据存储
由于OpenGL运行在本地环境,而android运行在Davik虚拟机上,需要把android中的数据复制到本地内存块。FloatBuffer类可以用来保存顶点数据。
private static final int BYTES_PER_FLOAT = 4;
float[] tableVerticesWithTriangles;
// ByteBuffer.allocateDirect分配一块本地内存,不被垃圾回收
// 每个float类型有4个字节,本地内存的大小是tableVerticesWithTriangles.length * BYTES_PER_FLOAT
// order方法是确保按本地字节序排列保存
// put方法把数据从Davik的内存复制到本地内存
FloatBuffer vertexData = ByteBuffer
.allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(tableVerticesWithTriangles);
3、着色器文件
(1) 顶点着色器,生成每个顶点的最终位置,针对每个顶点,它都会执行一次。simple_vertex_shader.glsl文件
attribute vec4 a_Position;
void main()
{
gl_Position = a_Position;
gl_PointSize = 10.0;
}
vec4是包含4个分量的向量,可以认为是x,y,z,w,默认情况下,前三个坐标设为0,并把最后一个坐标设为1。着色器会有几个属性,关键字attribute是把这些属性放进着色器的手段。
main()是着色器的主要入口点,把前面定义过的位置复制到指定的输出变量gl_Position。
(2) 片段着色器,为组成点、线或者三角形的每个片段生成最终的颜色。
OpenGL把每个点、直线及三角形分解成大量的小片段,这些片段类似于屏幕上的像素,每一个都包含单一的纯色。对于每个片段,片段着色器都会被调用一次。simple_fragment_shader.glsl文件
precision mediump float;
uniform vec4 u_Color;
void main()
{
gl_FragColor = u_Color;
}
precision指定浮点数据类型精度,lowp、mediump和highp分别对应低精度、中等精度和高精度。uniform会让每个顶点都使用一个值,u_Color是4个分量的向量,对应红、绿、蓝和透明度。
main()是片段着色器的主要入口点,把uniform定义的颜色复制到指定的输出变量gl_FragColor,作为当前片段的最终颜色。
4、绘制着色器
(1) 加载着色器代码protected String readShaderFromRaw(int resId) {
BufferedReader br = null;
StringBuffer stringBuffer = new StringBuffer();
try {
br = new BufferedReader(new InputStreamReader(
getResources().openRawResource(resId)));
String line = null;
while ((line = br.readLine()) != null) {
stringBuffer.append(line + "\n");
}
} catch (IOException e) {
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
}
}
}
return stringBuffer.toString();
}
(2) 编译着色器代码protected int compileShader(int type, String shaderCode) {
// 创建一个新的着色器对象
int shaderObjectId = GLES20.glCreateShader(type);
if (shaderObjectId == 0) {
// 创建失败
return 0;
}
// 上传和编译着色器代码
GLES20.glShaderSource(shaderObjectId, shaderCode);
GLES20.glCompileShader(shaderObjectId);
// 获取编译状态
int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
// 获取着色器信息日志
LogUtil.log("OpenGL", GLES20.glGetShaderInfoLog(shaderObjectId));
if (compileStatus[0] == 0) {
// 如果失败,删除着色器对象
GLES20.glDeleteShader(shaderObjectId);
return 0;
}
return shaderObjectId;
}
(3) 链接程序对象protected int linkProgram(int vertexShaderId, int fragmentShaderId) {
// 创建一个新的程序对象
int programId = GLES20.glCreateProgram();
if (programId == 0) {
return 0;
}
// 新建程序对象附上着色器,并链接程序
GLES20.glAttachShader(programId, vertexShaderId);
GLES20.glAttachShader(programId, fragmentShaderId);
GLES20.glLinkProgram(programId);
// 获取链接状态
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, 0);
// 获取着程序链接信息日志
LogUtil.log("OpenGL", GLES20.glGetProgramInfoLog(programId));
if (linkStatus[0] == 0) {
// 如果链接失败,删除程序对象
GLES20.glDeleteProgram(programId);
return 0;
}
return programId;
}
(4) 验证程序对象protected boolean validateProgram(int programId) {
// 验证程序,只在开发阶段需要
GLES20.glValidateProgram(programId);
LogUtil.log("OpenGL", GLES20.glGetProgramInfoLog(programId));
int[] validateStatus = new int[1];
GLES20.glGetProgramiv(programId, GLES20.GL_VALIDATE_STATUS, validateStatus, 0);
return validateStatus[0] != 0;
}
(5) 使用程序对象
protected int useProgram(int vertexShaderResId, int fragmentShaderResId) {
String vertexShaderCode = readShaderFromRaw(vertexShaderResId);
String fragmentShaderCode = readShaderFromRaw(fragmentShaderResId);
int vertexShaderId = compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShaderId = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
int programId = linkProgram(vertexShaderId, fragmentShaderId);
validateProgram(programId);
GLES20.glUseProgram(programId);
return programId;
}
(6) 获取Uniform位置
private static final String U_COLOR = "u_Color";
uColorLocation = GLES20.glGetUniformLocation(mProgramId, U_COLOR);
(7) 获取Attribute位置
private static final String A_POSITION = "a_Position";
aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
(8) 关联顶点数据
vertexData.position(0);
GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT,
GLES20.GL_FLOAT, false, 0, vertexData);
glVertexAttribPointer(int indx, int size, int type, boolean normalized,int stride, java.nio.Buffer ptr)
- index是属性位置
- size是对每个属性的计数
- type是数据的类型
- normalized只对整型数据有意义
- stride只有当一个数组存储多于一个属性时,指定位移
- ptr是缓存数据
private class OpenGLVertexShaderRender implements GLSurfaceView.Renderer {
private static final String U_COLOR = "u_Color";
private static final String A_POSITION = "a_Position";
private static final int POSITION_COMPONENT_COUNT = 2;
private static final int BYTES_PER_FLOAT = 4;
private FloatBuffer vertexData;
private int mProgramId;
private int uColorLocation;
private int aPositionLocation;
OpenGLVertexShaderRender() {
float[] tableVerticesWithTriangles = {
// 左上角
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// 右下角
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
// 直线
-0.5f, 0f,
0.5f, 0f,
// 点
0f, -0.25f,
0f, 0.25f
};
// ByteBuffer.allocateDirect分配一块本地内存,不被垃圾回收,保存到FloatBuffer中
vertexData = ByteBuffer
.allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(tableVerticesWithTriangles);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mProgramId = useProgram(R.raw.simple_vertex_shader, R.raw.simple_fragment_shader);
// 获取Uniform的位置
uColorLocation = GLES20.glGetUniformLocation(mProgramId, U_COLOR);
// 获取Attribute位置
aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
vertexData.position(0);
// 关联顶点数据
GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT,
GLES20.GL_FLOAT, false, 0, vertexData);
GLES20.glEnableVertexAttribArray(aPositionLocation);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 绘制三角形,白色背景
GLES20.glUniform4f(uColorLocation, 1, 1, 1, 1);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
// 绘制直线,红色背景
GLES20.glUniform4f(uColorLocation, 1, 0, 0, 1);
GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);
// 绘制点,蓝色背景
GLES20.glUniform4f(uColorLocation, 0, 0, 1, 1);
GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1);
// 绘制点,红色背景
GLES20.glUniform4f(uColorLocation, 1, 0, 0, 1);
GLES20.glDrawArrays(GLES20.GL_POINTS, 9, 1);
}
}
显示如下