我的OpenGL学习进阶之旅如何抽取着色器代码到assets目录下的GLSL文件,以及如何通过Java或者C++代码来加载着GLSL文件?

Posted 字节卷动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅如何抽取着色器代码到assets目录下的GLSL文件,以及如何通过Java或者C++代码来加载着GLSL文件?相关的知识,希望对你有一定的参考价值。

关于着色器的相关知识,可以参考我之前的博客介绍:

一、着色器代码以字符串形式写在代码里的现状

网上大部分的例子,都是把着色器代码直接定义成字符串的形式,写出来。如下所示:

  • 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_TYPE
        GLES30.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 shaders
        vertexShader = 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 object
        programObject = GLES30.glCreateProgram();

        if (programObject == 0) 
            return 0;
        

        GLES30.glAttachShader(programObject, vertexShader);
        GLES30.glAttachShader(programObject, fragmentShader);

        // Link the program
        GLES30.glLinkProgram(programObject);

        // Check the link 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 resources
        GLES30.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 assets
        vertShaderSrc = readShader(context, vertexShaderFileName);
        System.out.println("  vertShaderSrc = " + vertShaderSrc);
        if (vertShaderSrc == null) 
            return 0;
        

        // Read fragment shader from assets
        fragShaderSrc = readShader(context, fragShaderFileName);
        System.out.println("  fragShaderSrc = " + fragShaderSrc);
        if (fragShaderSrc == null) 
            return 0;
        

        // Load the vertex shader
        vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertShaderSrc);
        if (vertexShader == 0) 
            GLES30.glDeleteShader(vertexShader);
            return 0;
        

        // Load the fragment shader
        fragmentShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragShaderSrc);
        if (fragmentShader == 0) 
            GLES30.glDeleteShader(fragmentShader);
            return 0;
        

        // Create the program object
        programObject = 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 resources
        GLES30.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 Program
    VERTEX_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);
       

以上是关于我的OpenGL学习进阶之旅如何抽取着色器代码到assets目录下的GLSL文件,以及如何通过Java或者C++代码来加载着GLSL文件?的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅 C++ 长行字符串多行书写的方法以及如何书写正确的OpenGL Shader着色器代码

我的OpenGL学习进阶之旅 C++ 长行字符串多行书写的方法以及如何书写正确的OpenGL Shader着色器代码

我的OpenGL学习进阶之旅着色器和程序(下)------ 程序对象

我的OpenGL学习进阶之旅着色器和程序(下)------ 程序对象

我的OpenGL学习进阶之旅着色器和程序(上)------着色器

我的OpenGL学习进阶之旅着色器和程序(上)------着色器