opengl es 相关概念
opengl es 全称 Open Graphics Library for Embedded Systems ,是 OpenGL 三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。该API由Khronos集团定义推广,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准。(摘自百度百科)。
概念很抽象,先记住 opengl 可以做出很多牛逼的特效就可以了。 只有熟练使用opengl后才会对opengl的概念有更加深刻的理解。
opengl es 初步使用
使用opengl 绘图需要用到两个基础的类,GLSurfaceView与GLSurfaceView的内部接口类Renderer,其中GLSurfaceView继承SurfaceView,用于显示渲染的图像,Renderer主要用于渲染图像。 在Renderer中调用opengl进行相应的绘制。
private MyGLSurfaceView mGlSurfaceView;
private MyRenderer mMyRenderer;
private boolean mRendererHasSet;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGlSurfaceView = new MyGLSurfaceView(this);
setContentView(mGlSurfaceView);
if (isSupportES20()) {
mGlSurfaceView.setEGLContextClientVersion(2);
mMyRenderer = new MyRenderer(this);
mGlSurfaceView.setRenderer(mMyRenderer);
mGlSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
mRendererHasSet = true;
} else {
Toast.makeText(this, "不支持opengl es 2.0", Toast.LENGTH_SHORT).show();
}
}
// 判断设备是否支持opengl es 2.0
private boolean isSupportES20() {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo deviceConfigurationInfo = activityManager.getDeviceConfigurationInfo();
return deviceConfigurationInfo.reqGlEsVersion >= 0x20000;
}
着色器
着色器分为顶点着色器和片元着色器两类,顶点着色器顾名思义是给顶点上色的,而片元着色器是给顶点之间的像素上色的,片元着色器的粒度更小。 着色器使用gpu对元素进行渲染,故在绘制及一些特效处理方面效率比较高。
着色器的语法接近C规范,且在编译时比较严格,故在写着色器代码时一定要小心各种规范问题。比如浮点数一定要写成带小数点的形式的。
如果应用在最后显示的结果不是预期的,那么可以看下着色器代码有没有报错。
顶点着色器基本写法如下:
attribute vec4 a_Position;
void main() {
// gl_Position是着色器内建变量
gl_Position = a_Position;
}
片元着色器基本写法如下:
uniform vec4 u_Color;
void main() {
// gl_FragColor是着色器内建变量
gl_FragColor = u_Color;
}
着色器一般以文本文件的形式放在raw文件夹下,有的也直接以字符串的形式写在代码中。 之所以可以这么做,是因为着色器最后都是以字符串的形式加载到opengl的。 可以用如下的代码将着色器代码读到字符串中
public static String readTextFromResource(Context context, int resId) {
StringBuilder sb = new StringBuilder();
try {
InputStream inputStream = context.getResources().openRawResource(resId);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader reader = new BufferedReader(inputStreamReader);
String nextLine;
while ((nextLine = reader.readLine()) != null) {
sb.append(nextLine);
sb.append("\n");
}
} catch (Exception e) {
}
return sb.toString();
}
加载着色器
加载着色器一般分为三步,即创建着色器、链接着色器源码、编译着色器。
private static int compileShader(int type, String shaderCode) {
// 1、创建着色器, type是指顶点还是片元。
final int shaderObjId = GLES20.glCreateShader(type);
if (shaderObjId == 0) {
Log.d(TAG, "compileShader: could not create shader with type: " + type);
return 0;
}
// 2、链接着色器源码,其中源码以字符串的形式给出。
GLES20.glShaderSource(shaderObjId, shaderCode);
// 3、编译着色器
GLES20.glCompileShader(shaderObjId);
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shaderObjId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == 0) {
GLES20.glDeleteShader(shaderObjId);
Log.i(TAG, "compileShader: failed compile shader: " + GLES20.glGetShaderInfoLog(shaderObjId));
return 0;
}
// 返回的是着色器的引用id。
return shaderObjId;
}
一般第一步和第三步要进行检查代码是否出错,并打印出相应的日志,方便在开发过程中定位问题。
加载opengl程序
加载opengl程序也分为三步,即创建opengl程序、链接着色器(包括顶点和片元)、链接opengl程序。
public static int linkProgram(int vertextShaderId, int fragmentShaderId) {
// 1、创建opengl程序
int programId = GLES20.glCreateProgram();
if (programId == 0) {
Log.d(TAG, "linkProgram: create program failed " + GLES20.glGetProgramInfoLog(programId));
return 0;
}
// 2、链接顶点着色器与片元着色器
GLES20.glAttachShader(programId, vertextShaderId);
GLES20.glAttachShader(programId, fragmentShaderId);
// 3、链接opengl程序
GLES20.glLinkProgram(programId);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] == 0) {
GLES20.glDeleteProgram(programId);
return 0;
}
// 返回opengl程序的引用id
return programId;
}
同加载着色器一样,加载opengl程序也一般在第一步和第三步进行检查并打印相关日志。
向着色器传递值
如下代码中,获取着色程序后,便可以使用opengl程序引用id来获取顶点着色器和片元着色器中的变量对应的引用,这样便可以传递顶点数据和颜色数据到opengl,从而绘制相应的图形。
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(1f, 0f, 0f, 0f);
String vertexShaderSource = TextResourceReader.readTextFromResource(mContext, R.raw.simple_vertex_shader);
String fragmentShaderSource = TextResourceReader.readTextFromResource(mContext, R.raw.simple_fragment_shader);
int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource);
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderSource);
mProgram = ShaderHelper.linkProgram(vertexShader, fragmentShader);
glUseProgram(mProgram);
// 通过aPositionHandle可以将数据传递到着色器中的变量a_Position
aPositionHandle = glGetAttribLocation(mProgram, "a_Position");
// 由于opengl是直接使用gpu渲染故在使用顶点数据时不能使用java层的,必需使用native层,故要通过ByteBuffer来转换一下。
// 注意这里是allocateDirect,不是allocate。 使用不当可能会报native order的错误。
mTableFloatBuffer = ByteBuffer.allocateDirect(mTablevertex.length * BYTE_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();
mTableFloatBuffer.put(mTablevertex);
mTableFloatBuffer.position(0);
// 指定aPositionHandle使用的数据指针,也及数据的位置。
glVertexAttribPointer(aPositionHandle, POSITION_COMPONENT_SIZE, GL_FLOAT, false, STRIDE, mTableFloatBuffer);
// 使能aPositionHandle,也即可以绘制了。 不调用这个就不会绘制。
glEnableVertexAttribArray(aPositionHandle);
uColor = glGetUniformLocation(mProgram, "u_Color");
// aColor = glGetAttribLocation(mProgram, "a_Color");
mTableFloatBuffer.position(POSITION_COMPONENT_SIZE);
glVertexAttribPointer(aColor, COLOR_COMPONENT_SIZE, GL_FLOAT, false, STRIDE, mTableFloatBuffer);
glEnableVertexAttribArray(aColor);
}
绘制
数据传到着色器中后,绘制便比较容易了。 主要代码如下:
@Override
public void onDrawFrame(GL10 gl) {
// 清空屏幕
glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 将颜色传到着色器中的uniform变量。 四个参数对应red,green, blue,alpha分量,取值范围均为[0,1]
glUniform4f(uColor, 1.0f, 1.0f, 1.0f, 1.0f);
// 绘制多个三角形,绘制模式还有三角形带,点,线等。
glDrawArrays(GL_TRIANGLE_FAN, 0, 10);
}
其中绘制模式有多种方式可选,其中三角形带和三角形扇是为了省略构建图形的三角形顶点设计的(比如挨着的顶点可以共用),也可以将每个三角开的顶点均指定,最后以绘制三角形的方式来绘制(对应的模式为GL_TRIANGLES)。
总结
opengl的使用最主要是正确的处理着色器,一般的步骤如下:
1、加载着色器
2、加载opengl程序(依赖上一步的着色器)
3、传递数据到opengl
4、调用绘制命令绘制。
如果在屏幕上没有显示预期的图像,那么可以通过日志查看是不是着色器代码有错误(如浮点数必需要以小数点的形式表达)或着色器及opengl程序是否正确加载,最后检查对着色器的变量引用获取是不是正确的。
opengl的使用细节很多,后面再慢慢补充吧。 万事开头难,坚持一定会有收获。