美文网首页
OpenGL学习3——着色器

OpenGL学习3——着色器

作者: 蓬篙人 | 来源:发表于2021-06-14 03:26 被阅读0次

    着色器(shaders)

    • 简单的理解,着色器就是将输入转换为输出的程序,同时着色器也是非常独立的程序,它们之间的通讯只能通过它们的输入和输出。


      OpenGL着色器主要内容

    1. GLSL

    • 着色器典型的结构:
    #version version_number
    in type in_variable_name;
    in type in_variable_name;
    
    out type out_variable_name;
    
    void main()
    {
        // 处理输入,做一些图形操作
        // 将处理结果设置到输出变量
        out_variable_name = werid_stuff_we_processed;
    }
    
    1. 声明版本(和指定渲染模式)。
    2. 定义输入变量。
    3. 定义输出变量。
    4. 在main函数中设置输出变量的值。
    
    • 当针对顶点着色器时,输入变量也称为顶点属性(vertex attribute)。我们可以声明的顶点属性的最大数量由硬件限制,但是OpenGL保证我们至少可以使用16个4维顶点属性变量。硬件具体支持的数量可以通过查询GL_MAX_VERTEX_ATTRIBS获得。
    int nrAttributes;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
    std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
    

    2. 类型

    • 矢量(vector):在GLSL中矢量是一个由1,2,3或4个基本类型组件组成的容器。可以有如下的形式(其中n代表组件的数量):
      • vecn:n个float的矢量(默认)。
      • bvecn:n个boolean的矢量。
      • ivecn:n个整数的矢量。
      • uvecn:n个无符号整数的矢量。
      • dvecn:n个double的矢量。
    • 矢量通过vec.x, vec.y, vec.z, vec.w访问相应的组件。在GLSL中,颜色还可以通过rgba访问,纹理(texture)可以通过stpq访问。
    • 矢量允许我们进行一些灵活的组件选择,叫做swizzling,语法类似下面的代码:
    vec2 someVec;
    vec4 differentVec = someVec.xyxx;
    vec3 anotherVec = differentVec.zyw;
    vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
    // 传入到构造函数中
    vec2 vect = vec2(0.5, 0.7);
    vec4 = result = vec4(vect, 0.0, 0.0);
    vec4 otherResult = vec4(result.xyz, 1.0);
    

    3. 输入和输出

    • 当一个输出变量与下一着色器阶段的输入变量匹配时,变量将在两个着色器间传递。
    • 在输入输出方面,比较特殊的两个着色器是顶点着色器和片元着色器。
        1. 顶点着色器:直接从顶点数据接收输入,使用location元数据来指定输入变量以便我们可以在CPU侧配置顶点属性。
        1. 片元着色器:因为片元着色器需要生成一个最终输出颜色,因此该着色器需要一个vec4的颜色输出变量。如果没有指定一个输出颜色,那么片元的颜色将是不确定的。
    • 如果我们要在两个着色器之间传递变量,那么我们需要在发送的着色器中声明一个输出变量,在接收的着色器中声明一个匹配的输入变量。这样,在链接着色器程序时,两个着色器的变量会链接到一起。下面的着色器显示从顶点着色器传递颜色值到片元着色器。
    // 顶点着色器
    #version 330 core
    layout (location = 0) in vec3 aPos;
    
    out vec4 vertexColor;   // 指定一个颜色输出给片元着色器
    
    void main()
    {
        gl_Position = vec4(aPos, 1.0);
        vertexColor = vec4(0.5, 0.0, 0.0, 1.0);   // 设置输出变量值 dark-red
    }
    
    // 片元着色器
    #version 330 core
    out vec4 FragColor;
    
    in vec4 vertexColor;   // 从顶点着色器获取值的输入变量(相同的名称和数据类型)
    
    void main()
    {
        FragColor = vertexColor;  // 设置输出变量值
    }
    
    • 将上述着色器应用到我们前一篇博客的代码中,运行效果如下:


      着色器传递变量效果

    4. Uniforms

    • Uniforms是另一种从CPU侧,即我们的OpenGL程序传递数据到GPU中的着色器的方式。
    • Uniforms与顶点属性的区别:
        1. Uniforms是全局的,意味着每个着色器程序对象中,uniform变量是唯一的,且可以从着色器程序中任何阶段的任何着色器进行访问。
        1. 不管你将uniforms值设置为什么,值会一直保持直到被重置或更新。
    • 使用uniform关键字声明Uniform变量。
    #version 330 core
    out vec4 FragColor;
    
    uniform vec4 ourColor;   // 该变量的值在我们的OpenGL代码中设置
    
    void main()
    {
        FragColor = ourColor;  // 设置输出变量值
    }
    
    • 注意:如果你声明了一个uniform,但是没有在你的GLSL代码使用,那么编译器将会在编译版本中移除,这可能导致一些不确定的问题。
    • 设置uniform变量的值
    float timeValue = glfwGetTime();
    float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
    int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
    
    glUseProgram(shaderProgram);
    glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
    
    1. glfwGetTime()函数获取程序运行时间,使用`sin()`函数转换为一个[0.0, 1.0]之间的颜色值。
    2. glGetUniformLocation()函数查询uniform变量的位置(示例中为ourColor)。
    3. 使用getUniform4f()函数设置uniform变量的值。
    
    • 将上述片元着色器应用到我们前一篇博客的代码中,运行效果如下,矩形将在绿色和黑色之间变化:


      Uniform设置效果图1
    Uniform设置效果图2

    5. 更多属性(attributes)

    • 为顶点数据添加颜色属性:
    // 顶点数据
    float vertices[] = {
        // positions        // colors
        -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
    };
    
    • 调整顶点着色器以接收顶点数据中的颜色属性:
    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;
    
    out vec3 ourColor;
    
    void main()
    {
        gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
        ourColor = aColor;   // 将顶点数据输入的颜色设置为输出
    }
    
    • 调整片元着色器接收顶点着色器的颜色输出:
    #version 330 core
    out vec4 FragColor;
    in vec3 ourColor;
    
    void main()
    {
        FragColor = vec4(ourColor, 1.0f);
    }
    
    • 包含位置和颜色属性的VBO内存可能如下图所示:(图片取自书中
      VBO内存示意图
    • 使用glVertexAttribPointer函数更新顶点数据的格式,参数的值设置对比上图的内存情形:
    // 1. 设置顶点位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 2. 设置顶点颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    
    • 运行结果:


      添加颜色属性运行效果

    6. 编写我们自己的着色器类

    • 编写一个着色器类将顶点着色器和片元着色器的创建和编译,以及着色器程序的链接封装起来。
    // 着色器头文件
    #pragma once
    
    #include <glad/glad.h>
    
    #include <string>
    #include <fstream>
    #include <sstream>
    #include <iostream>
    
    class Shader
    {
    public:
        // the program ID
        unsigned int ID;
    
        // constructor reads and builds the shader
        Shader(const char* vertexPath, const char* fragmentPath);
        // use/active the shader
        void use();
        // utility uniform functions
        void setBool(const std::string& name, bool value) const;
        void setInt(const std::string& name, int value) const;
        void setFloat(const std::string& name, float value) const;
    };
    
    // 着色器类实现文件
    #include <iostream>
    #include <fstream>
    #include "shader.h"
    
    Shader::Shader(const char* vertexPath, const char* fragmentPath)
    {
        //------1. 从指定路径读取顶点/片元着色器程序内容
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        // ensure ifstream objects can throw exceptions
        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try
        {
            // open file 
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            std::stringstream vShaderStream, fShaderStream;
            // read file's buffer contents into stream
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            // close file handlers
            vShaderFile.close();
            fShaderFile.close();
            // convert stream into string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        }
        catch(std::ifstream::failure e)
        {
            std::cout << "EEROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
        }
        const char* vShaderCode = vertexCode.c_str();
        const char* fShaderCode = fragmentCode.c_str();
        //------2. 创建和编译着色器
        unsigned int vertex, fragment;
        int success;
        char infoLog[512];
        //---------2.1 创建和编译顶点着色器
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        // print compile errors if any
        glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(vertex, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
        }
        //---------2.2 创建和编译片元着色器
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(fragment, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
        }
        //------3. 链接着色器程序
        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::LINK_FAILED\n" << infoLog << std::endl;
        }
        //------4. 链接着色器程序后删除着色器对象
        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }
    // 激活着色器程序
    void Shader::use()
    {
        glUseProgram(ID);
    }
    // 一些辅助函数,用于设置unfirom的值
    void Shader::setBool(const std::string& name, bool value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
    }
    
    void Shader::setInt(const std::string& name, int value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    }
    
    void Shader::setFloat(const std::string& name, float value) const
    {
        glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    }
    
    • 着色器类的调用
    // 声明着色器类(路径取决于shader文件的存放位置,文件后缀无关紧要)
    Shader ourShader("./shaders/VertexShader.vs", "./shaders/FragmentShader.fs");
    // 在渲染循环中调用
    ourShader.use();
    ourShader.setFloat("ourColor", 0.2f);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    

    相关文章

      网友评论

          本文标题:OpenGL学习3——着色器

          本文链接:https://www.haomeiwen.com/subject/bnsneltx.html