OpenGL是跨平台2D/3D图形API,而OpenGL ES是在前者基础上扩展的版本,适用于手机等嵌入式设备。具有以下版本:
版本 | 解释 |
---|---|
OpenGL ES 1.0 | 2003年发布 |
OpenGL ES 1.1 | 支持了多纹理,顶点缓冲对象等 |
OpenGL ES 2.0 | 2007年发布,基于OpenGL 2.0,删除了1.x中的固定管线(pipeline),提供可编程的管线 |
OpenGL ES 3.0 | 2012年发布,向后兼容2.x |
OpenGL ES 3.1 | 2014年发布 |
OpenGL ES 3.2 | 2015年发布 |
OpenGL ES 1.x 和OpenGL ES2.0不兼容的,是完全两种不同的实现。
OpenGL EX 1.x仅支持固定管线渲染,可以固件支持或软件模拟,但渲染能力有限。
OpenGL EX 2.X采用可编程的渲染管线,提高了渲染能力。但编程难度也提高了,且要求设备中必须有对应GPU硬件支持。
上面图3-16和图3-21分别是OpenGL 1.x和OpenGL 2.x渲染管线过程示意图。
“变换和光照”---->“顶点着色器”
“纹理环境和颜色求和”、“雾”、“Alpha测速”---->“片元着色器”
本系列文章将主要介绍OpenGL ES2.x
在认识了OpenGL ES基础后,下面直接带来一个入门级Demo(本程序官网也有类似的可以学习)。代码中添加了注释,可以更好帮助入门学习。
public class ShaderUtil {
private static final String TAG = "ShaderUtil";
/**
* 加载着色器编码进GPU并编译
*
* @param shaderType 着色器类型
* @param source 着色器脚本字符串
* @return
*/
public static int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "loadShader: ES20_ERROR,Could not compile shader " + shaderType + ";");
Log.e(TAG, "loadShader: ES20_ERROR,shader = " + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
/**
* 创建着色器程序
*
* @param vertexSource
* @param fragmentSource
* @return
*/
public static int createProgram(String vertexSource, String fragmentSource) {
//加载顶点着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
//加载片元着色器
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
int program = GLES20.glCreateProgram();
if (program != 0) {
GLES20.glAttachShader(program, vertexShader);
checkGLError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGLError("glAttachShader");
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(TAG, "createProgram: ES20_ERROR,Could not link program");
Log.e(TAG, "createProgram: ES20_ERROR,program = " + GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
/**
* 检测每一步操作是否有错误的方法
*
* @param op
*/
public static void checkGLError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.d(TAG, ">>>>>> " + op + " :glError " + error);
throw new RuntimeException(op + ":glError " + error);
}
}
/**
* 从sh脚本中加载着色器内容
*
* @param fname
* @param resources
* @return
*/
public static String loadFromAssetsFile(String fname, Resources resources) {
String result = null;
try {
InputStream in = resources.getAssets().open(fname);
int ch = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((ch = in.read()) != -1) {
baos.write(ch);//获取的信息写入流
}
byte[] buffer = baos.toByteArray();
baos.close();
in.close();
result = new String(buffer, "UTF-8");
result = result.replaceAll("\\r\\n", "\n");
} catch (IOException e) {
e.printStackTrace();
}
Log.d(TAG, "loadFromAssetsFile: result = " + result);
return result;
}
}
public class Triangle {
public static float[] mProjMatrix = new float[16];//4x4投影矩阵
public static float[] mVMatrix = new float[16];//摄像机位置朝向的参数矩阵
public static float[] mMvPMatrix;//总变换矩阵
private int program;//自定义渲染管线着色器程序id
private int muMVPMatrixHandle;//总变换矩阵引用
private int maPositionHandle;//顶点位置属性引用
private int maColorHandle;//顶点颜色属性引用
private String mVertexShader;//顶点着色器代码脚本
private String mFragmentShader;//片元着色器代码脚本
private static float[] mMMatrix = new float[16];//具体物体的3D变换矩阵,包括旋转、平移、缩放
private FloatBuffer mVertexBuffer;//顶点坐标数据缓冲
private FloatBuffer mColorBuffer;//顶点着色数据缓冲
private int vCount = 0;//顶点数量
public float xAngle = 0;//绕X轴旋转的角度
public Triangle(MyTDView myTDView) {
initVertexData();
initShader(myTDView);
}
/**
* 初始化着色器
* @param myTDView
*/
private void initShader(MyTDView myTDView) {
mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", myTDView.getResources());
mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", myTDView.getResources());
program = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
maPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
maColorHandle = GLES20.glGetAttribLocation(program, "aColor");
muMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
}
/**
* 绘制三角形
*/
public void drawSelf() {
GLES20.glUseProgram(program);
Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);//初始化变换矩阵
Matrix.translateM(mMMatrix, 0, 0, 0, 1);//设置沿Z轴正向位移
Matrix.rotateM(mMMatrix, 0, xAngle, 1, 0, 0);//设置绕X轴旋转
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFinalMatrix(mMMatrix), 0);
GLES20.glVertexAttribPointer(maPositionHandle, 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);
}
/**
* 初始化顶点数据
*/
private void initVertexData() {
vCount = 3;
final float UNIT_SIZE = 0.2f;//设置单位长度
//顶点坐标数组
float vertices[] = new float[]{
-4 * UNIT_SIZE, 0, 0,
0, -4 * UNIT_SIZE, 0,
4 * UNIT_SIZE, 0, 0
};
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());//设置字节顺序为本地操作系统顺序
mVertexBuffer = vbb.asFloatBuffer();//转换为浮点型缓冲
mVertexBuffer.put(vertices);//在缓冲区内写入数据
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);
cbb.order(ByteOrder.nativeOrder());
mColorBuffer = cbb.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
}
/**
* 最终变换矩阵
*
* @param spec
* @return
*/
public static float[] getFinalMatrix(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和GLSurfaceView.Render的使用
public class MyTDView extends GLSurfaceView {
private static final String TAG = "MyTDView";
//每次三角形旋转的角度
private final float ANGLE_SPAN = 0.375f;
//线程
private RotateThread mRotateThread;
//自定义渲染器
private SceneRenderer mSceneRenderer;
public MyTDView(Context context) {
super(context);
//使用OpenGLES2.0
this.setEGLContextClientVersion(2);
mSceneRenderer = new SceneRenderer();
this.setRenderer(mSceneRenderer);
this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
private class SceneRenderer implements GLSurfaceView.Renderer {
Triangle mTriangle;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0, 0, 0, 1.0f);//设置屏幕背景颜色
mTriangle = 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);//设置视口
float ratio = width / height;
//设置透视投影
Matrix.frustumM(Triangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);
//设置摄像机
Matrix.setLookAtM(Triangle.mVMatrix, 0, 0, 0, 3,
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);
mTriangle.drawSelf();
}
}
private class RotateThread extends Thread {
//设置循环标识位
public boolean flag = true;
@Override
public void run() {
super.run();
while (flag) {
mSceneRenderer.mTriangle.xAngle = mSceneRenderer.mTriangle.xAngle + ANGLE_SPAN;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
顶点着色器和片元着色器资源放置在assets目录下。本案例使用的如下所示:
precision mediump float;
varying vec4 vColor;
void main(){
gl_FragColor = vColor;
}
uniform mat4 uMVPMatrix;//总变换矩阵
attribute vec3 aPosition;//顶点位置
attribute vec4 aColor;//顶点颜色
varying vec4 vColor;//用于传递给片元着色器的易变变量
void main(){
gl_Position = uMVPMatrix * vec4(aPosition,1);//根据总变换矩阵计算此次绘制顶点的位置
vColor = aColor;
}
public class MainActivity extends AppCompatActivity {
private MyTDView mTDView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mTDView = new MyTDView(this);
mTDView.requestFocus();
mTDView.setFocusableInTouchMode(true);
setContentView(mTDView);
}
@Override
protected void onResume() {
super.onResume();
mTDView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mTDView.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
本示例程序可以直接运行,查看效果。