创建MainActivity
类继承于AppCompatActivity
(其他Activity也可以)
package com.yxf.triangle;
import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
//1,定义GLSurfaceView对象,这个View提供了OpenGL ES的显示窗口
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//2,创建GLSurfaceView对象
glSurfaceView = new GLSurfaceView(this);
//3,设置OpenGL ES版本为2.0
glSurfaceView.setEGLContextClientVersion(2);
//4,设置渲染器
glSurfaceView.setRenderer(new MyRenderer(this));
//5,设置GLSurfaceView为主窗口
setContentView(glSurfaceView);
}
@Override
protected void onPause() {
super.onPause();
//6.1,当Activity暂停时暂停glSurfaceView
glSurfaceView.onPause();
}
@Override
protected void onResume() {
super.onResume();
//6.2,当Activity恢复时恢复glSurfaceView
glSurfaceView.onResume();
}
}
在onPause()
和onResume()
中的6.1和6.2代码非常重要,千万别忘记加哦
在MainActivity同目录下创建一个MyRenderer
类
实现android.opengl.GLSurfaceView.Renderer
接口(注意Renderer有好几个别搞错了)
package com.yxf.triangle;
import android.content.Context;
import android.opengl.GLSurfaceView;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
//1,静态导入OpenGL ES2.0包
import static android.opengl.GLES20.*;
public class MyRenderer implements GLSurfaceView.Renderer {
//5,定义顶点坐标所需数字数量,二维图形只需要x,y两个 , 三维需要x,y,z三个.
//我们先画个二维的三角形,所以顶点坐标所需数字常量设置为2.
private static final int POSITION_COMPONENT_COUNT = 2;
//6,设置三角形的三个顶点坐标: 左下(-0.5f,0f),右下(0.5f,0f),中上(0f,0.5f)
private float[] triangleVertices = {
-0.5f,0f,
0.5f,0f,
0f,0.5f,
};
//7,定义Native的浮点数储存所需字节数
private static final int BYTES_PER_FLOAT = 4;
//8,定义Native浮点数缓存的引用
private final FloatBuffer vertexData;
private Context context;
public MyRenderer(Context context) {
this.context = context;
//9,将三角形顶点数据储存到Native的浮点缓存中
//注意必须使用ByteBuffer.allocateDirect而不是ByteBuffer.allocate,前者分配的才是不会被垃圾回收的本地内存
vertexData = ByteBuffer.allocateDirect(triangleVertices.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(triangleVertices);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//2,设置清空屏幕用的颜色
glClearColor(0f, 1f, 0f, 0f);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//3,设置视口尺寸
glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
//4,根据步骤2设置的颜色清空屏幕,注意如果不清空会保留之前的绘制,所以这步一般是必要的
glClear(GL_COLOR_BUFFER_BIT);
}
}
步骤1,后续的OpenGL ES方法都将以这种方式调用,这将极大节约我们的开发时间.
在res中创建raw目录
在raw目录中添加文件triangle_vertex_shader.glsl
代码如下
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
模糊的解释下,我们将会把我们在MyRenderer
中定义的顶点传进a_Position
中,然后着色器将a_Position
赋值给gl_Position
,gl_Position
便是OpenGL
所认定的最终顶点
在raw目录中添加文件triangle_fragment_shader.glsl
代码如下
precision mediump float;
uniform vec4 u_Color;
void main(){
gl_FragColor = u_Color;
}
简单解释下precision mediump float
定义浮点数精度为中等精度,
后续程序会将颜色值赋值给u_Color
,
然后u_Color
赋值给gl_FragColor
,
OpenGL
根据gl_FragColor
给片段着色
创建CommonUtils
类如下
package com.yxf.triangle;
import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
//1,静态导入导入OpenGL ES 2.0常用方法
import static android.opengl.GLES20.*;
public class CommonUtils {
private static final String TAG = "CommonUtils";
/**
* 用于读取GLSL Shader文件内容
* @param context
* @param resId
* @return
*/
public static String readTextFromResource(Context context, int resId) {
StringBuilder builder = new StringBuilder();
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader reader = null;
try {
inputStream = context.getResources().openRawResource(resId);
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
String nextLine;
while ((nextLine = reader.readLine()) != null) {
builder.append(nextLine);
builder.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return builder.toString();
}
/**
* 编译着色器
* @param type
* @param source
* @return
*/
public static int compileShader(int type, String source) {
final int shaderId = glCreateShader(type);
if (shaderId == 0) {
//2,如果着色器创建失败则会返回0
Log.w(TAG, "Could not create new shader");
return 0;
}
//3,将Shader源文件加载进ID为shaderId的shader中
glShaderSource(shaderId, source);
//4,编译这个shader
glCompileShader(shaderId);
final int[] status = new int[1];
//5,获取编译状态储存于status[0]
glGetShaderiv(shaderId, GL_COMPILE_STATUS, status, 0);
Log.v(TAG, "compile source : \n" + source + "\n" +
"info log : " + glGetShaderInfoLog(shaderId));
if (status[0] == 0) {
//6,检查状态是否正常,0为不正常
Log.w(TAG, "Compilation of shader failed.");
return 0;
}
return shaderId;
}
/**
* 编译顶点着色器
* @param source
* @return
*/
public static int compileVertexShader(String source) {
return compileShader(GL_VERTEX_SHADER, source);
}
/**
*编译片段着色器
* @param source
* @return
*/
public static int compileFragmentShader(String source) {
return compileShader(GL_FRAGMENT_SHADER, source);
}
}
更新MyRenderer
的onSurfaceCreated
如下
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//2,设置清空屏幕用的颜色
glClearColor(0f, 1f, 0f, 0f);
//9,读取shader源文件
String vertexShaderSource = CommonUtils.readTextFromResource(context, R.raw.triangle_vertex_shader);
String fragmentShaderSource = CommonUtils.readTextFromResource(context, R.raw.triangle_fragment_shader);
//10,编译shader 源文件
int vertexShader = CommonUtils.compileVertexShader(vertexShaderSource);
int fragmentShader = CommonUtils.compileFragmentShader(fragmentShaderSource);
}
在CommonUtils
添加如下方法
/**
* 创建OpenGL对象,并添加着色器,返回OpenGL对象Id
* @param vertexShaderId
* @param fragmentShaderId
* @return
*/
public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
//7,创建OpenGL对象
final int programId = glCreateProgram();
if (programId == 0) {
Log.w(TAG, "Create OpenGL program failed");
return 0;
}
//8,在program上附上着色器
glAttachShader(programId, vertexShaderId);
glAttachShader(programId, fragmentShaderId);
//9,链接程序
glLinkProgram(programId);
final int[] status = new int[1];
glGetProgramiv(programId, GL_LINK_STATUS, status, 0);
Log.v(TAG, "Results of linking program : \n" + glGetProgramInfoLog(programId));
if (status[0] == 0) {
Log.w(TAG, "Link program failed");
return 0;
}
return programId;
}
这个方法主要是创建OpenGL的Program对象,然后将着色器链接进Program中,并返回Program的Id;
更新MyRenderer
,在类中添加全局变量
//11,添加program对象Id定义
private int program;
更新MyRenderer.onSurfaceCreated
,在onSurfaceCreated
方法末尾添加如下代码
//12,将shader添加进program,并获得Program的Id
program = CommonUtils.linkProgram(vertexShader, fragmentShader);
//13,使用这个program
glUseProgram(program);
首先获得Shader中u_Color
和a_Position
的位置
在MyRenderer
类中添加全局变量
//14,定义u_Color和a_Position相关变量
private static final String U_COLOR = "u_Color"; private int uColorLocation;
private static final String A_POSITION = "a_Position";
private int aPositionLocation;
然后在MyRenderer.onSurfaceCreated
最后添加
//15,获得u_Color和a_Position的位置
//一个Uniform,一个Attribute别搞错了
uColorLocation = glGetUniformLocation(program, U_COLOR);
aPositionLocation = glGetAttribLocation(program, A_POSITION);
现在我们已经获得u_Color
和a_Position
的位置
接下来将数据写入其中吧
在MyRenderer.onSurfaceCreated
最后添加
//16,根据a_Position的位置将顶点数据传给a_Position
vertexData.position(0);
glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT, false, 0, vertexData);
//17,激活顶点数组
glEnableVertexAttribArray(aPositionLocation);
终于可以画三角形喽
在MyRenderer.onDrawFrame
最后添加
//18,更新着色器中u_Color的值(r,g,b,a) = (255,255,255,255)
glUniform4f(uColorLocation, 1f, 1f, 1f, 1f);
//19,告诉OpenGL,画三角形,从开头读顶点数据开始读,读三个顶点
glDrawArrays(GL_TRIANGLES, 0, 3);
运行程序,会得到如下效果
想想是不是哪里不对劲?
记得我们的三个顶点吗?
(-0.5f,0f),(0.5f,0),(0,0.5f)
这根据数学计算不应该是一个直角三角形吗?可是为什么最终的图形却不是直角三角形呢?
原来OpenGL的坐标系并不是按长度算的,而是按比例算的,也就是说OpenGL取屏幕中间为(0f,0f),x坐标是从[-1,1],y坐标范围也是从[-1,1],这样的话在竖屏模式下,以我手机(1080*1920)为例,明显y的1个单位长度(960px)就比x轴的1单位长度((540px)长多了,这就导致了最终不是直角三角形了.
那么如果遇到需要明确的设置长度的时候怎么办呢?
请听下回分解
OpenGL-Triangle-1
OpenGL ES应用开发实践指南 Android卷
GLSL Shader常见属性说明
Android OpenGL ES 部分方法说明
OpenGL ES初探(二) – 用OpenGL画一个三角形(2)