不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
【OpenGL ES】构建可移植、易扩展的代码书写结构
MainActivity
package com.example.openglndk;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
//JNI类
public class MainActivity extends AppCompatActivity {
private GLSurfaceView mGLSurfaceView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLSurfaceView = new GLSurfaceView(this);
setContentView(mGLSurfaceView);
//设置版本ES 3.0
mGLSurfaceView.setEGLContextClientVersion(3);
//设置渲染器 JAVA版本
GLSurfaceView.Renderer renderer = new MyRenderer(this);
//设置渲染器 C++版本
// GLSurfaceView.Renderer renderer = new WaveRenderer(this);
mGLSurfaceView.setRenderer(renderer);
}
}
JAVA语言
分离GLSL语言
-
创建
assets
文件夹
-
在
assets
文件夹中创建glsl
文件
创建工具类
-
工具类中有三个方法。
readFileFromAssets
/** * 读取assets文件夹中的文件 提取着色器命令 * * @param fileName 文件名 * @param context 上下文环境 * @return */ public static String readFileFromAssets(Context context, String fileName) { //初始化,关流判断 InputStream inputStream = null; Reader reader = null; BufferedReader bufferedReader = null; //底层更快,性能更好 StringBuilder shaderCode = new StringBuilder(); try { //得到资源中的asset数据流 inputStream = context.getResources().getAssets().open(fileName); //字节输入流 reader = new InputStreamReader(inputStream);// 字符流 bufferedReader = new BufferedReader(reader); //缓冲流 String temp; //从第一行数据读取到最后一行为止 while ((temp = bufferedReader.readLine()) != null) { //连起来,成为字符串 shaderCode.append(temp); } } catch (Exception e) { e.printStackTrace(); } finally { //先打开的后关闭,后打开的先关闭 if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return shaderCode.toString(); }
LoadShader
/** * 创建、编译着色器 * * @param type 顶点着色器:GLES30.GL_VERTEX_SHADER * 片段着色器:GLES30.GL_FRAGMENT_SHADER * @param shaderCode * @return */ public static int LoadShader(int type, String shaderCode) { //创建一个着色器 final int shaderId = GLES30.glCreateShader(type); if (shaderId != 0) { //加载到着色器 GLES30.glShaderSource(shaderId, shaderCode); //编译着色器 GLES30.glCompileShader(shaderId); //检测状态 final int[] compileStatus = new int[1]; GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] == 0) { String logInfo = GLES30.glGetShaderInfoLog(shaderId); System.err.println(logInfo); //创建失败 GLES30.glDeleteShader(shaderId); return 0; } return shaderId; } else { //创建失败 return 0; } }
linkProgram
/** * 链接统一管理程序 * * @param vertexShader 顶点着色器 * @param fragmentShader 片段着色器 * @return */ public static int linkProgram(int vertexShader, int fragmentShader) { final int program = GLES30.glCreateProgram(); if (program != 0) { //将顶点着色器加入到程序 GLES30.glAttachShader(program, vertexShader); //将片元着色器加入到程序中 GLES30.glAttachShader(program, fragmentShader); //链接着色器程序 GLES30.glLinkProgram(program); final int[] linkStatus = new int[1]; GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] == 0) { String logInfo = GLES30.glGetProgramInfoLog(program); System.err.println(logInfo); GLES30.glDeleteProgram(program); return 0; } return program; } else { //创建失败 return 0; } }
传入环境
C++语言
分离GLSL语言
和JAVA版本的一样。
创建工具类
在工具类中需加入下列头文件。
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <malloc.h>
#include <iostream>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
-
工具类中还是三个方法。
readFileFromAssets
/** * 读取文件 * @param env jni环境 * @param assetManager 资源 * @param fileName 文件名 * @return 数据字符串 */ char *readFileFromAssets(JNIEnv *env, jobject assetManager, char *fileName) { //创建资源 AAssetManager *mgr = AAssetManager_fromJava(env, assetManager); if (mgr == NULL) { return nullptr; } //根据文件名打开资源文件 AAsset *asset = AAssetManager_open(mgr, fileName, AASSET_MODE_UNKNOWN); if (NULL == asset) { return nullptr; } //获取文件长度 long size = AAsset_getLength(asset); //获取内存,存储数据 char *shaderCode = (char *) malloc(sizeof(char) * size + 1); //结尾符号 shaderCode[size] = '\0'; //读取资源文件到buffer缓冲区中 AAsset_read(asset, shaderCode, size); //不可释放内存,释放了数据就没了 // free(shaderCode); //释放资源 AAsset_close(asset); //返回数据 return shaderCode; }
LoadShader
/**创建和编译着色器 * */ GLuint LoadShader(int type, char *shaderCode) { //创建一个着色器 GLuint shader = glCreateShader(type); if (shader != 0) { //加载到着色器 glShaderSource(shader, 1, &shaderCode, NULL); //编译着色器 glCompileShader(shader); //检测状态 GLint compileStatus; glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); if (compileStatus == 0) { //声明log长度变量 GLint infoLen = 0; //获取长度并赋值 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 1) { //创建成功小于等于1 char *infoLog = static_cast<char *>(malloc(sizeof(char) * infoLen)); glGetShaderInfoLog(shader, infoLen, NULL, infoLog); cout << "SHADER ERROR!" << endl; free(infoLog); } //创建失败 glDeleteShader(shader); return 0; } return shader; } else { //创建失败 return 0; } }
linkProgram
/**链接统一管理程序 * */ GLuint linkProgram(GLuint vertexShader, GLuint fragmentShader) { GLuint program = glCreateProgram(); if (program != 0) { //将顶点着色器加入到程序 glAttachShader(program, vertexShader); //将片元着色器加入到程序中 glAttachShader(program, fragmentShader); //链接着色器程序 glLinkProgram(program); //检测状态 GLint linkStatus; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (linkStatus == 0) { GLint infoLen = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 1) { char *infoLog = static_cast<char *>(malloc(sizeof(char) * infoLen)); glGetProgramInfoLog(program, infoLen, NULL, infoLog); cout << "PROGRAM ERROR!" << endl; free(infoLog); } glDeleteProgram(program); return 0; } return program; } else { //创建失败 return 0; } }
传入环境
总结
根据上述划分,可以分为五部分:
GLSL
语句文件。- 工具类来实现读取文件、创建和编译着色器、链接统一管理程序等功能。
- 在构造方法中,利用上下文环境来使用这些方法。
- 在
surfaceChanged
中确定图形尺寸。 - 在
drawFrame
中进行图形绘制。
如果是
GLSL
语句发生变化,改变其文件即可;绘制图形变化,改变drawFrame
方法即可。所以这样只要哪一个部分改变,修改那个部分即可,实现了分离,利于维护和扩展代码。