• OpenGL之着色器 安静点


    着色器

    我们要写一个三角形,需要经理上图流程,其中顶点着色器片段着色器需要我们自己写。

    着色器基本上只是一个程序, 不同点在于它是运行在我们的gpu上,在我们的显卡上,而不是像C++程序一样运行在cpu上。

    (1)顶点着色器
    获取了每一个我们想要渲染的顶点的调用。
    这个例子上,我我们有三个顶点,这就意味着顶点着色器会调用三次,每个顶点各一次。一个顶点着色器的基本目的就是告诉opengl你想要那个顶点在显示器的哪里,简单的说就是窗口的哪里。

    (2)片段着色器

    片段着色器会为每一个像素运行一次光栅化,光栅化实际上是画在屏幕上。
    我们的窗口实际上是由像素组成的。

    (3)顶点着色器和片段着色器的区别

    现在你可以注意到这两者之间有一点不一样,顶点着色器调用三次,片段着色器调用成百上千次,这取决于我们的三角形在我们的屏幕上占用了多大空间,如果你有一个细小的三角形,在你的窗口上一个真的很小的三角形,那可能会调用额外的50次。如果你有一个巨大的三角形,这充满了你的屏幕,这可能是有一百万像素或者50万像素,那就意味着片段着色器要调用50万次。

    需要用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后进行编译。

    #version 330 core表示OpenGL的版本号及使用核心模式。(对输入数据,只传输未处理)。
    in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。顶点都为3D坐标,因此创建vec3类型的输入变量aPos。
    顶点着色器需要将数据赋值给预定义的gl_Position变量(顶点着色器的输出,vec4类型,w分量设置为1.0f)。我们同样也通过layout (location = 0)设定了输入变量的位置值。

    片段着色器所做的是计算像素最后的颜色输出
    片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色,我们应该自己将其计算出来。声明输出变量可以使用out关键字,这里我们命名为FragColor。下面,我们将一个Alpha值为1.0(1.0代表完全不透明)的橘黄色的vec4赋值给颜色输出。

    (4)编译着色器

    现在,我们暂时将顶点着色器的源代码硬编码在代码文件顶部的C风格字符串中:

    const char *vertexShaderSource = "#version 330 core\n"
     "layout (location = 0) in vec3 aPos;\n" 
    "void main()\n"
     "{\n" 
    " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" 
    "}\0"; 
    
    const char *fragmentShaderSource = "#version 330 core\n" 
    "out vec4 FragColor;\n" 
    "void main()\n" 
    "{\n" 
    " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" 
    "}\n\0";

    为了让OpenGL使用着色器,必须在运行时从源码中动态编译着色器。首先创建着色器对象。 
    各个阶段的着色器需要通过着色器程序对象链接起来。着色器程序对象是多个着色器组合的最终链接版本。
    将着色器链接到程序时,会将每个着色器的输出链接到下一个着色器的输入。如果输出和输入不匹配,会出现链接错误。

    我们首先要做的是创建一个着色器对象,注意还是用ID来引用的。所以我们储存这个顶点着色器为unsigned int,然后用glCreateShader创建这个着色器:

    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);

    我们把需要创建的着色器类型以参数形式提供给glCreateShader。由于我们正在创建一个顶点着色器,传递的参数是GL_VERTEX_SHADER

    下一步我们把这个着色器源码附加到着色器对象上,然后编译它:

    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL

    (5)着色器程序

    着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
    当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,你会得到一个连接错误。
    创建一个程序对象很简单:

    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();

     glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用。现在我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们:

    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

     得到的结果就是一个程序对象,我们可以调用glUseProgram函数,用刚创建的程序对象作为它的参数,以激活这个程序对象:

    glUseProgram(shaderProgram);

     对了,在把着色器对象链接到程序对象以后,记得删除着色器对象,我们不再需要它们了:

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    代码示例:

    // GLAD的include文件包含所需的OpenGL头文件(如GL/GL.h),因此确保在其他需要OpenGL的头文件(如GLFW)之前包含GLAD。就是#include <glad/glad.h> 放在最前面
    #include <glad/glad.h> 
    #include <GLFW/glfw3.h>
    #include <iostream>
    float vertices[] = {
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
    };
    const char* vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";
    
    const char* fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n\0";
    
    void framebuffer_size_callback(GLFWwindow* window, int width, int height);
    void processInput(GLFWwindow* window);
    
    int main() {
        // 初始化GLFW,只有初始化完成之后才能够使用GLFW的函数
        glfwInit();
        // GLFW配置设置
        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  
    
        // 创建窗口 大小和名称
        GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
        if (window == NULL) {
            std::cout << "Failed to create GLFW window" << std::endl;
            // 此函数销毁所有剩余的窗口和光标
            glfwTerminate();
            return -1;
        }
        //GLFW将窗口的上下文设置为当前线程的上下文
        glfwMakeContextCurrent(window);
        // 告诉GLFW我们希望每当窗口调整大小的时候调用这个函数
        glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
        //GLAD
        // glad: 加载所有OpenGL函数指针
        if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
            std::cout << "Failed to initialize GLAD" << std::endl;
            return -1;
        }
    #pragma region 着色器
    
        // 创建和编译着色器程序 
    //顶点着色器
        unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
        glCompileShader(vertexShader);
        // 检查编译错误
        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;
        }
        // 片段着色器
        unsigned int 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);
            std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
        }
        //着色器程序
        unsigned int shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);
        //链接错误检查
        glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
        if (!success) {
            glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
        }
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
    
    #pragma endregion
    
        //创建VBO和VAO对象,并赋予ID
        unsigned int VBO, VAO;
        //创建1个VAO对象
        glGenVertexArrays(1, &VAO);
        //创建1个VBO对象
        glGenBuffers(1, &VBO);
        //绑定VBO和VAO对象
        glBindVertexArray(VAO);
        //缓冲对象如果绑定的是顶点属性则用:GL_ARRAY_BUFFER
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        //为当前绑定到target的缓冲区对象创建一个新的数据存储。
        //如果data不是NULL,则使用来自此指针的数据初始化数据存储
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
        //上面我们将数据存到了缓冲区对象中,下面就需要
        //告知Shader(着色器)如何解析缓冲里的属性值
        //第一个属性是从哪个位置开始解析
        //第二个属性是每个顶点属性由几个组合而成
        //第三个属性是数组中每个元素的类型
        //第四个表示是否标准化,这里暂时不需要,需要注意只有整型值才会有效,如果浮点型的数据不会起作用
        //第五个是步长,因为我们这个是数组中每3个元素组成一个顶点属性,而且是float类型
        //第六个是偏移量,我们这里为0,就是从0开始读取数组中的数据的
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
        //开启VAO管理的第一个属性值
        glEnableVertexAttribArray(0);
        //作为习惯,用完之后将VBO,VAO解绑,需要的时候可以重新绑定
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0); 
        // 渲染循环 一个循环就是一帧 只要是窗体不关闭,就会一直循环
        while (!glfwWindowShouldClose(window)) {
            processInput(window);
    
            // 在这里,我们将屏幕设置为了类似黑板的深蓝绿色
            glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //状态设置
            // 调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
            glClear(GL_COLOR_BUFFER_BIT); //状态使用
    
    #pragma region 绘制三角形
    
    // 首先,要选择使用哪个着色器 每个着色器调用和渲染调用都会使用这个程序对象
            glUseProgram(shaderProgram);
    
            // 需要注意的是前面我们已经解绑了VAO,所以现在是无法解析数据的,所以我们需要重新绑定,
            // 至于数据我们已经存到缓冲区了
            glBindVertexArray(VAO);
            // 从数据数组中indexwei0处开始读取,每三个做一个三角形的顶点。第三个参数是说一共绘制三个顶点数据(每个顶点由vertices数组中的3个元素组成)
            glDrawArrays(GL_TRIANGLES, 0, 3);
    #pragma endregion
    
            // glfw: 交换缓冲区 该函数在指定窗口的前后缓冲区交换
            // 前缓冲区:屏幕上显示的图像
            // 后缓冲区:正在渲染的图像
            // glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),
            // 它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上
            glfwSwapBuffers(window);
            // 轮询IO事件(按键按下 / 释放、鼠标移动等)通过下面方法就可以是得窗体对鼠标做出的动作做出反应,比如关闭,移动窗体等
            glfwPollEvents();
        }
        // glfw: 回收前面分配的GLFW先关资源. 一定要注意,只有关闭窗体之后才会跳出while循环走到这一步!!!
        glfwTerminate();
    
        // 在while循环退出后释放内存:
        glDeleteVertexArrays(1, &VAO);
        glDeleteBuffers(1, &VBO);
        glDeleteProgram(shaderProgram);
    
        return 0;
    }
    // glfwGetKey函数:需要一个窗口以及一个按键作为输入;函数将会返回这个按键是否正在被按下
    void processInput(GLFWwindow* window)
    {
        // 如果按下了ESC键,设置窗体的关闭标志为true,代表窗体可以退出
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
            glfwSetWindowShouldClose(window, true);
    }
    
    // 当改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用
    void framebuffer_size_callback(GLFWwindow* window, int width, int height)
    {
        // 设置窗口维度
        // glViewport(前两参数为窗口左下角位置,3.宽度,4.高度)
        glViewport(0, 0, width, height);
    }

     结果:

     

     参考:

     https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/#_5

  • 相关阅读:
    2.搭建第一个http服务:三层架构
    1.基础入门
    MyISAM和InnoDB索引区别
    分区
    事务的四大特性
    事务
    String
    自己写native方法
    序列化和反序列化
    反射
  • 原文地址:https://www.cnblogs.com/anjingdian/p/16655607.html
Copyright © 2020-2023  润新知