自学OpenGL(七)-着色器

前言

着色器是运行在GPU上的程序,为图形渲染管线特定部分而运行,从某种意义上来说,着色器是把输入转化为输出的程序。着色器程序是完全独立的程序,着色器之间不能直接通信,只能通过输入输出实现通信。在前面的几篇文章中,粗略地介绍了一下着色器,接下来我将详细的介绍着色器。

GLSL

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。用法与特性可学习 GLSL 中文手册

封装我们自己的着色器类

通过我们几篇博客了解到着色器需要编写、编译、管理着色器,这是件麻烦事,在着色器主题的最后,我们会写一个类来让我们的生活轻松一点,它可以从硬盘读取着色器,然后编译并链接它们,并对它们进行错误检测,这就变得很好用了。这也会让你了解该如何封装目前所学的知识到一个抽象对象中。

我们会把着色器类全部放在在头文件里,主要是为了学习用途,当然也方便移植。类结构设计如下:

  1. 声明一个抽象类 ShaderStreamInterface,该抽象类代表一个着色器程序的 GLSL 源代码。
#define _SHADER_SRC(...) #__VA_ARGS__
#define SHADER_SRC(...) _SHADER_SRC(__VA_ARGS__)

class ShaderStreamInterface {
public:
    virtual char *getVertexCode() = 0;
    
    virtual char *getFragmentCode() = 0;
protected:
    char *vertextCode;
    char *fragmentCode;
};
  1. 声明一个着色器类 Shader,勇于编译、链接、管理着色器。

在Shader.h 文件中代码如下

#define GLEW_STATIC
#include <GL/glew.h>
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include "ShaderStreamInterface.h"
/** 着色器类 */
class Shader {
    
public:
    // 程序ID
    unsigned int ID;
    // 构造器读取并构建着色器
    Shader(ShaderStreamInterface *shaderStream);
    // 使用/激活程序
    Shader * use();
    // uniform工具函数
    Shader * setBool(const std::string &name, bool value);
    Shader * setInt(const std::string &name, int value);
    Shader * setFloat(const std::string &name, float value);
    Shader * setColor(const std::string &name, float red, float green, float blue, float alpha);
};

在Shader.cpp 文件中的代码如下:


Shader:: Shader(ShaderStreamInterface *shaderStream) {
    // 1. 从文件路径中获取顶点/片段着色器
    const char* vShaderCode = shaderStream->getVertexCode();
    const char* fShaderCode = shaderStream->getFragmentCode();
    
    // 2. 编译着色器
    unsigned int vertex, fragment;
    int success;
    char infoLog[512];

    // 顶点着色器
    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vShaderCode, NULL);
    glCompileShader(vertex);
    // 打印编译错误(如果有的话)
    glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
    if(!success) {
        glGetShaderInfoLog(vertex, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    };

    // 片段着色器也类似
    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &fShaderCode, NULL);
    glCompileShader(fragment);
    
    // 着色器程序
    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::LINKING_FAILED\n" << infoLog << std::endl;
    }

    // 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
    glDeleteShader(vertex);
    glDeleteShader(fragment);
}

Shader * Shader:: use() {
    glUseProgram(ID);
    return this;
}

Shader * Shader:: setBool(const std::string &name, bool value) {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    return this;
}

Shader * Shader:: setInt(const std::string &name, int value) {
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    return this;
}

Shader * Shader:: setFloat(const std::string &name, float value) {
    glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    return this;
}

Shader * Shader:: setColor(const std::string &name, float red, float green, float blue, float alpha) {
    glUniform4f(glGetUniformLocation(ID, name.c_str()), red, green, blue, alpha);
    return this;
}

上面的设计方式有如下几大优点:

  1. 功能职责分明,ShaderStreamInterface 抽象类专注于着色器代码的编写、加载和管理,,Shader 类专注于着色器的编译、链接、管理着色器的生命周期。
  2. 扩展性强,如果需要编写新的着色器代码,只需要新写一个类继承自 ShaderStreamInterface 即可。
  3. 易于调试,可以分别对 Shader 类以及 ShaderStreamInterface 其子类进行单元测试。

现在整个着色器类已经设计完成,接下来我们来完成绘制一个有颜色的三角形

绘制有颜色的三角形

  1. 定义 ShaderStream 类继承自 ShaderStreamInterface,并编写顶点着色器和片段着色器代码
    .hpp
#include <stdio.h>
#include "ShaderStreamInterface.h"
/** 章节案例 */
class ShaderStream: public ShaderStreamInterface {
public:
    ShaderStream();
    char *getVertexCode() {
        return vertextCode;
    }
    char *getFragmentCode() {
        return fragmentCode;
    }
protected:
    char *vertexStream();
    char *fragmentStream();
};

.cpp

ShaderStream:: ShaderStream() {
    // 顶点着色器
    vertextCode = vertexStream();
    // 片段着色器代码
    fragmentCode = fragmentStream();
}

char * ShaderStream:: vertexStream() {
    return SHADER_SRC(
                      \#version 330 core\n
                      layout (location = 0) in vec3 aPos;
                      layout (location = 1) in vec3 aColor;

                      out vec3 ourColor;

                      void main()
                      {
                          gl_Position = vec4(aPos, 1.0);
                          ourColor = aColor;
                      }
            );
}

char * ShaderStream:: fragmentStream() {
    return SHADER_SRC(
                      \#version 330 core\n
                      out vec4 FragColor;
                      in vec3 ourColor;
                      void main() {
                          FragColor = vec4(ourColor, 1.0);
                      }
    );
}
  1. 来到主应用程序 main 函数中,引入相关类
#include "ShaderStream.hpp"
#include "Shader.hpp"
  1. 初始化glfw,初始化窗口,初始化 glew,前面已经讲过了,这里不再重复。
  2. 初始化着色器
    // 着色器代码对象
    ShaderStream *shaderCode = new ShaderStream();
    // 着色器程序
    Shader *shader = new Shader(shaderCode);
  1. 创建顶点数组、顶点缓冲对象、顶点数组对象,这块是跟之前你好 三角形一样一样的
    // 创建顶点数组多想 VAO
    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    // 顶部
    };
    
    unsigned int vao, vbo;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    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);
    // 颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
    glEnableVertexAttribArray(1);
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
  1. 执行渲染循环
    while (!glfwWindowShouldClose(window)) {
        procesInput(window);
        glClearColor(0.2, 0.2, 0.3, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        shader->use();
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
        
    }
  1. 处理退出,释放资源
    std::cout << "Hello, World!\n";
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glDeleteProgram(shader->ID);
    glfwTerminate();

运行command + R 运行,得到一个五彩斑斓的三角形:


FileSharing.png

如果你运行出来有问题或者出错了,情仔细检查一下代码,或者在本文下方下载笔者写的demo。

源代码在这里:https://github.com/muxueChen/LearnOpenGL

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

推荐阅读更多精彩内容