自学内容网 自学内容网

Android显示系统(05)- OpenGL ES - Shader绘制三角形(使用glsl文件)

Android显示系统(02)- OpenGL ES - 概述
Android显示系统(03)- OpenGL ES - GLSurfaceView的使用
Android显示系统(04)- OpenGL ES - Shader绘制三角形
Android显示系统(05)- OpenGL ES - Shader绘制三角形(使用glsl文件)
Android显示系统(06)- OpenGL ES - VBO和EBO和VAO
Android显示系统(07)- OpenGL ES - 纹理Texture
Android显示系统(08)- OpenGL ES - 图片拉伸

一、前言:

上一篇文章我们使用了Shader绘制了一个基本的三角形,但是,发现那样写Shader程序特别麻烦,各种加双引号,还没有语法高亮提示。因为glsl也和java、c++一样是一门语言,实际工程项目都是单独的glsl文件管理的,本节我们整改下之前的项目。

二、整改步骤:

  • 新建assets目录,管理glsl资源文件;
  • 新增ShaderController类来操作glsl文件;
  • Shader代码移植到glsl文件当中;

三、编码:

1、创建glsl文件:

  • 新建assets目录:

    在这里插入图片描述

    在这里插入图片描述

  • 新建glsl文件:

    在这里插入图片描述

2、编写Shader程序:

顶点着色器:

文件路径:.\app\src\main\assets\triangle_vertex.glsl

attribute vec4 vPosition;

void main() {
  gl_Position = vPosition;
}

片元着色器:

文件路径:.\app\src\main\assets\triangle_fragment.glsl

precision mediump float;

uniform vec4 vColor;
void main() {
  gl_FragColor = vColor;
}

3、 新建ShaderController类管理Shader程序:

文件路径:com/example/glsurfaceviewdemo/ShaderController.java

package com.example.glsurfaceviewdemo;

import android.content.Context;
import android.opengl.GLES30;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ShaderController {
  /**
   * 从 assets 文件夹中读取指定文件的内容并返回为字符串
   *
   * @param filename 文件名
   * @param context  上下文对象
   * @return 读取的文件内容字符串
   */
  public static String loadShaderCodeFromFile(String filename, Context context) {
      // 用于存储读取的着色器代码的字符串
      StringBuilder shaderCode = new StringBuilder();
      try {
          InputStream inputStream = context.getAssets().open(filename);
          // 使用 BufferedReader 包装输入流,以便逐行读取文件内容
          BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

          String line;
          // 逐行读取文件内容并将每行内容追加到 shaderCode 中
          while ((line = bufferedReader.readLine()) != null) {
              shaderCode.append(line).append("\n");
          }
          // 关闭 BufferedReader
          bufferedReader.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
      // 返回读取的文件内容字符串
      return shaderCode.toString();
  }

  // 创建并编译着色器
  public static int compileShader(int type, String shaderCode) {
      // 创建一个着色器
      int shader = GLES30.glCreateShader(type);
      // 将着色器代码设置到着色器对象中
      GLES30.glShaderSource(shader, shaderCode);
      // 编译着色器
      GLES30.glCompileShader(shader);
      return shader;
  }

  /**
   * 创建 OpenGL Program 对象,用于链接顶点着色器和片段着色器
   *
   * @param vertexShader   顶点着色器源代码
   * @param fragmentShader 片段着色器源代码
   * @return 创建的 OpenGL Program 对象 ID
   */
  public static int createGLProgram(String vertexShader, String fragmentShader) {
      // 编译生成顶点着色器
      int vShader = compileShader(GLES30.GL_VERTEX_SHADER, vertexShader);
      if (vShader == 0) {
          Log.e("GLProgram", "Failed to compile vertex shader.");
          return 0; // 返回0表示创建失败
      }
      // 编译生成片元着色器
      int fShader = compileShader(GLES30.GL_FRAGMENT_SHADER, fragmentShader);
      if (fShader == 0) {
          Log.e("GLProgram", "Failed to compile fragment shader.");
          GLES30.glDeleteShader(vShader); // 删除已经生成的顶点着色器
          return 0;
      }

      // 创建一个OpenGL程序
      int program = GLES30.glCreateProgram();
      if (program == 0) {
          Log.e("GLProgram", "Failed to create OpenGL program.");
          GLES30.glDeleteShader(vShader);
          GLES30.glDeleteShader(fShader);
          return 0;
      }

      // attach两个编译好的着色器到program当中
      GLES30.glAttachShader(program, vShader);
      GLES30.glAttachShader(program, fShader);

      // 链接OpenGL程序
      GLES30.glLinkProgram(program);

      // 检查链接结果是否成功
      int[] linkStatus = new int[1];
      GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0);
      if (linkStatus[0] == 0) {
          Log.e("GLProgram", "Failed to link program: " + GLES30.glGetProgramInfoLog(program));
          GLES30.glDeleteProgram(program);
          GLES30.glDeleteShader(vShader);
          GLES30.glDeleteShader(fShader);
          return 0;
      }

      // 删除着色器,因为已经链接到程序中,不再需要保留
      GLES30.glDeleteShader(vShader);
      GLES30.glDeleteShader(fShader);

      Log.i("GLProgram", "GL program created successfully.");
      return program;
  }
}

