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程序写得不够规范,后续章节逐渐补齐。