首页 > 其他分享 >OpenGL入门——着色器类

OpenGL入门——着色器类

时间:2023-09-17 10:57:58浏览次数:44  
标签:std 入门 OpenGL void window include 着色器 string

着色器的编写、编译、管理是个很繁琐的事。所以就需要写一个类,这个类可以从文件读取着色器源码,可以编译链接它们,可以对它们进行错误检测,可以设置Uniform值。

 

1. 类的声明

#pragma once

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

//着色器对象类型
#define SHADER_TYPE_VERTEX        0    //顶点着色器对象
#define SHADER_TYPE_FRAGMENT    1    //片段着色器对象
#define SHADER_TYPE_PROGRAM        2    //着色器程序对象


class CShader
{
public:
    CShader(std::string vertex_path, std::string fragment_path);

    ~CShader();

    //激活着色器程序
    void run();

    //设置uniform值
    void setUniformBool(const std::string val_name, bool val);

    void setUniformInt(const std::string val_name, int val);

    void setUniformFloat(const std::string val_name, float val);

private:
    //编译、链接着色器
    void initialize(const char *vertexShaderSource, const char *fragmentShaderSource);

    //编译检测
    void checkShader(unsigned int shader_id, int type);

private:
    unsigned int        ID;
};

2. 类的定义

#include "shader.h"

CShader::CShader(std::string vertex_path, std::string fragment_path)
{
    // 1. 从文件路径中获取顶点/片段着色器
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;
    // 保证ifstream对象可以抛出异常:
    vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    try
    {
        // 打开文件
        vShaderFile.open(vertex_path);
        fShaderFile.open(fragment_path);
        std::stringstream vShaderStream, fShaderStream;
        // 读取文件的缓冲内容到数据流中
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();
        // 关闭文件处理器
        vShaderFile.close();
        fShaderFile.close();
        // 转换数据流到string
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    }
    catch (std::ifstream::failure e)
    {
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
    }
    const char* vShaderCode = vertexCode.c_str();
    const char* fShaderCode = fragmentCode.c_str();

    initialize(vShaderCode, fShaderCode);
}

CShader::~CShader()
{
    glDeleteProgram(ID);
}

void CShader::initialize(const char *vertexShaderSource, const char *fragmentShaderSource)
{
    //创建一个顶点着色器对象
    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//着色器源码附加到着色器对象上
    glCompileShader(vertexShader);//编译源码
    checkShader(vertexShader, SHADER_TYPE_VERTEX);

    //创建一个片段着色器对象,注意还是用ID来引用的
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);//编译源码
    checkShader(fragmentShader, SHADER_TYPE_FRAGMENT);

    //创建一个着色器的程序
    ID = glCreateProgram();
    glAttachShader(ID, vertexShader);//把之前编译的着色器附加到程序对象上
    glAttachShader(ID, fragmentShader);
    glLinkProgram(ID);//glLinkProgram链接它们
    checkShader(ID, SHADER_TYPE_PROGRAM);
    
    //链接后即可删除
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

}

void CShader::checkShader(unsigned int shader_id, int type)
{
    int  success;
    char infoLog[512];
    if (type != SHADER_TYPE_PROGRAM)
    {
        glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success);//用glGetShaderiv检查是否编译成功
        if (!success)
        {
            glGetShaderInfoLog(shader_id, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
        }
    }
    else
    {
        glGetProgramiv(shader_id, GL_COMPILE_STATUS, &success);//用glGetProgramiv检查是否编译成功
        if (!success)
        {
            glGetShaderInfoLog(shader_id, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::PROGRAM::LINK_FAILED\n" << infoLog << std::endl;
        }
    }
}

void CShader::run()
{
    glUseProgram(ID);
}

void CShader::setUniformBool(const std::string val_name, bool val)
{
    glUniform1i(glGetUniformLocation(ID, val_name.c_str()), (int)val);
}

void CShader::setUniformInt(const std::string val_name, int val)
{
    glUniform1i(glGetUniformLocation(ID, val_name.c_str()), val);
}

void CShader::setUniformFloat(const std::string val_name, float val)
{
    glUniform1i(glGetUniformLocation(ID, val_name.c_str()), val);
}

 

3. 类的使用

把着色器源码保存到文件中,后缀可以自定义,如下:

顶点着色器文件hello_triangle.vs

//vertex shader source
#version 330 core
layout(location = 0) in vec3 position;    //位置变量的属性位置为0
layout(location = 1) in vec3 color;        //颜色变量的属性位置为1
out vec3 vertexColor;                    //为片段着色器指定一个颜色输出

void main()
{
    gl_Position = vec4(position, 1.0);    //opengl顶点坐标
    vertexColor = color;                //从顶点数据那里得到的输入颜色
}

片段着色器文件hello_triangle.fs

//fragment shader source
#version 330 core
out vec4 fragColor;        //像素的最终颜色
in vec3 vertexColor;    //从顶点着色器传来的输入变量(名称、类型必须相同)
//uniform vec3 ourColor;//程序中设定这个变量

void main()
{
    fragColor = vec4(vertexColor, 1.0);
}

应用程序中使用着色器类,只需要2步:定义着色器和激活程序对象

    ...//初始化窗口

    ///定义着色器
    CShader shader("hello_triangle.vs", "hello_triangle.fs");
    
    ...//定义顶点对象及其属性

        
    while (!glfwWindowShouldClose(window))
    {
        processInput(window);


        //清空屏幕
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);


        ///绘制物体
        shader.run();//激活程序对象

        ...

        glfwPollEvents();//检查有没有触发什么事件
    }

    //释放对象
    ...

 

