OpenGL学习——Shader

Posted yiqian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenGL学习——Shader相关的知识,希望对你有一定的参考价值。

之前已经接触过Vertex Shader和Fragment Shader,这次学习如何编写Shader并封装成类。

Shader源码主要有四部分:

  1. 版本声明 #version xxx core
  2. 使用in和out关键字定义输入输出变量,上一个Shader的输出变量必须和下一个Shader的输入变量保持一致;
  3. 有时使用uniform关键字定义全局变量;
  4. main主函数。

看一个Vertex Shader的例子:

#version 330 core
layout (location = 0) in vec3 Pos;
out vec4 Color;
void main()
{
    gl_Position = vec4(Pos, 1.0f);
    Color = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}

首先定义一个location为0的三维输入变量Pos,表示顶点的位置属性。之后用一个四维的Color变量表示输出的颜色。
在main函数中需要将Pos变换成齐次坐标,输出变量Color直接设置为红色。
Fragment Shader就很容易编写了,只需要把Vertex Shader的输出变量作为输入:

#version 330 core
in vec4 Color;
out vec4 fragColor;
void main()
{
    fragColor = Color;
}

深入一点,考虑如何实现三角形颜色渐变的效果。基本想法是颜色变量在渲染循环中随着时间的变化而变化,这就需要uniform定义一个全局变量来表示颜色。
首先是Vertex Shader:

#version 330 core
layout (location = 0) in vec3 Pos;
void main()
{
    gl_Position = vec4(Pos, 1.0f);
}

在Fragment Shader中使用uniform关键字取代in关键字:

#version 330 core
uniform vec4 Color;
out vec4 fragColor;
void main()
{
    fragColor = Color;
} 

在渲染循环中,调用glfwGetTime函数获取从GLFW被初始化后经过的时间,使用sin函数根据时间计算绿色分量的值,并映射到[0, 1]。不要忘记添加cmath头文件。
接下来调用glGetUniformLocation函数获取Color变量在Shader Program中的位置,并调用glUniform函数更新Color的值。注意glUniform函数后的后缀4f,表示Color的值是一个float类型的四维向量:

glUseProgram(shaderProgram);
float timeValue = glfwGetTime();
float greenValue = sin(timeValue)/2.0f+0.5f;
int uniformlocation = glGetUniformLocation(shaderProgram, "Color");
glUniform4f(uniformlocation, 0.0f, greenValue, 0.0f, 1.0f);

运行代码后会发现三角形颜色逐渐由绿变成黑,再变成绿。


之前所有的代码都只涉及了一个顶点属性,即Pos用来表示顶点的位置属性。如果在Vertex Shader中再定义一个location为1的三维输入变量,表示顶点的颜色属性,那么该如何配置VBO和顶点属性指针,并和VAO绑定呢?
首先在顶点数据中添加颜色数据,位置(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.5f, 0.5f, 0.0f)的顶点颜色为(0.0f, 0.0f, 1.0f):

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.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f};

编写Vertex Shade,Pos的属性位置为0,表示位置,Col的属性位置为1,表示颜色:

#version 330 core
layout (location = 0) in vec3 Pos;
layout (location = 1) in vec3 Col;
out vec4 Color;
void main()
{
    gl_Position = vec4(Pos, 1.0f);
    Color = vec4(Col, 1.0f);
}

不需要更改Fragment Shader:

#version 330 core
in vec4 Color;
out vec4 fragColor;
void main()
{
    fragColor = Color;
}

最后配置VBO和顶点属性指针并绑定VAO:

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); //使能属性位置0
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float))); //配置颜色
glEnableVertexAttribArray(1); //使能属性位置1

Shadr的创建,源码编译和链接成Shader Program需要编写很多重复代码,为了减少工作量,将这部分代码封装成类。
这个Shader类很简单,只包含了两个函数:

  1. shader函数负责处理Shader的创建,源码编译和链接;
  2. use函数负责激活Shader Program。

代码如下:

#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>
#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;

class Shader{
public:
    unsigned int ID;
    void shader(const char *vertexPath, const char *fragmentPath)
    {
        string vertexCode;
        string fragmentCode;
        ifstream vertexShaderFile;
        ifstream fragmentShaderFile;
        stringstream vertexStream;
        stringstream fragmentStream;
        const char *vertexShaderSource;
        const char *fragmentShaderSource;
        vertexShaderFile.open(vertexPath);
        fragmentShaderFile.open(fragmentPath);
        vertexStream << vertexShaderFile.rdbuf();
        fragmentStream << fragmentShaderFile.rdbuf();
        vertexShaderFile.close();
        fragmentShaderFile.close();
        vertexCode = vertexStream.str();
        fragmentCode = fragmentStream.str();
        vertexShaderSource = vertexCode.c_str();
        fragmentShaderSource = fragmentCode.c_str();

        int vertexShader;
        int fragmentShader;
        int success;
        char infoLog[512];
        vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
        glCompileShader(vertexShader);
        glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
        if(!success){
            glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
            cout << "ERROR::VERTEXSHADER::COMPILATION_FAILED\n" << infoLog << endl;
        }
        else cout << "VERTEXSHADER_COMPILATION_SUCCESS" << endl;
        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
        glCompileShader(fragmentShader);
        glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
        if (!success){
            glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
            cout << "ERROR::FRAGMENTSHADER::COMPILATION_FAILED\n" << infoLog << endl;
        }
        else cout << "FRAGMENTSHADER_COMPILATION_SUCCESS" << endl;

        ID = glCreateProgram();
        glAttachShader(ID, vertexShader);
        glAttachShader(ID, fragmentShader);
        glLinkProgram(ID);
        glGetProgramiv(ID, GL_LINK_STATUS, &success);
        if(!success){
            glGetProgramInfoLog(ID, 512, NULL, infoLog);
            cout << "ERROR::LINKING_FAILED\n" << infoLog << endl;
        }
        else cout << "LINKING_SUCCESS" << endl;

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
    }
    void use()
    {
        glUseProgram(ID);
    }
};

#endif // SHADER_H

这样在使用时,只需要将其添加到工程中,并添加头文件“shader.h”。

以上是关于OpenGL学习——Shader的主要内容,如果未能解决你的问题,请参考以下文章

Visual Studio 2012 和 2010 中基于 Opengl Sierpinski Shader 的 C 代码

我的OpenGL学习进阶之旅解决着色器语法错误:The shader uses varying u_Color, but previous shader does not write to it

我的OpenGL学习进阶之旅解决着色器语法错误:The shader uses varying u_Color, but previous shader does not write to it

我的OpenGL学习进阶之旅解决着色器语法错误:The shader uses varying u_Color, but previous shader does not write to it

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

片段着色器中未使用纹理数据 - OpenGL