OpenGL着色器

OpenGL着色器

着色器是运行在 GPU 上的小程序。这些程序在图形管道的每个特定部分运行。从基本意义上讲,着色器只不过是将输入转换为输出的程序。着色器是非常孤立的程序,因为它们不允许相互通信。

一、OpenGL着色语言–GLSL

着色器是用类 C 语言 GLSL 编写的。GLSL 专为与图形一起使用而量身定制,并包含专门针对矢量和矩阵操作的有用功能。

着色器总是以一个版本声明开始,然后是输入、输出、uniforms 变量列表, 和它的主要的功能。每个着色器的入口点都在我们处理任何输入变量并在其输出变量中输出结果的函数。

着色器通常具有以下结构:

#version version_number
in type in_variable_name;  // 上一阶段输入的数据
in type in_variable_name;  
out type out_variable_name;   // 这个阶段需要输出的数据
uniform type uniform_name;    // 全局数据, 应用程序中也能读取和写入的变量,用于着色器和应用程序之间互通数据
  
void main()
{
   
  out_variable_name = weird_stuff_we_processed;  // 数据处理并输出
}
  • 类型
    GLSL 与其他编程语言一样,具有用于指定我们想要使用的变量类型的数据类型。GLSL默认的基本类型有: int,float,double,uint和bool。GLSL 还有两种我们经常使用的容器类型,即vectors和matrices。

  • 向量
    GLSL 中的向量是一个包含 1、2、3 或 4 个组件的容器,它们可以采用以下形式(n表示组件的数量):

  • vecn:默认n浮点数向量。

  • bvecn:n布尔值向量。

  • ivecn: n整数向量。

  • uvecn: 一个n无符号整数向量。

  • dvecn: n双分量向量。
    您可以最多使用 4 个字母的任意组合来创建新向量,只要原始向量具有这些组件即可。我们还可以将向量作为参数传递给不同的向量构造函数调用,从而减少所需的参数数量:

vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);

着色器本身就是不错的小程序,但它们是整体的一部分,因此我们希望在单个着色器上具有输入和输出,以便我们可以移动内容。GLSL专门为此定义了inout关键字。每个着色器都可以使用这些关键字指定输入和输出,并且只要输出变量与下一个着色器阶段的输入变量匹配,它们就会被传递。不过,顶点着色器和片段着色器略有不同。

顶点着色器应该接收某种形式的输入,否则它会无效。顶点着色器的不同之处在于它的输入,因为它直接从顶点数据接收输入。为了定义顶点数据的组织方式,我们使用位置元数据指定输入变量,以便我们可以在 CPU 上配置顶点​​属性。我们在上一章中已经将其视为layout (location = 0). 因此,顶点着色器需要为其输入提供额外的布局规范,以便我们可以将其与顶点数据链接。

另一个例外是片段着色器需要一个vec4颜色输出变量,因为片段着色器需要生成最终输出颜色。如果您未能在片段着色器中指定输出颜色,则这些片段的颜色缓冲区输出将是未定义的(这通常意味着 OpenGL 会将它们渲染为黑色或白色)。

因此,如果我们想从一个着色器向另一个着色器发送数据,我们必须在发送着色器中声明一个输出,并在接收着色器中声明一个类似的输入。当双方的类型和名称相同时,OpenGL 会将这些变量链接在一起,然后可以在着色器之间发送数据(这是在链接程序对象时完成的)。为了向您展示这在实践中是如何工作的,我们将更改上一章中的着色器,让顶点着色器决定片段着色器的颜色。
顶点着色器

#version 330 core
layout (location = 0) in vec3 aPos; // the position variable has attribute position 0  
out vec4 vertexColor; // specify a color output to the fragment shader

void main()
{
   
    gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor
    vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color
}

片段着色器

#version 330 core
out vec4 FragColor;  
in vec4 vertexColor; // the input variable from the vertex shader (same name and same type)  

void main()
{
   
    FragColor = vertexColor;
} 
  • uniforms

uniforms是将数据从 CPU 上的应用程序传递到 GPU 上的着色器的另一种方式。然而,与顶点属性相比,uniforms略有不同。首先,uniforms是全局的,意味着每个着色器程序对象的统一变量是唯一的,并且可以在着色器程序的任何阶段从任何着色器访问。其次,无论您将uniforms 值设置为什么,uniforms 值都会保持其值,直到它们被重置或更新。

要在 GLSL 中声明全局变量,我们只需将uniform关键字添加到具有类型和名称的着色器中。我们可以在着色器中使用声明的uniforms变量,现在通过uniform来设置三角形的颜色:

#version 330 core
out vec4 FragColor;  
uniform vec4 ourColor; // we set this variable in the OpenGL code.

void main()
{
   
    FragColor = ourColor;
}   

我们在片段着色器中声明了一个uniforms vec4 ourColor并将片段的输出颜色设置为这个全局变量的内容。由于uniforms 是全局变量,我们可以在我们想要的任何着色器阶段定义它们,因此无需再次通过顶点着色器来获取片段着色器的内容。我们没有在顶点着色器中使用这个全局变量,所以没有必要在那里定义它。注意,如果你声明了一个没有在你的 GLSL 代码中的任何地方使用的uniform变量,编译器会悄悄地从编译版本中删除这个变量。

uniforms目前是空的;我们还没有向uniforms添加任何数据。我们首先需要在着色器中找到全局变量的索引位置。一旦我们有了uniforms的索引位置,我们就可以更新它的值。不是将单一颜色传递给片段着色器,让我们通过随着时间的推移逐渐改变颜色来增加趣味:

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);

首先,我们通过以秒为单位获取时间;然后我们通过使用正弦函数在范围0.0-1.0内改变颜色并将结果存储在greenValue 中;接下来我们使用查询uniforms ourColor的位置函数glGetUniformLocation. 我们向查询函数提供着色器程序和uniforms的名称(我们要从中检索位置)。如果glGetUniformLocation返回-1,它找不到位置。最后,我们可以使用glUniform4f函数。请注意,找到统一位置不需要您首先使用着色器程序,但更新统一确实需要您首先使用该程序(通过调用使用程序),因为它在当前活动的着色器程序上设置为uniforms。

现在我们知道如何设置全局变量的值,并使用它们进行渲染。如果我们希望颜色逐渐变化,我们希望每帧更新这个uniform,否则如果我们只设置一次,三角形将保持单一的纯色。所以我们计算greenValue并在每次渲染迭代时更新统一值:
整个工程源码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char *vertexShaderSource ="#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos, 1.0);\n"
    "}\0";

const char *fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "uniform vec4 ourColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = ourColor;\n"
    "}\n\0";

int main()
{
   
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
   
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
   
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
   
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
   
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // link shaders
    
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值