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

Posted 欧阳鹏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅着色器和程序(上)------着色器相关的知识,希望对你有一定的参考价值。

一、前言

在博客 【我的OpenGL学习进阶之旅】你好,三角形:一个OpenGL ES 3.0示例。 分别使用C++ Native & Java 两种方式来实现 中,我们介绍了绘制一个三角形的简单程序。

在那个例子中,我们创建了两个着色器对象(一个顶点着色器,一个片段着色器)和一个程序对象,以渲染三角形。

着色器对象和程序对象是使用OpenGL ES 3.0着色器的基本概念。

接下来,我们要讲解的主题,如下所示:

  • 着色器和程序对象概述
  • 创建和编译着色器
  • 创建和链接程序
  • 获取和设置统一变量
  • 获取和设置属性
  • 着色器编译器和程序二进制代码

二、着色器和程序

需要创建两个基本对象才能用着色器进行渲染:着色器对象和程序对象。

理解着色器对象和程序对象的最佳方式就是将它们比作C语言的编译器和链接程序。 C编译器为一段源代码生成目标代码(例如,.obj或者.o文件)。在创建目标文件之后,C链接程序将对象文件链接为最后的程序。

OpenGL ES 在着色器的表现上使用类似的方式。

  1. 着色器对象是包含单个着色器的对象。
  2. 源代码提供给着色器对象,然后着色器对象被编译为一个目标形式(类似与.obj文件)。
  3. 编译之后,着色器对象可以连接到一个程序对象。程序对象可以连接多个着色器对象。
  4. 在OpenGL ES中,每个程序对象必须连接一个顶点着色器和一个片段着色器(不多也不少),这和桌面OpenGL不同。
  5. 程序对象被链接为用于渲染的最后“可执行程序”。

获得连接后的着色器对象的一般过程包括6个步骤:

  1. 创建一个顶点着色器对象和一个片段着色器对象
  2. 将源代码连接到每个着色器对象。
  3. 编译着色器对象。
  4. 创建一个程序对象。
  5. 将编译后的着色器对象连接到程序对象。
  6. 链接程序对象。

如果没有错误,就可以在任何时候通知GL使用这个程序绘图。

这篇博客我们先介绍着色器相关的知识。

2.1 创建和编译一个着色器

2.1.1 创建着色器

使用着色器对象的第一步是创建着色器,这可以用glCreateShader完成。

 GLuint glCreateShader (GLenum type);


参数 type : 创建的着色器类型可以是GL_VERTEX_SHADER或者GL_FRAGMENT_SHADER

调用glCreateShader 将根据传入的 type 参数创建一个新的顶点着色器或者片段着色器。 返回值是指向新着色器对象的句柄。

2.1.2 删除着色器

当完成着色器对象时,可以使用glDeleteShader 删除

void glDeleteShader (GLuint shader);

参数shader表示: 要删除的着色器对象的句柄。

注意,如果一个着色器链接到一个程序对象,那么调用glDeleteShader不会立刻删除着色器,而且将着色器标记为删除,在着色器不再连接到任何程序对象时,它的内存将被释放

2.1.3 提供着色器源代码

一旦创建着色器对象,下一件事通常是使用glShaderSource提供着色器源代码

void  glShaderSource (GLuint shader, GLsizei count, 
                      const GLchar *const*string,
                      const GLint *length);

参数讲解:

  • shader
    指向着色器对象的句柄
  • count
    着色器源字符串的数量。着色器可以由多个源字符串组成,但是每个着色器只能有一个main函数。
  • string
    指向保存数量为count的着色器源字符串的数组指针。
  • length
    指向保存每个着色器字符串大小且元素数量为count的整数数组指针。如果lengthNULL,着色器字符串将被认定为空。如果length不为NULL,则它的每个元素保存对应string数组的着色器的字符数量。如果任何元素的length值均小于0,则该字符串被认定以null结束。

2.1.4 编译色器

指定着色器源代码之后,下一步是用glCompileShader编译着色器。

void glCompileShader (GLuint shader);

参数 shader 表示: 需要编译的着色器对象句柄。

2.1.4 查询有关着色器对象的信息

