简单着色器编写(中上)

这篇我们来介绍函数部分,也就是下面这些:

cpp 复制代码
static unsigned int CompileShader(unsigned int type,const std::string& source)
{
    unsigned int id = glCreateShader(type);
    const char* src = source.c_str();
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result==GL_FALSE)
    {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to compile"<<
            (type==GL_VERTEX_SHADER?"vertex":"fragment")
            << "shader!" << std::endl;
        std::cout << message << std::endl;
        glDeleteShader(id);
        return 0;
    }

    return id;
}

下面来逐步解释每一行代码:

static unsigned int CompileShader(unsigned int type,const std::string& source)

  • static:这个关键字表示函数是静态的,意味着它属于类或命名空间,而不是特定的对象实例。它可以通过类名或命名空间名直接调用,而无需创建对象。

  • unsigned int type:这是一个参数,表示要编译的着色器的类型。可以是 GL_VERTEX_SHADER(顶点着色器)或 GL_FRAGMENT_SHADER(片段着色器)。

  • const std::string& source:这是另一个参数,表示要编译的着色器源代码。它是一个引用,指向一个字符串对象,其中包含了要编译的着色器源代码。

unsigned int id = glCreateShader(type);

这行代码的作用是使用 glCreateShader 函数创建一个着色器对象,并将返回的标识符(ID)存储在名为 id 的无符号整数变量中。

  • unsigned int id:这是一个无符号整数变量,用于存储着色器对象的标识符(ID)。在OpenGL中,标识符通常是无符号整数类型。

  • glCreateShader(type):这是一个OpenGL函数调用,用于创建一个指定类型的着色器对象。type 参数表示着色器的类型,可以是 GL_VERTEX_SHADER(顶点着色器)或 GL_FRAGMENT_SHADER(片段着色器)。函数返回一个表示新创建着色器对象的唯一标识符。

const char* src = source.c_str();

这行代码的作用是将从 source 字符串对象获取的C风格字符串(null-terminated string)赋值给名为 srcconst char* 指针。

  • const char* src:这是一个指向字符的指针,它被声明为指向常量字符(const char),表示它指向的字符数据是只读的。

  • source.c_str():这是一个 std::string 对象的成员函数,用于返回一个指向以null结尾的C风格字符串的指针。通过调用这个函数,你可以获取 source 字符串对象的C风格表示。

这个操作通常用于将C++的 std::string 类型转换为C风格字符串,因为很多OpenGL函数需要接受C风格字符串作为参数。在这种情况下,从 source 字符串对象中获取一个C风格字符串指针,以便在后续的OpenGL函数中使用。请注意,因为 src 是一个指向常量字符的指针,所以不能通过它来修改原始字符串数据。

glShaderSource(id, 1, &src, nullptr);

  • id:这是一个无符号整数(GLuint),代表着色器对象的标识符。你可以通过 glCreateShader 函数创建着色器对象,并将其标识符赋给 id

  • 1:这是一个整数,表示要加载的源代码字符串的数量。在这里,我们加载一个源代码字符串。

  • &src:这是一个指向源代码字符串的指针。由于在之前的代码中,我们将 source 字符串对象的C风格表示(即以null结尾的字符序列)赋给了 src 指针,所以可以通过 &src 获取该指针的地址。

  • nullptr:这是一个指向字符数组的长度的指针。在这里,我们没有指定字符串长度,因为OpenGL会根据null终止符自动计算字符串长度。

总的来说,glShaderSource 函数的作用是将源代码加载到着色器对象中,使着色器对象拥有这些源代码,以便后续的编译操作。

这个函数比较复杂,我来解释一下,

函数签名:

cpp 复制代码
void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
  • shader:表示要设置源代码的着色器对象的标识符。这是通过 glCreateShader 创建的。
  • count:表示源代码字符串的数量。通常情况下,你会加载一个源代码字符串,所以值为 1。
  • string:是一个指向源代码字符串的指针的指针(const GLchar**)。这里的 string 是一个数组,每个元素指向一个源代码字符串的起始位置。你可以使用单个源代码字符串,也可以将多个源代码字符串合并成一个数组。
  • length:是一个指向整数数组的指针(const GLint*),用于指定每个源代码字符串的长度。通常情况下,你可以将其设置为 nullptr,OpenGL会自动计算字符串长度。

glShaderSource 函数将源代码加载到指定的着色器对象中,以便后续使用 glCompileShader 函数对其进行编译。

glCompileShader(id);

调用 glCompileShader 函数会编译着色器对象中的源代码,将其转换为可以在图形渲染管线中执行的机器代码。如果源代码中存在语法错误、逻辑问题或其他编译错误,编译过程将失败,并且你可以通过查询编译状态和错误日志来调试和查找问题。

一旦着色器成功编译,你就可以将其与其他着色器链接到一起,或将其用于渲染管线的其他阶段,从而实现所需的图形渲染效果。

glGetShaderiv(id, GL_COMPILE_STATUS, &result);

