【我的OpenGL学习进阶之旅】如何抽取着色器代码到assets目录下的GLSL文件,以及如何通过Java或者C++代码来加载着GLSL文件?
目录
- 一、着色器代码以字符串形式写在代码里的现状
- 二、抽取着色器代码为单独的GLSL文件
- 2.1 Java中的字符串抽取为GLSL文件并加载
- 2.1.1 Java中的字符串抽取为GLSL文件
- 2.1.2 Java加载GLSL文件
- 2.2 C++中的字符串抽取为GLSL文件并加载
- 2.2.1 C++中的字符串为抽取GLSL文件
- 2.2.2 C++加载GLSL文件
- 三、总结
关于着色器的相关知识,可以参考我之前的博客介绍:
- 【我的OpenGL学习进阶之旅】着色器和程序(上)------着色器
- 【我的OpenGL学习进阶之旅】着色器编译器和程序二进制码
- 【我的OpenGL学习进阶之旅】OpenGL ES 着色语言 (上)
- 【我的OpenGL学习进阶之旅】OpenGL ES 着色语言 (下)
- 【我的OpenGL学习进阶之旅】OpenGL ES 着色语言的IDE插件(Android Studio和Visutal Studio)以及常见GLSL文件扩展名介绍
- 【我的OpenGL学习进阶之旅】解决着色器编译错误:#version directive must occur on the first line of the shader
一、着色器代码以字符串形式写在代码里的现状
网上大部分的例子,都是把着色器代码直接定义成字符串的形式,写出来。如下所示:
- C++ 代码
// 顶点着色器const char* VERTEX_SHADER_TRIANGLE ="#version 300 es \n""layout(location = 0) in vec4 vPosition; \n""void main() \n""{ \n"" gl_Position = vPosition; \n""} \n";// 片段着色器const char* FRAGMENT_SHADER_TRIANGLE ="#version 300 es \n""precision mediump float; \n""out vec4 fragColor; \n""void main() \n""{ \n"" fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 ); \n""} \n";
- Java代码
// 顶点着色器
String vShaderStr ="#version 300 es \n"+ "in vec4 vPosition; \n"+ "void main() \n"+ "{ \n"+ " gl_Position = vPosition; \n"+ "} \n";// 片段着色器
String fShaderStr ="#version 300 es \n"+ "precision mediump float; \n"+ "out vec4 fragColor; \n"+ "void main() \n"+ "{ \n"+ " fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 ); \n"+ "} \n";
好吧,不知道为啥,我看到类似的代码很厌恶,完全不支持语法关键字高亮,看着贼难受!
二、抽取着色器代码为单独的GLSL文件
为此,我把着色器代码都单独抽取为GLSL文件,并使用下面博客介绍的插件
【我的OpenGL学习进阶之旅】OpenGL ES 着色语言的IDE插件(Android Studio和Visutal Studio)以及常见GLSL文件扩展名介绍
实现了语法高亮的效果。
2.1 Java中的字符串抽取为GLSL文件并加载
2.1.1 Java中的字符串抽取为GLSL文件
Java抽取GLSL文件,如下所示:
- 顶点着色器
#version 300 es
// 表示OpenGL ES着色器语言V3.00// 使用in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。
// 声明一个输入属性数组:一个名为vPosition的4分量向量
// 在图形编程中我们经常会使用向量这个数学概念,因为它简明地表达了任意空间中的位置和方向,并且它有非常有用的数学属性。
// 在GLSL中一个向量有最多4个分量,每个分量值都代表空间中的一个坐标,它们可以通过vec.x、vec.y、vec.z和vec.w来获取。
//注意vec.w分量不是用作表达空间中的位置的(我们处理的是3D不是4D),而是用在所谓透视除法(Perspective Division)上。
in vec4 vPosition;
void main()
{// 为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position变量,它在幕后是vec4类型的。// 将vPosition输入属性拷贝到名为gl_Position的特殊输出变量// 每个顶点着色器必须在gl_Position变量中输出一个位置,这个位置传递到管线下一个阶段的位置gl_Position = vPosition;
}
- 片段着色器
#version 300 es
// 表示OpenGL ES着色器语言V3.00// 声明着色器中浮点变量的默认精度
precision mediump float;
// 声明一个输出变量fragColor,这是一个4分量的向量,
// 写入这个变量的值将被输出到颜色缓冲器
out vec4 fragColor;
void main()
{// 所有片段的着色器输出都是红色( 1.0, 0.0, 0.0, 1.0 )fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );// 会输出橘黄色// fragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
可以看到语法是高亮的,看起来就舒服。
2.1.2 Java加载GLSL文件
既然抽取出去了,那就得加载它。
我们调用如下代码,指定GLSL的相对路径即可,如下所示:
// 得到的结果就是一个程序对象,我们可以调用glUseProgram函数,用刚创建的程序对象作为它的参数,以激活这个程序对象mProgramObject = ESShader.loadProgramFromAsset(mContext,"shaders/vertexShader.vert","shaders/fragmentShader.frag");
ESShader内部有个readShader方法来读取assets目录下的文件,如下所示:
/*** brief Read a shader source into a String* @param context context* @param fileName fileName Name of shader file* @return A String object containing shader source, otherwise null*/private static String readShader(Context context, String fileName) {StringBuilder sb = new StringBuilder();try {InputStream is = context.getAssets().open(fileName);BufferedReader br = new BufferedReader(new InputStreamReader(is));String line;while ((line = br.readLine()) != null) {sb.append(line).append("\n");}} catch (IOException e) {e.printStackTrace();}return sb.toString();}
ESShader.java的完整代码如下:
// ESShader
//
// Utility functions for loading GLSL ES 3.0 shaders and creating program objects.
//package com.openglesbook.common;import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;import android.content.Context;
import android.opengl.GLES30;
import android.util.Log;public class ESShader {/*** brief Read a shader source into a String* @param context context* @param fileName fileName Name of shader file* @return A String object containing shader source, otherwise null*/private static String readShader(Context context, String fileName) {StringBuilder sb = new StringBuilder();try {InputStream is = context.getAssets().open(fileName);BufferedReader br = new BufferedReader(new InputStreamReader(is));String line;while ((line = br.readLine()) != null) {sb.append(line).append("\n");}} catch (IOException e) {e.printStackTrace();}return sb.toString();}/*** brief Load a shader, check for compile errors, print error messages to output log* @param type Type of shader (GL_VERTEX_SHADER or GL_FRAGMENT_SHADER)* @param shaderSrc shaderSrc Shader source string* @return A new shader object on success, 0 on failure*/public static int loadShader(int type, String shaderSrc) {int shader;int[] compiled = new int[1];// Create the shader object// 调用glCreateShader将根据传入的type参数插件一个新的顶点着色器或者片段着色器shader = GLES30.glCreateShader(type);if (shader == 0) {return 0;}// Load the shader source// glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数 着色器真正的源码GLES30.glShaderSource(shader, shaderSrc);// Compile the shader// 编译着色器GLES30.glCompileShader(shader);// Check the compile status// 检测编译时的状态,是编译错误还是编译成功// pname: 获得信息的参数,可以为// GL_COMPILE_STATUS// GL_DELETE_STATUS// GL_INFO_LOG_LENGTH// GL_SHADER_SOURCE_LENGTH// GL_SHADER_TYPEGLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);// 如果着色器编译成功,结果将是GL_TRUE。如果编译失败,结果将为GL_FALSE,编译错误将写入信息日志if (compiled[0] == 0) {// 用glGetShaderInfoLog检索信息日志Log.e("ESShader", GLES30.glGetShaderInfoLog(shader));// 删除着色器对象GLES30.glDeleteShader(shader);return 0;}return shader;}/*** brief Load a vertex and fragment shader, create a program object, link program. Errors output to log.* @param vertShaderSrc Vertex shader source code* @param fragShaderSrc Fragment shader source code* @return A new program object linked with the vertex/fragment shader pair, 0 on failure*/public static int loadProgram(String vertShaderSrc, String fragShaderSrc) {int vertexShader;int fragmentShader;int programObject;int[] linked = new int[1];// Load the vertex/fragment shadersvertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertShaderSrc);if (vertexShader == 0) {return 0;}fragmentShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragShaderSrc);if (fragmentShader == 0) {GLES30.glDeleteShader(vertexShader);return 0;}// Create the program objectprogramObject = GLES30.glCreateProgram();if (programObject == 0) {return 0;}GLES30.glAttachShader(programObject, vertexShader);GLES30.glAttachShader(programObject, fragmentShader);// Link the programGLES30.glLinkProgram(programObject);// Check the link statusGLES30.glGetProgramiv(programObject, GLES30.GL_LINK_STATUS, linked, 0);if (linked[0] == 0) {Log.e("ESShader", "Error linking program:");Log.e("ESShader", GLES30.glGetProgramInfoLog(programObject));GLES30.glDeleteProgram(programObject);return 0;}// Free up no longer needed shader resourcesGLES30.glDeleteShader(vertexShader);GLES30.glDeleteShader(fragmentShader);return programObject;}/*** brief Load a vertex and fragment shader from "assets", create a program object, link program. Errors output to log.* @param context context* @param vertexShaderFileName Vertex shader source file name* @param fragShaderFileName Fragment shader source file name* @return A new program object linked with the vertex/fragment shader pair, 0 on failure*/public static int loadProgramFromAsset(Context context, String vertexShaderFileName, String fragShaderFileName) {int vertexShader;int fragmentShader;int programObject;int[] linked = new int[1];String vertShaderSrc = null;String fragShaderSrc = null;// Read vertex shader from assetsvertShaderSrc = readShader(context, vertexShaderFileName);System.out.println(" vertShaderSrc = " + vertShaderSrc);if (vertShaderSrc == null) {return 0;}// Read fragment shader from assetsfragShaderSrc = readShader(context, fragShaderFileName);System.out.println(" fragShaderSrc = " + fragShaderSrc);if (fragShaderSrc == null) {return 0;}// Load the vertex shadervertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertShaderSrc);if (vertexShader == 0) {GLES30.glDeleteShader(vertexShader);return 0;}// Load the fragment shaderfragmentShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragShaderSrc);if (fragmentShader == 0) {GLES30.glDeleteShader(fragmentShader);return 0;}// Create the program objectprogramObject = GLES30.glCreateProgram();if (programObject == 0) {return 0;}// 在OpenGL ES3.0中,每个程序对象必须连接一个顶点着色器和一个片段着色器// 把之前编译的着色器附加到程序对象上// 着色器可以在任何时候连接-----在连接到程序之前不一定需要编译,甚至可以没有源代码。// 唯一要求是:每个程序对象必须有且只有一个顶点着色器和一个片段着色器与之连接// 除了连接着色器之外,你还可以用glDetachShader断开着色器的连接GLES30.glAttachShader(programObject, vertexShader);GLES30.glAttachShader(programObject, fragmentShader);// Link the program// 链接操作负责生成最终的可执行的程序。// 一般来说,链接阶段是生成在硬件上运行的最终硬件指令的时候GLES30.glLinkProgram(programObject);// Check the link status 检测链接着色器程序是否失败// pname 获取信息的参数,可以是// GL_ACTIVE_ATTRIBUTES 返回顶点着色器中活动属性的数量// GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 返回最大属性名称的最大长度(以字符数表示),这一信息用于确定存储属性名字符串所需的内存量// GL_ACTIVE_UNIFORM_BLOCK 返回包含活动统一变量的程序中的统一变量块数量// GL_ACTIVE_UNIFORM_BLOCK_MAX_LENGTH 返回包含活动统一变量的程序中的统一变量块名称的最大长度// GL_ACTIVE_UNIFORMS 返回活动统一变量的数量// GL_ACTIVE_UNIFORM_MAX_LENGTH 返回最大统一变量名称的最大长度// GL_ATTACHED_SHADERS 返回连接到程序对象的着色器数量// GL_DELETE_STATUS 查询返回程序对象是否已经标记为删除// GL_LINK_STATUS 检查链接是否成功// GL_INFO_LOG_LENGTH 程序对象存储的信息日志长度// GL_LINK_STATUS 链接是否成功// GL_PROGRAM_BINARY_RETRIEVABLE_HINT 返回一个表示程序目前是否启用二进制检索提示的值// GL_TRANSFORM_FEEDBACK_BUFFER_MODE 返回GL_SEPARATE_ATTRIBS 或 GL_INTERLEAVED_ATTRIBS 表示变化反馈启用时的缓冲区模式// GL_TRANSFORM_FEEDBACK_VARYINGS 返回程序的变化反馈模式中捕捉的输出变量// GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 返回程序的变化反馈模式中捕捉的输出变量名称的最大长度// GL_VALIDATE_STATUS 查询最后一个校验操作的状态GLES30.glGetProgramiv(programObject, GLES30.GL_LINK_STATUS, linked, 0);if (linked[0] == 0) {Log.e("ESShader", "Error linking program:");// 获取着色器对象的信息日志Log.e("ESShader", GLES30.glGetProgramInfoLog(programObject));// 删除一个程序对象GLES30.glDeleteProgram(programObject);return 0;}// Free up no longer needed shader resourcesGLES30.glDeleteShader(vertexShader);GLES30.glDeleteShader(fragmentShader);return programObject;}
}
2.2 C++中的字符串抽取为GLSL文件并加载
2.2.1 C++中的字符串为抽取GLSL文件
抽取出去的GLSL文件和Java抽取的类似,如下所示:
- 顶点着色器
#version 300 es
// 表示OpenGL ES着色器语言V3.00// 使用in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。
// 声明一个输入属性数组:一个名为vPosition的4分量向量
// 在图形编程中我们经常会使用向量这个数学概念,因为它简明地表达了任意空间中的位置和方向,并且它有非常有用的数学属性。
// 在GLSL中一个向量有最多4个分量,每个分量值都代表空间中的一个坐标,它们可以通过vec.x、vec.y、vec.z和vec.w来获取。
//注意vec.w分量不是用作表达空间中的位置的(我们处理的是3D不是4D),而是用在所谓透视除法(Perspective Division)上。
layout(location = 0) in vec4 vPosition;
void main()
{// 为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position变量,它在幕后是vec4类型的。// 将vPosition输入属性拷贝到名为gl_Position的特殊输出变量// 每个顶点着色器必须在gl_Position变量中输出一个位置,这个位置传递到管线下一个阶段的位置gl_Position = vPosition;
}
- 片段着色器
#version 300 es
// 表示OpenGL ES着色器语言V3.00// 声明着色器中浮点变量的默认精度
precision mediump float;
// 声明一个输出变量fragColor,这是一个4分量的向量,
// 写入这个变量的值将被输出到颜色缓冲器
out vec4 fragColor;void main()
{//在计算机图形中颜色被表示为有4个元素的数组:红色、绿色、蓝色和alpha(透明度)分量,//通常缩写为RGBA。当在OpenGL或GLSL中定义一个颜色的时候,//我们把颜色每个分量的强度设置在0.0到1.0之间。//比如说我们设置红为1.0f,绿为1.0f,我们会得到两个颜色的混合色,即黄色。//这三种颜色分量的不同调配可以生成超过1600万种不同的颜色!// 所有片段的着色器输出都是红色( 1.0, 0.0, 0.0, 1.0 )fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );// 会输出橘黄色// fragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
可以看到语法是高亮的,看起来就舒服。
2.2.2 C++加载GLSL文件
既然抽取出去了,那就得加载它。
我们调用如下代码,指定GLSL的相对路径即可,如下所示:
void NativeTriangle::create() {GLUtils::printGLInfo();// Main ProgramVERTEX_SHADER = GLUtils::openTextFile("vertex/vertex_shader_hello_triangle.glsl");FRAGMENT_SHADER = GLUtils::openTextFile("fragment/fragment_shader_hello_triangle.glsl");mProgram = GLUtils::createProgram(&VERTEX_SHADER, &FRAGMENT_SHADER);if (!mProgram) {LOGD("Could not create program")return;}// 设置清除颜色glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
}
GLUtils::openTextFile 代码如下所示:
char *GLUtils::openTextFile(const char *path) {char *buffer;FUN_BEGIN_TIME("GLUtils::openTextFile")AAsset *asset = loadAsset(path);if (asset == nullptr) {LOGE("Couldn't load %s", path)return nullptr;}off_t length = AAsset_getLength(asset);buffer = new char[length + 1];int num = AAsset_read(asset, buffer, length);AAsset_close(asset);if (num != length) {LOGE("Couldn't read %s", path)delete[] buffer;return nullptr;}buffer[length] = '\0';FUN_END_TIME("GLUtils::openTextFile")return buffer;
}
有用到 loadAsset方法,代码如下:
#include <android/asset_manager_jni.h>
typedef AAsset esFile;static JNIEnv *sEnv = nullptr;
static jobject sAssetManager = nullptr;void GLUtils::setEnvAndAssetManager(JNIEnv *env, jobject assetManager) {sEnv = env;sAssetManager = assetManager;
}static AAsset *loadAsset(const char *path) {AAssetManager *nativeManager = AAssetManager_fromJava(sEnv, sAssetManager);if (nativeManager == nullptr) {return nullptr;}return AAssetManager_open(nativeManager, path, AASSET_MODE_UNKNOWN);
}
代码引入了 <android/asset_manager_jni.h>
,然后通过AAssetManager
来打开asset文件
对了,得注意调用setEnvAndAssetManager
方法,不然会引起空指针异常
void MyGLRenderContext::OnSurfaceCreated(JNIEnv *env, jobject assetManager) {LOGD("MyGLRenderContext::OnSurfaceCreated")// 初始化设置assetManager 一定要记得初始化,否则会报空指针异常GLUtils::setEnvAndAssetManager(env, assetManager);// ... 其他操作
}
三、总结
通过上面的介绍,我们可以把着色器代码从Java或C++的字符串中抽取出去到assets目录下的GLSL文件,然后可以很方便加载抽取出去的GLSL文件。
对于我来说,我觉得有以下几个好处:
编写GLSL代码,配合对于的GLSL插件,可以语法高亮,并且可以智能提示,写起来很顺手
很多GLSL代码是可以复用的,抽取出去之后,可以在不同的特效实现中复用它。比如加载常规Texture的着色器代码就是通用的,完全可以复用。
当然抽取出去之后需要通过文件读取的方式去加载它,可能有一定的io操作,性能会有所损坏。但是一般只会加载一次,也就还好!如下图所,有时候加载两个着色器耗时为0ms。
当然有时候也会多一点,第一次启动程序的时候用了6ms
以上示例代码,可以在下面的github上找到
- java版本代码: https://github.com/ouyangpeng/opengles3-book
- Native版本代码:https://github.com/ouyangpeng/OpenGLESDemo
【我的OpenGL学习进阶之旅】如何抽取着色器代码到assets目录下的GLSL文件,以及如何通过Java或者C++代码来加载着GLSL文件?相关推荐
- 【我的OpenGL学习进阶之旅】解决着色器编译错误:#version directive must occur on the first line of the shader
目录 一.问题描述 二.分析错误 三.解决问题 三.总结 一.问题描述 今天编写一个OpenGL ES的demo,发现没有任何图元输出. 查看日志,发现报了如下错误: 2021-11-15 15:09 ...
- 【我的OpenGL学习进阶之旅】解决着色器语法错误:The shader uses varying u_Color, but previous shader does not write to it
一.错误描述 在加载完顶点着色器和片段着色器,然后link生成program的时候,出现了错误,如下所示: 2021-12-31 09:34:01.072 15937-16006/com.oyp.op ...
- 【我的OpenGL学习进阶之旅】着色器和程序(上)------着色器
着色器和程序 一.前言 二.着色器和程序 2.1 创建和编译一个着色器 2.1.1 创建着色器 2.1.2 删除着色器 2.1.3 提供着色器源代码 2.1.4 编译色器 2.1.4 查询有关着色器对 ...
- 【我的OpenGL学习进阶之旅】介绍一下 绘制图元
目录 一.绘制图元 1.1 `glDrawArrays` 1.1.1 `glDrawArrays`API说明 1.1.2 `glDrawArrays`API示例 1.2 `glDrawElements ...
- 【我的OpenGL学习进阶之旅】C++如何加载TGA文件?
一.TGA文件相关介绍 通过前面的博客 [我的OpenGL学习进阶之旅]什么是TGA文件以及如何打开TGA文件? 地址:https://ouyangpeng.blog.csdn.net/article ...
- 【我的OpenGL学习进阶之旅】【持续更新】关于学习OpenGL的一些资料
目录 一.相关书籍 OpenGL 方面 C方面 NDK 线性代数 二.相关博客 2.0 一些比较官方的链接 2.1 OpenGL着色器语言相关 2.2 [[yfan]](https://segment ...
- 【我的OpenGL学习进阶之旅】OpenGL ES 3.0新功能
目录 1.1 纹理 1.2 着色器 1.3 几何形状 1.4 缓冲区对象 1.5 帧缓冲区 OpenGL ES 2.0 开创了手持设备可编程着色器的时代,在驱动大量设备的游戏.应用程序和用户接口中获得 ...
- 【我的OpenGL学习进阶之旅】着色器编译器和程序二进制码
目录 一.着色器编译器 二.程序二进制码 2.1 glGetProgramBinary 2.2 glProgramBinary 一.着色器编译器 当你要求OpenGL ES 编译和链接着色器的时候,光 ...
- 【我的OpenGL学习进阶之旅】关于3D模型知识之:什么是obj文件和mtl文件
文章目录 一.学习3D模型的背景 二.3D模型效果展示 三.好奇3D模型文件是啥内容? 3.1 打开.obj文件 3.2 打开.obj文件 3.3 在外部使用查看3D模型的软件打开.obj文件 3.3 ...
- 【我的OpenGL学习进阶之旅】什么是TGA文件以及如何打开TGA文件?
目录 一.问题描述 二.解决问题:打开TGA文件 三.什么是TGA文件? 四. 如何打开TGA文件 4.1. 使用图像编辑器打开TGA文件 4.2. 使用Pain.Net打开TGA文件 4.3.使用T ...
最新文章
- python教材免费版-北大内部教材python版算法与数据结构PDF电子书免费下载
- ASP.net Joyrock异步应用示例、JSON-RPC使用方法
- linux mmap 详解【转】
- linux mint 18.3 内核,Linux Mint Linux用户可以升级到18.2 18.3”
- 《机器学习》周志华 习题答案9.4
- mysql根据排序取前百分之二十_MySQL 性能优化 MySQL常见SQL错误用法
- java xml解析器_Java XML解析器
- Atitit 提升开发进度大方法--高频功能与步骤的优化 类似性能优化
- Julia: 自制的Julia代码排版工具CodeBeautify
- 有关单片机c语言的参考文献,单片机设计参考文献
- luogu P3966 [TJOI2013]单词
- 批量替换Excel超级链接
- android开发之UI
- 计算机技术教学,小学计算机技术教学计划
- APOLLO UDACITY自动驾驶课程笔记——感知、预测
- 一文告诉你,SIMULIA/Abaqus究竟有多强大
- django—APIView详细讲解
- 解密阿里巴巴加密技术: 爬虫JS逆向实践-1688 【JS混淆加密解析】
- Vue UI插件集合(转载)
- 【论文分享】UMVD-FSL:Unseen Malware Variants Detection Using Few-Shot Learning