自学OpenGL(七)-着色器

前言

着色器是运行在GPU上的程序,为图形渲染管线特定部分而运行,从某种意义上来说,着色器是把输入转化为输出的程序。着色器程序是完全独立的程序,着色器之间不能直接通信,只能通过输入输出实现通信。在前面的几篇文章中,粗略地介绍了一下着色器,接下来我将详细的介绍着色器。

GLSL

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。用法与特性可学习 GLSL 中文手册

封装我们自己的着色器类

通过我们几篇博客了解到着色器需要编写、编译、管理着色器,这是件麻烦事,在着色器主题的最后,我们会写一个类来让我们的生活轻松一点,它可以从硬盘读取着色器,然后编译并链接它们,并对它们进行错误检测,这就变得很好用了。这也会让你了解该如何封装目前所学的知识到一个抽象对象中。

我们会把着色器类全部放在在头文件里,主要是为了学习用途,当然也方便移植。类结构设计如下:

  1. 声明一个抽象类 ShaderStreamInterface,该抽象类代表一个着色器程序的 GLSL 源代码。
#define _SHADER_SRC(...) #__VA_ARGS__
#define SHADER_SRC(...) _SHADER_SRC(__VA_ARGS__)

class ShaderStreamInterface {
public:
    virtual char *getVertexCode() = 0;
    
    virtual char *getFragmentCode() = 0;
protected:
    char *vertextCode;
    char *fragmentCode;
};
  1. 声明一个着色器类 Shader,勇于编译、链接、管理着色器。

在Shader.h 文件中代码如下

#define GLEW_STATIC
#include <GL/glew.h>
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include "ShaderStreamInterface.h"
/** 着色器类 */
class Shader {
    
public:
    // 程序ID
    unsigned int ID;
    // 构造器读取并构建着色器
    Shader(ShaderStreamInterface *shaderStream);
    // 使用/激活程序
    Shader * use();
    // uniform工具函数
    Shader * setBool(const std::string &name, bool value);
    Shader * setInt(const std::string &name, int value);
    Shader * setFloat(const std::string &name, float value);
    Shader * setColor(const std::string &name, float red, float green, float blue, float alpha);
};

在Shader.cpp 文件中的代码如下:


Shader:: Shader(ShaderStreamInterface *shaderStream) {
    // 1. 从文件路径中获取顶点/片段着色器
    const char* vShaderCode = shaderStream->getVertexCode();
    const char* fShaderCode = shaderStream->getFragmentCode();
    
    // 2. 编译着色器
    unsigned int vertex, fragment;
    int success;
    char infoLog[512];

    // 顶点着色器
    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vShaderCode, NULL);
    glCompileShader(vertex);
    // 打印编译错误(如果有的话)
    glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
    if(!success) {
        glGetShaderInfoLog(vertex, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    };

    // 片段着色器也类似
    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &fShaderCode, NULL);
    glCompileShader(fragment);
    
    // 着色器程序
    ID = glCreateProgram();
    glAttachShader(ID, vertex);
    glAttachShader(ID, fragment);
    glLinkProgram(ID);
    // 打印连接错误(如果有的话)
    glGetProgramiv(ID, GL_LINK_STATUS, &success);
    if(!success) {
        glGetProgramInfoLog(ID, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }

    // 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
    glDeleteShader(vertex);
    glDeleteShader(fragment);
}

Shader * Shader:: use() {
    glUseProgram(ID);
    return this;
}

Shader * Shader:: setBool(const std::string &name, bool value) {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    return this;
}

Shader * Shader:: setInt(const std::string &name, int value) {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    return this;
}

Shader * Shader:: setFloat(const std::string &name, float value) {
    glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    return this;
}

Shader * Shader:: setColor(const std::string &name, float red, float green, float blue, float alpha) {
    glUniform4f(glGetUniformLocation(ID, name.c_str()), red, green, blue, alpha);
    return this;
}

上面的设计方式有如下几大优点:

  1. 功能职责分明,ShaderStreamInterface 抽象类专注于着色器代码的编写、加载和管理,,Shader 类专注于着色器的编译、链接、管理着色器的生命周期。
  2. 扩展性强,如果需要编写新的着色器代码,只需要新写一个类继承自 ShaderStreamInterface 即可。
  3. 易于调试,可以分别对 Shader 类以及 ShaderStreamInterface 其子类进行单元测试。

现在整个着色器类已经设计完成,接下来我们来完成绘制一个有颜色的三角形

绘制有颜色的三角形

  1. 定义 ShaderStream 类继承自 ShaderStreamInterface,并编写顶点着色器和片段着色器代码
    .hpp
#include <stdio.h>
#include "ShaderStreamInterface.h"
/** 章节案例 */
class ShaderStream: public ShaderStreamInterface {
public:
    ShaderStream();
    char *getVertexCode() {
        return vertextCode;
    }
    char *getFragmentCode() {
        return fragmentCode;
    }
protected:
    char *vertexStream();
    char *fragmentStream();
};

.cpp

ShaderStream:: ShaderStream() {
    // 顶点着色器
    vertextCode = vertexStream();
    // 片段着色器代码
    fragmentCode = fragmentStream();
}

char * ShaderStream:: vertexStream() {
    return SHADER_SRC(
                      \#version 330 core\n
                      layout (location = 0) in vec3 aPos;
                      layout (location = 1) in vec3 aColor;

                      out vec3 ourColor;

                      void main()
                      {
                          gl_Position = vec4(aPos, 1.0);
                          ourColor = aColor;
                      }
            );
}

char * ShaderStream:: fragmentStream() {
    return SHADER_SRC(
                      \#version 330 core\n
                      out vec4 FragColor;
                      in vec3 ourColor;
                      void main() {
                          FragColor = vec4(ourColor, 1.0);
                      }
    );
}
  1. 来到主应用程序 main 函数中,引入相关类
#include "ShaderStream.hpp"
#include "Shader.hpp"
  1. 初始化glfw,初始化窗口,初始化 glew,前面已经讲过了,这里不再重复。
  2. 初始化着色器
    // 着色器代码对象
    ShaderStream *shaderCode = new ShaderStream();
    // 着色器程序
    Shader *shader = new Shader(shaderCode);
  1. 创建顶点数组、顶点缓冲对象、顶点数组对象,这块是跟之前你好 三角形一样一样的
    // 创建顶点数组多想 VAO
    float vertices[] = {
        // 位置              // 颜色
         0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // 右下
        -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // 左下
         0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 顶部
    };
    
    unsigned int vao, vbo;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    // 位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
    glEnableVertexAttribArray(1);
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
  1. 执行渲染循环
    while (!glfwWindowShouldClose(window)) {
        procesInput(window);
        glClearColor(0.2, 0.2, 0.3, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        shader->use();
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
        
    }
  1. 处理退出,释放资源
    std::cout << "Hello, World!\n";
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glDeleteProgram(shader->ID);
    glfwTerminate();

运行command + R 运行,得到一个五彩斑斓的三角形:


FileSharing.png

如果你运行出来有问题或者出错了,情仔细检查一下代码,或者在本文下方下载笔者写的demo。

源代码在这里:https://github.com/muxueChen/LearnOpenGL

推荐阅读更多精彩内容