Android OpenGL ES 学习(一)

  • Post author:
  • Post category:其他




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的使用细节很多,后面再慢慢补充吧。 万事开头难,坚持一定会有收获。



版权声明:本文为honeysx原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。