一、前言:
上一篇文章我们使用了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");}// 关闭 BufferedReaderbufferedReader.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和fragmentShaderString 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() {// 使用programGLES30.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程序写得不够规范,后续章节逐渐补齐。