完整代码示例

//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "shader.h"

//改变窗口大小
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

//输入
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//点击ESC键退出绘制
        glfwSetWindowShouldClose(window, true);
}

GLFWwindow* init_window()
{
    ///窗口初始化
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号,当API以不兼容的方式更改时,该值会增加。
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本号,当特性被添加到API中时,它会增加,但是它保持向后兼容。
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//使用核心模式,不兼容已废弃函数

    //创建glfw窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "ping-window", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "failed to create GLFW window" << std::endl;
        glfwTerminate();//释放/删除之前的分配的所有资源
        return nullptr;
    }
    glfwMakeContextCurrent(window);//将窗口的上下文设置为当前线程的主上下文
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//注册为调整窗口回调函数

    //GLAD是用来管理OpenGL的函数指针的,在调用任何OpenGL的函数之前初始化GLAD
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))//给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数
    {
        std::cout << "failed to intialize GLAD" << std::endl;
        return nullptr;
    }

    glViewport(0, 0, 800, 600);//处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)

    return window;
}

int hello_triangle()
{
    GLFWwindow* window = init_window();

    ///定义着色器
    CShader shader("hello_triangle.vs", "hello_triangle.fs");

    ///定义顶点对象
    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.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 顶部
    };
    //生成VAO对象,缓冲ID为VAO
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);//绑定VAO,从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO,供之后使用

    //生成VBO对象,缓冲ID为VBO
    unsigned int VBO;
    glGenBuffers(1, &VBO);//第一个参数GLsizei是要生成的缓冲对象的数量,第二个GLuint是要输入用来存储缓冲对象名称的数组

    //绑定到目标对象,VBO变成了一个顶点缓冲类型
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//第一个就是缓冲对象的类型,第二个参数就是要绑定的缓冲对象的名称
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//数据传入缓冲内存中,GL_STATIC_DRAW:数据不会或几乎不会改变; GL_DYNAMIC_DRAW:数据会被改变很多; GL_DYNAMIC_DRAW:数据会被改变很多

    //设置顶点属性指针,如何解析顶点数据
    /*
    第一个参数指定我们要配置的顶点属性,顶点着色器中使用layout(location = 0)定义
    第二个参数指定顶点属性的大小
    第三个参数指定数据的类型
    第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间
    第五个参数步长(Stride),它告诉我们在连续的顶点属性组之间的间隔
    最后一个参数的类型是void*,数据在缓冲中起始位置的偏移量(Offset)
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);//启用顶点属性layout(location = 0),顶点属性默认是禁用的
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);//启用顶点属性layout(location = 1),顶点属性默认是禁用的
    glBindBuffer(GL_ARRAY_BUFFER, 0);//设置完属性,解绑VBO

    glBindVertexArray(0);//配置完VBO及其属性,解绑VAO


    //绘制模式为线条GL_LINE,填充面GL_FILL
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//正反面

    while (!glfwWindowShouldClose(window))
    {
        processInput(window);


        //清空屏幕
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);


        ///绘制物体
        shader.run();

        glBindVertexArray(VAO);
        //使用VAO绘制
        glDrawArrays(GL_TRIANGLES, 0, 3);//绘制图元为三角形,起始索引0,绘制顶点数量3
        
        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲)
        glfwPollEvents();//检查有没有触发什么事件
    }

    //释放对象
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);

    std::cout << "finish!" << std::endl;
    glfwTerminate();//释放/删除之前的分配的所有资源
    return 0;
}

int main()
{
    hello_triangle();

    return 0;
}

 

运行结果

 

标签:std,入门,OpenGL,void,window,include,着色器,string
From: https://www.cnblogs.com/ping-code/p/17707930.html

相关文章

  • 一文读懂ref函数:从入门到精通的具体操作指南
    什么是ref函数Vue3中的ref函数是一个用于创建响应式数据的函数。在Vue3中,当组件渲染时,会生成一个响应式数据对象,该对象包含了组件实例的所有数据属性。使用ref函数可以创建一个响应式的数据对象,并且可以在组件的生命周期内进行读取和修改。具体来说,ref函数接受一个初始值作为参数,并......
  • openGL (point sprite) 点精灵
    pointsprite这个词一般都是指一个贴了纹理图片的点。OpenGL在描述每个点的时候只用了一个vertex,这就使得点精灵无法像其他图元那样,去指定纹理坐标参与后面的插值过程。为了解决这个限制,OpenGL会帮你去生成点精力的纹理坐标,你有了这些纹理坐标,你就可以干任何事情了。有了点精灵后......
  • Reverse入门指北
     moectf{F1rst_St3p_1s_D0ne}Reverse入门指北两个附件,其中有一个exe,但是直接打开失败了  拖入die,无壳32位拖入ida,F5\shift+F12\ctrl+X都试了试,在shift+F12查看字符串中发现flag,简单解决 复制提交moectf{F1rst_St3p_1s_D0ne} ......
  • GPT之路(八) LangChain - Models入门
    环境:Python3.11.4,LangChain0.0.270,Jupyter Models模型简介官方地址:LangChian-ModelsLangchain所封装的模型分为两类:大语言模型(LLM)聊天模型(ChatModels)Langchain的支持众多模型供应商,包括OpenAI、ChatGLM、ModelScope、HuggingFace等。后面的示例我将以OpenA......
  • Blender入门--快捷键
    为什么要学习快捷键快捷键是几乎所有软件都会必备的一种功能,快捷键的使用会大大提高软件的使用效率,有一些软件可能只有几个快捷键,但是像我们要学习的blender,就有许多的快捷键,我们没必要全部都掌握,但是掌握最基本的快捷键操作能够大大提高我们的建模效率。快捷键介绍基础操作首......
  • GPT之路(七) LangChain AI编成框架入门的第一个demo
    环境:Python3.11.4,LangChain0.0.2701.Langchain简介1.1 PythonLangchain官方文档大型语言模型(LLM)正在成为一种具有变革性的技术,使开发人员能够构建以前无法实现的应用程序。然而,仅仅依靠LLM还不足以创建一个真正强大的应用程序。它还需要其他计算资源或知识来源。Langch......
  • Arduino[入门]舵机的声源定位方向控制测试
      测试视频请点击收看 利用声源定位板让舵机可以根据声源位置定位来控制舵板的方向.因为声源定位板本身已经给出了6个方位高电平的脚位.所以只需要我们在Arduino内调用舵机的程序库.然后各自赋予每个相应角度的各种的舵机角度值就可以做到.声源定位板介绍: 声源定位核心板上......
  • 不知道如何入门Kotlin?《Android版kotlin协程入门进阶实战》带你从入门,带你飞
    作为一名Android开发者,掌握Kotlin语言对于职业发展具有重要意义。随着Google正式将Kotlin确立为Android开发的官方编程语言,Kotlin的地位在Android开发领域迅速攀升。因此,仅仅依靠Java语言进行开发已经不能满足当前市场需求。作为一名Android开发者,学习和掌握Kotl......
  • Android入门教程 | EditText 用户输入
    EditText监听回车使用EditText时,有时候我们会需要监听输入的回车,以做出一些操作。或者需要把回车变成“搜索”,“发送”或“完成”等等。EditText为我们提供了一个属性imeOptions用来替换软键盘中enter键的外观,如actionGo会使外观变成“前往”。需要同时设置android:input......
  • 二、 Axios入门—Axios源码分析
    一、axios与Axios的关系从语法上来说:axios不是Axios的实例从功能上来说:axios是Axios的实例axios是Axios.prototype.request函数bind()返回的函数axios作为对象有Axios原型对象上的所有方法,有Axios对象上所有属性二、instance与axios的区别相同:(1)都是一......