零蚀
前言
-
之前的代码用的是GLES3.0其实内部调用的还是GLES2.0的代码内容,所以,后面我还是用GLES.20来写,毕竟书上用的是2.0,至于3.0 它的改动可能和2.0 存在出入了,有些api都不一样,等到后续接触的时候再看看3.0的内容的改动处就OK。
-
在这一周,每天抽出一小时休息时间,emmm,但是英文文档阅读真的很耗时间,有时候出问题还要看看context有没有漏掉的,然后理解查单词,有时候一页纸要停留好久,因为要好好揣摩一下,虽然耗时,但是也挺有意思的不是,还好英语还没忘光,不过还是挺依赖有道词典的。
将着色器关联到项目
-
步骤&解释
-
之前有用到了关于glsl是什么:这是OpenGL着色器的代码,一般我们可以在
glClearColor
之后进行着色器内容的编写,而编写的内容,是要通过获取着色器的代码的,所以通过流的方式将着色器的代码进行写入到内存,这就是我们source_code。public class ReadShaderResouce { public static String getVertexShaderResource(Context context){ return readTextFileResource(context,R.raw.simple_vertex_shader); } public static String getFragmentShaderResource(Context context){ return readTextFileResource(context,R.raw.simple_fragment_shader); } private static String readTextFileResource(Context context,int resourceId){ StringBuilder body=new StringBuilder(); try { InputStream inputStream = context.getResources().openRawResource(resourceId); InputStreamReader reader = new InputStreamReader(inputStream); BufferedReader bufferedReader=new BufferedReader(reader); String line; while((line=bufferedReader.readLine())!=null){ body.append(line); body.append('\n'); } } catch (IOException e) { e.printStackTrace(); } return body.toString(); } }
-
glShaderSource
:当code(源代码)已经写到内存中后,我们需要将其绑定到我们获得的shader的handle句柄上,也就是,再之后我们就可以编译我们的着色器了。而之前用的glGetShaderiv
就是为了检查当前着色器的状态,是否编译成功(这部分的代码会在编译着色器之后)。// class ShaderHelp private static int compileVertexShader(Context context){ String shaderCode=ReadShaderResouce.getVertexShaderResource(context); return compileShader(GLES20.GL_VERTEX_SHADER,shaderCode); } private static int compileFragmentShader(Context context){ String shaderCode = ReadShaderResouce.getFragmentShaderResource(context); return compileShader(GLES20.GL_FRAGMENT_SHADER,shaderCode); } /*编译的着色器源码绑定到创建的着色器句柄上*/ private static int compileShader(int type,String code){ // 创建着色器,并获取handle int shaderObjectId= GLES20.glCreateShader(type); if(shaderObjectId==0){ Log.e(TAG,"执行的程序不在GLThread中,未能获取shader句柄"); }else{ GLES20.glShaderSource(shaderObjectId,code); } Log.e(TAG,"成功获取着色器的句柄"); GLES20.glCompileShader(shaderObjectId); // 检查编译着色器的状态 return checkGetShaderByHandle(shaderObjectId); }
-
compileShader
编译着色器,此时opengl会拿着一个type和着色器的源码(.glsl)进行编译,如果返回的数组的状态数据是0,则表示编译失败。当然除此之外还有更细致的编译api,例如:compileVertexShader
&compileFragmentShader
。// class ShaderHelp /** * 他会将测试的结果写在数组的第一个元素上。 * 这种形式在OpenGL内非常常见,会将result保存在第一个数组元素上。 * @param ShaderObjectId */ private static int checkGetShaderByHandle(int ShaderObjectId){ final int[] compileStatus = new int[1]; GLES20.glGetShaderiv(ShaderObjectId,GLES20.GL_COMPILE_STATUS,compileStatus,0); if(compileStatus[0]==0){ // 获取失败,回收掉着色器 Log.e(TAG,"着色器编辑失败"); GLES20.glDeleteShader(ShaderObjectId); // 打印编译时候发生的问题 LogOpenGlInfo(ShaderObjectId); return 0; } return ShaderObjectId; }
-
关联着色器:在编译结束之后,我们需要关联着色器,我们的着色器都是顶点着色器和片段着色器同步运行的,opengl需要顶点着色器,因为它需要通过顶点着色器来获取图像在屏幕上的位置。opengl也需要片段着色器,因为opengl需要通过他来了解如何绘制一个片段(点,线,三角形.etc),他不知道用什么颜色,该怎么画。(虽然他们协同使用,但是并不是1对1的关系),一般我们用这个方法来将着色器关联到项目:
glLinkProgram()
// class ShaderHelp /** * 关联着色器 */ public static int linkShader(Context context){ int programObjectId = getGLProgram(); int vertexShaderId = compileVertexShader(context); int fragmentShaderId=compileFragmentShader(context); // 关联项目和着色器 GLES20.glAttachShader(programObjectId,vertexShaderId); GLES20.glAttachShader(programObjectId,fragmentShaderId); // 关联到项目上 GLES20.glLinkProgram(programObjectId); // 检查其他情况 if(!validateProgram(programObjectId)){ return 0; } // 检测此时的链接状态 final int[] linkStatus =new int[1]; GLES20.glGetProgramiv(programObjectId,GLES20.GL_LINK_STATUS,linkStatus,0); if(linkStatus[0]==0){ Log.e(TAG,"关联项OpenGL目失败"); GLES20.glGetProgramInfoLog(programObjectId); GLES20.glDeleteProgram(programObjectId); return 0; } GLES20.glUseProgram(programObjectId); return programObjectId; } /** * 检查program异常情况,比如效率低,崩溃等,其实这个可以在创建program后的,这里我放在link的代码里,位置滞后了。 * @param programObjectId * @return */ private static boolean validateProgram(int programObjectId) { GLES20.glValidateProgram(programObjectId); final int[] validateStatus = new int[1]; GLES20.glGetProgramiv(programObjectId, GLES20.GL_VALIDATE_STATUS, validateStatus, 0); Log.v(TAG, "Results of validating program: " + validateStatus[0] + "\nLog:" + GLES20.glGetProgramInfoLog(programObjectId)); return validateStatus[0] != 0; }
-
那么把我们的着色器关联到什么上呢?其实我们是吧着色器关联到我们的opengl上了,而这里关联的OpenGL程序我们也定义为OpenGL特有的
program
,它通知opengl在屏幕上如何绘制,这里我们会用到一个方法来生成一个Program:glCreateProgram()
/** * 创建OpenGL的项目对象 * @return */ private static int getGLProgram(){ int programObjectId = GLES20.glCreateProgram(); if(programObjectId==0){ Log.e("zero","构建项目失败"); GLES20.glDeleteProgram(programObjectId); return 0; } Log.e(TAG,"构建项目成功"); return programObjectId; }
-
关联好部件后,我们开始传入进我们早就准备好的floatBuffer的数据,但是如何传输数据呢,这就要将我们之前的数据floatBuffer关联到我们的着色器的vertex里面的a_position和fragment里面的u_color。首先我们要获取到之前的glsl里面的参数。
//class ShaderHelp public static final String U_COLOR="u_Color"; // 获取fragment.glsl的参数 private int uColorLocation; // 获取fragment.glsl的参数对象 public static final String A_POSITION="a_Position"; private int positionLocation;// 获取vertex.glsl的参数 public static int getColorLocation(int programId){ // 在surfaceCreate之后调用,确保shade已经关联 return GLES20.glGetUniformLocation(programId,U_COLOR); } public static int getPositionLocation(int programId){ // 在surfaceCreate之后调用,确保shade已经关联 return GLES20.glGetAttribLocation(programId,A_POSITION); }
-
然后设置我们之前定义的floatBuffer的内容,设置读取的位置,用position()这个方法,然后关联上opengl的参数
private Context context; private static final int BYTES_FLOAT=4; // 每个顶点坐标具有的元素数量 private static final int POSITION_COMPONENT_COUNT=2; private int uColorLocation; ........ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { GLES20.glClearColor(1.0f,1.0f,0.0f,0.0f); int program =ShaderHelp.linkShader(context); if(program!=0){ vertexBuffer.position(0); // 读取数据之前需要制定读取的位置是从头开始的 int positionLocation = ShaderHelp.getPositionLocation(program); uColorLocation = ShaderHelp.getColorLocation(program); // 关联java和OpenGl的数据 GLES20.glVertexAttribPointer( positionLocation, POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false,0,vertexBuffer); GLES20.glEnableVertexAttribArray(positionLocation); } }
-
最后我们将内容绘制到屏幕上。到这一步但是并不能绘制我们的图形,只能看到下列黑色的图案,这是为什么呢,是因为我们的openGL不能找到对应的屏幕的物理坐标点。他不知道如何将自己的逻辑坐标点,投射到对应的实际物理坐标点上。结果就如下图所示。
// onDrawFrame @Override public void onDrawFrame(GL10 gl) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // 提供我们的颜色给fragment.glsl的片段着色器 GLES20.glUniform4f(uColorLocation , 1.0f, 1.0f, 1.0f, 1.0f); // 第一个参数是说我们想要绘制的是三角形, // 第二个参数是绘制三角形是从顶点数组开始 // 第三个参数是来说一共有6个顶点 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6); // 绘制直线 GLES20.glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f); GLES20.glDrawArrays(GLES20.GL_LINES,6,2); // 绘制蓝色曲棍点 GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f); GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1); // 绘制红色曲棍点 GLES20.glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f); GLES20.glDrawArrays(GLES20.GL_POINTS, 9, 1); }
-
OpenGL的平面坐标系&绘制
-
解释
-
opengl的平面坐标系和Android的平面坐标是不一样的,其实我们的opengl的坐标系的点的范围是在[-1 , +1]之间。如下图所示,比Android 的y轴逆向坐标系是不是更任性化一点。
-
所以接下来我们就需要改动一下我们的vertex的代码了。就可以看到一下的结果了,
public class AirHockeyConstant { static float tableVertices[]={ // Triangle one -0.5f,-0.5f, 0.5f,0.5f, -0.5f,0.5f, // Triangle two -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, // 定义中位线 -0.5f, 0f, 0.5f, 0f, // 定义曲棍球的曲棍位置 0f, -0.25f, 0f, 0.25f }; }
-
我们去时画了什么出来,但是,点呢,为什么只有一条线,边框啥都没有,这是因为我们没有定义点的大小,在默认看来,点这个东西是没有大小的,所以我们要设置点的大小,如何设置当然要在着色器中设置。在我们原有设置坐标位置的代码之后添加设置点大小的参数。
// simple_vertex_shader.glsl attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 20.0; }
-
总结
🔗 前言
🔗 OpenGL ES 篇
🔗 NO.1 OpenGL ES 前言
🔗 NO.2 从OpenGL与GC的矛盾说起
🔗 NO.4 OpenGl 色彩过渡&正投影
🔗 NO.5 OpenGl 第三维度
🔗 NO.6 OpenGl 纹理