代码链接: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的编写错误。