4、修改原来的Triangle类:

文件路径:`com/example/glsurfaceviewdemo/Triangle.java`

```java
package com.example.glsurfaceviewdemo;

import android.content.Context;
import android.opengl.GLES30;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL;

public class Triangle {
    // 顶点数据是float类型,因此,使用这个存储
    private FloatBuffer mVertexBuffer;
    private int mProgram;
    // 定义的三角形顶点坐标数组
    private final float[] mTriangleCoords = new float[]{
            0.0f, 0.2f, 0.0f,   // 顶部
            -0.5f, -0.5f, 0.0f, // 左下角
            0.5f, -0.5f, 0.0f   // 右下角
    };

    public Triangle(Context context) {
        // 1.初始化顶点缓冲区,存储三角形坐标
        // 为顶点坐标分配DMA内存空间
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);
        // 设置字节顺序为本地字节顺序(会根据硬件架构自适应大小端)
        byteBuffer.order(ByteOrder.nativeOrder());
        // 将字节缓冲区转换为浮点缓冲区
        mVertexBuffer = byteBuffer.asFloatBuffer();
        // 将顶点三角形坐标放入缓冲区
        mVertexBuffer.put(mTriangleCoords);
        // 设置缓冲区的位置指针到起始位置
        mVertexBuffer.position(0);

        // 2.加载并编译vertexShader和fragmentShader
        String vertexShaderCode = ShaderController.loadShaderCodeFromFile("triangle_vertex.glsl", context);
        String fragmentShaderCode = ShaderController.loadShaderCodeFromFile("triangle_fragment.glsl", context);

        // 3.创建一个OpenGL程序,并链接程序
        mProgram = ShaderController.createGLProgram(vertexShaderCode, fragmentShaderCode);
    }

    // 定义的fragment的颜色数组,表示每个像素的颜色
    private final float[] mColor = new float[]{0.0f, 1.0f, 0.0f, 1.0f};
    // 顶点着色器的位置句柄
    private int mPositionHandle = 0;
    // 片元着色器的位置句柄
    private int mColorHandle = 0;
    private final int COORDS_PER_VERTEX = 3;

    public void draw() {
        // 使用program
        GLES30.glUseProgram(mProgram);
        // 获取顶点着色器的位置句柄
        mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
        // 启用顶点属性数组
        GLES30.glEnableVertexAttribArray(mPositionHandle);
        // 准备三角形坐标数据
        // 重置缓冲区位置
        mVertexBuffer.position(0);
        // 指定顶点属性数据的格式和位置
        GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, mVertexBuffer);

        // 获取片元着色器的颜色句柄
        mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
        // 设置绘制三角形的颜色
        GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);
        // 绘制三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);
        // 禁用顶点属性数组
        GLES30.glDisableVertexAttribArray(mPositionHandle);
    }
}
```

四、运行:

在这里插入图片描述

五、小结:

本文主要是讲原来的shader代码拆分到对应的glsl文件中去,为了保证连贯性,Shader没有增减语句,但是,实际工程中来说,这个shader程序写得不够规范,后续章节逐渐补齐。


原文地址:https://blog.csdn.net/Ziwubiancheng/article/details/144278611

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!