代码链接:https://github.com/smzhldr/AGLFramework

一、前言

这部分内是学习OpenGL的第一部分,类似于一个“Hello World”的程序,一开始并没有打算写这部分的基础知识,但鉴于基础知识的重要性,我觉得还是有必要总结一下的,对于初学者能有一定的帮助和辅导。

二、基础知识一览

1.shader基础

shader语法跟C语言语法接近,例如以下是两个最简单的shader:

//顶点着色器
 attribute vec2 vPosition;            // 顶点位置属性vPosition
 void main()
 {                         
     gl_Position = vec4(vPosition,0,1); // 确定顶点位置
 }

// 片元着色器
precision mediump float;     // 声明float类型的精度为中等(精度越高越耗资源)
uniform vec4 uColor;              // uniform的属性uColor
void main(){                     
    gl_FragColor = uColor;         // 给此片元的填充色
}

看到这里是不是觉得挺熟悉,至少不用学习一门新语言的语法了。
需要强调的事顶点着色器和片元着色器是成对出现的,不能单独的使用。

更多内容大家可参考书籍,文档,源码等进行学习,这里只做抛砖引玉的概念。重点学士使用。

也可以参阅:写的内容很详细。

shader使用流程如下

整个流程其实就是创建Shader和创建Program两个子流程。创建Shader的流程如下:

    调用glCreateShader()创建一个Shader对象。
    调用glShaderSource()创建加载Shader脚本源码。
    调用glCompileShader()编译Shader脚本。

然后创建好的Shader需要被挂载到Program中,创建Program的流程如下:

    调用glCreateProgram()创建一个Program对象。
    调用glAttachShader()将创建好的Shader进行挂载。
    调用glLinkProgram()执行链接操作。
    最后在需要使用Shader时,调用glUseerProgram()应用当前Shader。

2.画一个三角形这里分为两部分

除了shader语法就是如何使用了,使用前先获取相应program的参数地址:

vPosition = GLES20.glGetAttribLocation(programId, "vPosition");
uColor = GLES20.glGetUniformLocation(programId, "uColor");

然后调用glUseerProgram(), 将参数传入存储中,调用回执方法就ok:

GLES20.glUseProgram(programId);
 GLES20.glVertexAttribPointer(vPosition, 2,GLES20.GL_FLOAT, false, 0, buffer);
 // 允许顶点位置数据数组
 GLES20.glEnableVertexAttribArray(vPosition);
 // 设置属性uColor(颜色 索引,R,G,B,A)
 GLES20.glUniform4f(uColor, 0.0f, 1.0f, 0.0f, 1.0f);
 // 绘制
 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
 
 GLES20.glDisableVertexAttribArray(vPosition);

到此为止,就可以绘制出一个三角形了,虽然使用写的简单,但绝大多数OpenGL程序都遵循这个使用流程,知识代码的复杂程度不一样而已。

3、经典的OpenGL 使用流程

创建一个shader程序编译帮助类GlHelper

public class GlHelper {
 //创建GL程序
    public static int createGlProgram(String vertexSource, String fragmentSource){
        int vertex=loadShader(GLES20.GL_VERTEX_SHADER,vertexSource);
        if(vertex==0)
            return 0;
        int fragment=loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentSource);
        if(fragment==0)
            return 0;
        int program= GLES20.glCreateProgram();
        if(program!=0){
            GLES20.glAttachShader(program,vertex);
            GLES20.glAttachShader(program,fragment);
            GLES20.glLinkProgram(program);
            int[] linkStatus=new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS,linkStatus,0);
            if(linkStatus[0]!= GLES20.GL_TRUE){
                glError(1,"Could not link program:"+ GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program=0;
            }
        }
        return program;
    }

    //加载shader
    public static int loadShader(int shaderType,String source){
        int shader= GLES20.glCreateShader(shaderType);
        if(0!=shader){
            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){
                glError(1,"Could not compile shader:"+shaderType);
                glError(1,"GLES20 Error:"+ GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader=0;
            }
        }
        return shader;
    }
}

使用GlHelper编译并使用shader

Class HelloRender implements GLSurfaceView.Renderer {
    // 顶点着色器的脚本
    private static final String verticesShader
                    = "attribute vec2 vPosition;            \n" // 顶    点位置属性vPosition
                    + "void main(){                         \n"
                    + "   gl_Position = vec4(vPosition,0,1);\n" // 确定顶点位置
                    + "}";

    // 片元着色器的脚本
    private static final String fragmentShader
                    = "precision mediump float;         \n" // 声明float类型的精度为中等(精度越高越耗资源)
                    + "uniform vec4 uColor;             \n" // uniform的属性uColor
                    + "void main(){                     \n"
                    + "   gl_FragColor = uColor;        \n" // 给此片元的填充色
                    + "}";

    private Context context;
    private FloatBuffer buffer;
    float[] triangleCoods={
            0.5f,0.5f,
           -0.5f,-0.5f,
            0.5f,-0.5f };

    public HelloRender(Context context){
        this.context = context;
        buffer= ByteBuffer.allocateDirect(triangleCoods.length*4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        buffer.put(triangleCoods);
        buffer.position(0);
    }
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //编译链接   
        programId = GlHelper.createGlProgram(verticesShader, fragmentShader);
		//获取shader变量的地址
        vPosition = GLES20.glGetAttribLocation(programId, "vPosition");
        uColor = GLES20.glGetUniformLocation(programId, "uColor");
    }

 	@Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0,0,width,height);
    }
    
    @Override
    public void onDrawFrame(GL10 gl) {            
 		GLES20.glUseProgram(programId);
		GLES20.glVertexAttribPointer(vPosition, 2,GLES20.GL_FLOAT, false, 0, buffer);
				 // 允许顶点位置数据数组
		GLES20.glEnableVertexAttribArray(vPosition);
				 // 设置属性uColor(颜色 索引,R,G,B,A)
		GLES20.glUniform4f(uColor, 0.0f, 1.0f, 0.0f, 1.0f);
				 // 绘制
		GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
 
		GLES20.glDisableVertexAttribArray(vPosition);
	}
}

在activity中的调用

public class GlTriangleActivity extends AppCompatActivity {

    GLSurfaceView glSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setEGLContextClientVersion(2);
        glSurfaceView.setRenderer(new HelloRender(this));
        setContentView(glSurfaceView);
    }

    @Override
    protected void onResume() {
        super.onResume();
        glSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        glSurfaceView.onPause();
    }
}

这样一来就可以画出三角形在屏幕上,内容虽然简单,但却是基础中的基础。

4、防止采坑

所有关于GLES相关的方法都应该在OpenGl线程中调用,以GLSurfaceView为例,都应该写在onSurfaceCreate,onSurfaceChange,onDrawFrame这三个方法中,具体原因有兴趣的可以研究下,这里只当做预防采坑的手段。
此外,在OpenGL程序运行没效果的时候先检查shader编译有没有通过,对于初学者往往是因为shader的编写错误。