调用glCompileShader将编译已经保存在着色器对象的着色器源代码。
和常规的语言编译器一样,编译之后你想知道的第一件事是不是没有错误。你可以使用glGetShaderiv查询这一信息和其他有关着色器对象的信息。

void glGetShaderiv (GLuint shader, GLenum pname, GLint *params);


参数讲解:

  • shader
    执向需要获取信息的着色器对象的句柄
  • pname
    获取信息的参数,可以是:
    • GL_COMPILE_STATUS
    • GL_DELETE_STATUS
    • GL_INFO_LOG_LENGTH
    • GL_SHADER_SOURCE_LENGTH
    • GL_SHADER_TYPE
  • params
    指向查询结果的整数存储位置的指针。

2.1.4.1 查询GL_COMPILE_STATUS

要检查着色器是否编译成功,可以用pname参数GL_COMPILE_STATUS在着色器对象上调用glGetShaderiv
如果着色器编译成功,结果将是GL_TRUE。如果编译失败,结果将是GL_FALSE,编译错误将写入信息日志

信息日志是由编译器写入并包含错误信息或者警告的日志。
即使编译操作成功,也会在信息日志中写入信息。要检索信息日志,可以使用GL_INFO_LOG_LENGTH查询它的长度。

日志本身可以用glGetShaderInfoLog检索。

2.1.4.2 查询GL_SHADER_TYPE

查询GL_SHADER_TYPE 将返回着色器类型:GL_VERTEX_SHADER或者GL_FRAGMENT_SHADER

2.1.4.3 查询GL_DELETE_STATUS

查询GL_DELETE_STATUS 返回着色器是否用glDeleteShader 标记为删除。

2.1.5 获取信息日志

编译着色器并检查信息日志长度之后,你可能希望检索信息日志(特别是在编译失败时查看原因)。为此,首先需要查询GL_INFO_LOG_LENGTH并分配一个足以存储信息日志的字符串。
然后,用glGetShaderInfoLog检索信息日志。

void glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);


参数说明:

  • shader
    需要获取信息日志的着色器对象句柄
  • bufSize
    保存信息日志的缓冲区大小
  • length
    写入的信息日志的长度(减去null终止符)。如果不需要知道长度,这个参数可以为NULL
  • infoLog
    指向保存信息日志的字符缓冲区的指针。

信息日志没有任何强制的格式或者必须的信息。但是,大部分OpenGL ES 3.0实现将返回错误信息,包括编译器发现的错误源代码行号。有些实现还在日志中提供警告或者附件信息。

比如我的博客 【我的OpenGL学习进阶之旅】解决着色器编译错误:#version directive must occur on the first line of the shader中描述的,产生的信息:

2021-11-15 15:09:07.406 26065-26107/opengles3.book.hello_Triangle E/ESShader: ERROR: 0:2: '
    ' : #version directive must occur on the first line of the shader 
    ERROR: 0:9: 'in' : storage qualifier supported in GLSL ES 3.00 only  

三、加载着色器示例

到现在为止,我们已经说明了创建着色器、编译、找出编译状态和查询信息日志所需的所有函数。

下面写一个示例,来加载一个着色器。

/**
 * Loads the given source code as a shader of the given type.
 *
 * 负责 加载着色器源代码、编译并检查错误。他返回一个着色器对象
 */
static GLuint loadShader(GLenum shaderType, const char** source) 
	// Create the shader object
	GLuint shader;
	GLint compiled;

	// Create the shader object
	// shaderType 可以是  GL_VERTEX_SHADER  或者  GL_FRAGMENT_SHADER
	shader = glCreateShader(shaderType);
	if (shader == 0) 
		return 0;
	

	// Load the shader source
	glShaderSource(shader, 1, source, nullptr);

	// Compile the shader
	glCompileShader(shader);

	// Check the compile status
	glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);

	if (!compiled) 
		GLint infoLen = 0;

		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);

		if (infoLen > 1) 
			char* infoLog = (char*)malloc(sizeof(char) * infoLen);
			// 检索信息日志
			glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
			LOGE("Error compiling shader:\\n%s\\n", infoLog);

			free(infoLog);
		
		// 删除Shader
		glDeleteShader(shader);
		return 0;
	
	return shader;

以上是关于我的OpenGL学习进阶之旅着色器和程序(上)------着色器的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (上)

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (上)

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (下)