glGetShaderiv 是一个OpenGL函数,用于查询着色器对象的特定信息。在这段代码中,它用于查询着色器的编译状态,即是否成功编译了着色器源代码。

函数签名:

cpp 复制代码
void glGetShaderiv(GLuint shader, GLenum pname, GLint* params);

参数解释:

  • shader:表示要查询的着色器对象的标识符。
  • pname:表示要查询的参数名称,这里是 GL_COMPILE_STATUS,用于查询编译状态。
  • params:是一个指向整数的指针,用于存储查询结果。

在这段代码中,传递的 pnameGL_COMPILE_STATUS,表示想查询编译状态。params 是一个指向 result 的指针,用于存储查询结果。如果编译成功,result 将被设置为 GL_TRUE,如果编译失败,result 将被设置为 GL_FALSE

通过查询编译状态,你可以判断着色器是否成功编译,从而在发生编译错误时进行适当的处理和调试。

glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);

在这段代码中,glGetShaderiv 函数用于查询着色器信息日志的长度。这个信息日志包括着色器编译过程中的警告、错误和其他相关信息。通过查询信息日志的长度,你可以了解信息日志所需的缓冲区大小,然后可以为信息日志分配足够的空间来存储这些信息。

具体地说,以下是这段代码中各个参数的含义:

  • id:着色器对象的标识符,即你之前通过 glCreateShader 创建的着色器对象。
  • GL_INFO_LOG_LENGTH:表示你要查询的参数名称,这里是用于查询信息日志长度的参数。
  • &length:一个指向整数的指针,用于存储查询结果,即信息日志的长度。

通过这个查询,你可以获得信息日志的长度,然后根据这个长度分配足够大的内存,以便在之后的调用中使用 glGetShaderInfoLog 函数获取实际的信息日志内容。这些信息日志对于调试和定位着色器编译过程中的问题非常有用。

char* message = (char*)alloca(length * sizeof(char));

  • allocaalloca 是一个函数,用于在栈上动态分配内存空间。与标准的动态内存分配函数(如 malloc)不同,alloca 分配的内存在函数退出时会自动释放,因为它是在栈上分配的。这意味着分配的内存空间不能在函数外部访问,也不能在函数内部的其他函数中访问。在这里,它用于为信息日志分配临时的内存空间。

  • char* message:这声明了一个指向字符的指针,名为 message,用于存储信息日志内容。

  • (char*)alloca(length * sizeof(char)):这部分是对 alloca 函数的调用。alloca 分配的内存大小由 length * sizeof(char) 决定,其中 length 是通过查询 GL_INFO_LOG_LENGTH 得到的信息日志长度。sizeof(char) 是一个字符的字节数(通常为1),因此这里的乘法实际上只是为了分配与信息日志长度相等的内存空间。类型转换 (char*) 是将 alloca 返回的指针转换为指向字符的指针,以便后续存储字符串数据。

总之,这段代码的目的是在栈上分配一块内存空间,以便在之后的调用中存储着色器信息日志的内容。需要注意的是,由于 alloca 分配的内存在函数退出时会自动释放,所以你在退出函数后不能再使用指向这块内存的指针。

glGetShaderInfoLog(id, length, &length, message);

  • id:着色器对象的标识符,即你之前通过 glCreateShader 创建的着色器对象。
  • length:这是之前查询到的信息日志的长度,通过调用 glGetShaderiv 传递 GL_INFO_LOG_LENGTH 参数获得的。
  • &length:这是一个指向整数的指针,传递给 glGetShaderInfoLog 函数,用于接收实际写入信息日志内容的字符数。
  • message:这是一个之前通过 alloca 分配的字符数组,用于存储信息日志的内容。

通过调用 glGetShaderInfoLog 函数,OpenGL将实际的信息日志内容写入到 message 数组中,并将实际写入的字符数更新到 length 变量中。你可以随后使用这些内容来查看着色器编译过程中的警告、错误和其他消息,以帮助你进行调试和排错。

return id;

在函数 CompileShader 的最后,有一行代码 return id;,它的作用是将编译后的着色器对象标识符返回给调用者。这样做的目的是让调用者可以在后续的代码中继续使用这个着色器对象,例如将它附加到程序对象中,以便进行渲染。

在这个特定的代码中,CompileShader 函数负责编译着色器并返回一个表示着色器对象的标识符。调用者(可能是 CreateShader 函数)将得到这个标识符,并在适当的地方使用它。通过返回标识符,你可以在多个函数之间传递着色器对象,并在不同的地方使用它,以便实现更复杂的OpenGL操作。

相关推荐
ULTRA??5 分钟前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
凌云行者40 分钟前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者43 分钟前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
可均可可2 小时前
C++之OpenCV入门到提高004:Mat 对象的使用
c++·opencv·mat·imread·imwrite
白子寰2 小时前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
小芒果_012 小时前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
gkdpjj2 小时前
C++优选算法十 哈希表
c++·算法·散列表
王俊山IT2 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
-Even-2 小时前
【第六章】分支语句和逻辑运算符
c++·c++ primer plus