NO.3 OpenGl 着色器&绘制

零蚀


前言

  • 之前的代码用的是GLES3.0其实内部调用的还是GLES2.0的代码内容,所以,后面我还是用GLES.20来写,毕竟书上用的是2.0,至于3.0 它的改动可能和2.0 存在出入了,有些api都不一样,等到后续接触的时候再看看3.0的内容的改动处就OK。

  • 在这一周,每天抽出一小时休息时间,emmm,但是英文文档阅读真的很耗时间,有时候出问题还要看看context有没有漏掉的,然后理解查单词,有时候一页纸要停留好久,因为要好好揣摩一下,虽然耗时,但是也挺有意思的不是,还好英语还没忘光,不过还是挺依赖有道词典的。


将着色器关联到项目

  • 步骤&解释

    • 之前有用到了关于glsl是什么:这是OpenGL着色器的代码,一般我们可以在glClearColor之后进行着色器内容的编写,而编写的内容,是要通过获取着色器的代码的,所以通过流的方式将着色器的代码进行写入到内存,这就是我们source_code。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vfERCqB-1590196959395)(media/15900352439957/15901264148353.jpg)]

      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);
      
      }
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsJMYNgx-1590196959396)(media/15900352439957/15901537809173.jpg)]


OpenGL的平面坐标系&绘制

  • 解释
    • opengl的平面坐标系和Android的平面坐标是不一样的,其实我们的opengl的坐标系的点的范围是在[-1 , +1]之间。如下图所示,比Android 的y轴逆向坐标系是不是更任性化一点。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPnsUyRY-1590196959398)(media/15900352439957/15901957011149.jpg)]

    • 所以接下来我们就需要改动一下我们的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
          };
      }
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hgrPuJPt-1590196959400)(media/15900352439957/15901959460697.jpg)]

    • 我们去时画了什么出来,但是,点呢,为什么只有一条线,边框啥都没有,这是因为我们没有定义点的大小,在默认看来,点这个东西是没有大小的,所以我们要设置点的大小,如何设置当然要在着色器中设置。在我们原有设置坐标位置的代码之后添加设置点大小的参数。

      // simple_vertex_shader.glsl
      attribute vec4 a_Position;
      
      void main() {
          gl_Position = a_Position;
          gl_PointSize = 20.0;
      }
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsEK2hLO-1590196959402)(media/15900352439957/15901961882797.jpg)]


总结

  • 绘制流程
    • 最后用一个流程图来解释这个着色器绘制都做了什么,这部分就到这。大致内容就是如下流程,按照下图流程就能完成一个基本的绘制。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0M8M8F2j-1590198797674)(media/15900352439957/15901987152612.jpg)]


🔗 前言
🔗 OpenGL ES 篇
🔗 NO.1 OpenGL ES 前言
🔗 NO.2 从OpenGL与GC的矛盾说起
🔗 NO.4 OpenGl 色彩过渡&正投影
🔗 NO.5 OpenGl 第三维度
🔗 NO.6 OpenGl 纹理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零蚀zero eclipse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值