OpenGL学习3——着色器

着色器(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);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容