OpenGL 学习笔记(五)

一、OpenGL着色器

       从基本意义上来说,着色器只是一种把输入转化为输出的程序。

       着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。着色器是使用一种叫 GLSL(全称 OpenGL Shading Language) 的类C语言写成的。GLSL 是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。着色器的开头总是要声明版本,接着是 输入和输出变量、uniform 和 main 函数。每个着色器的入口点都是 main 函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。

一个典型的着色器的模板程序为:

#version version_number

in vector_type in_variable_name;

out vector_type out_variable_name;

uniform type uniform_name;

void main()

{

// 处理输入并进行一些图形操作

...

// 将处理过的结果送到输出变量

out_variable_name = 处理过的结果;

}

        GLSL中的向量是一个可以包含有1、2、3 或者 4 个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式( n 代表分量的数量):

         多数时候我们使用 “vec+n”,因为 float 足够满足大多数要求了。一个向量的分量可以通过 vec.x 这种方式获取,这里 x 是指这个向量的第一个分量。我们可以分别使用 “ .x、.y、.z 和 .w ” 来获取它们的第 1、2、3、4 个分量。GLSL 也允许你对颜色使用 rgba ,或是对纹理坐标使用 stpq 访问相同的分量。

二、OpenGL顶点/片元着色器

         顶点着色器与片元着色器之间的联系:

   我们一般先通过顶点着色器,在输入端获得颜色,作为顶点属性,再传到片元着色器中进行处理,最后输出出来。虽然着色器是各自独立的小程序,但它们都是一个整体的一部分。GLSL 定义了 in 和 out 关键字专门来实现这个目的。每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。但在顶点和片段着色器中会有点不同。顶点着色器应该接收的是一种特殊形式的输入。顶点着色器的输入特殊在,它从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用 location 这一元数据指定输入变量。书写格式:layout (location = 0)。顶点着色器需要为它的输入提供一个额外的 layout 标识,这样我们才能把它链接到顶点数据。

(1).渲染彩色三角形的顶点着色器示例:

// 文件名为 “shader_v.txt”
#version 330 core                            // 3.30版本
layout(location = 0) in vec3 position;        // 位置变量的属性位置值为0
layout(location = 1) in vec3 color;            // 颜色变量的属性位置值为1
out vec3 ourColor;                            // 颜色输出
void main()
{
    gl_Position = vec4(position, 1.0f);        // 核心函数(位置信息赋值)
    ourColor = color;
} 

        备注:把txt和下面的txt一起放到同.cpp同一个目录下。

        另一个是片元着色器,它需要一个 vec4 颜色输出变量,因为片元着色器需要生成一个最终输出的颜色。如果我们打算从一个着色器向另一个着色器发送数据,我们必须在发送方中声明一个输出,并在接收方中声明一个类似的输入。当类型和名字都一样的时候,OpenGL 就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。为了展示这是如何工作的,我们会稍微改动一下之前教程里的那个着色器,让顶点着色器为片元着色器决定颜色。

(2).渲染彩色三角形的片元着色器示例

// 文件名为 “shader_f.txt”
#version 330 core        // 3.30版本
in vec3 ourColor;        // 输入(3维)颜色向量
out vec4 FragColor;     // 输出到四个浮点数构成的一个(4维)向量 FragColor
void main()
{
    FragColor = vec4(ourColor, 1.0f);    // 核心函数(颜色信息赋值)
}

三、创建着色器类

下面实现一个典型着色器类,代码如下:

#pragma once    
#include<string>
#include<fstream>
#include<sstream>
#include<iostream>
using namespace std;
#include"glew-2.2.0\include\GL\glew.h"    // 注:这一部分要根据个人情况进行设定
#include"glfw-3.3.4.bin.WIN32\include\GLFW\glfw3.h"

// 我们自己的着色器
class Shader
{
private:
    GLuint vertex, fragment;    // 顶点着色器 和 片元着色器 
public:
    GLuint Program;    // 着色器程序的ID

    // Constructor(着色器构造函数)
    Shader( const GLchar *vertexPath, const GLchar *fragmentPath )
    {
        // 文件读取系列的变量定义
        string vertexCode;
        string fragmentCode;
        ifstream vShaderFile;
        ifstream fShaderFile;

        // 异常机制处理:保证ifstream对象可以抛出异常:
        vShaderFile.exceptions(ifstream::badbit);
        fShaderFile.exceptions(ifstream::badbit);
try
        {
            // 打开文件
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            stringstream vShaderStream, fShaderStream;

            // 读取文件的缓冲内容到数据流中
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();

            // 关闭文件处理器
            vShaderFile.close();
            fShaderFile.close();

            // 转换数据流到string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();

        } catch( ifstream::failure e  ){    // 发生异常时输出
            cout<<"ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ"<<endl;
        }

        /* 将 string 类型的字符串转化为 char数组 类型 */
        const GLchar *vShaderCode = vertexCode.c_str();
        const GLchar *fShaderCode = fragmentCode.c_str();

        /* 顶点着色器 */
        vertex = glCreateShader(GL_VERTEX_SHADER);        // 创建顶点着色器对象
        glShaderSource(vertex, 1, &vShaderCode, NULL);        // 将顶点着色器的内容传进来
        glCompileShader(vertex);                            // 编译顶点着色器
        GLint flag;                                        // 用于判断编译是否成功
        GLchar infoLog[512];                
        glGetShaderiv(vertex, GL_COMPILE_STATUS, &flag); // 获取编译状态
        if( !flag )
        {
            glGetShaderInfoLog(vertex, 512, NULL, infoLog);    
            cout<<"ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"<<infoLog<<endl;
        }
/* 片元着色器 */
        fragment = glCreateShader(GL_FRAGMENT_SHADER);    // 创建片元着色器对象
        glShaderSource(fragment, 1, &fShaderCode, NULL);        // 将片元着色器的内容传进来
        glCompileShader(fragment);                            // 编译顶点着色器
        glGetShaderiv(fragment, GL_COMPILE_STATUS, &flag);    // 获取编译状态
        if( !flag )
        {
            glGetShaderInfoLog(fragment, 512, NULL, infoLog);     
            cout<<"ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"<<infoLog<<endl;
        }

        /* 着色器程序 */
        this->Program = glCreateProgram();
        glAttachShader(this->Program, vertex);
        glAttachShader(this->Program, fragment);
        glLinkProgram(this->Program);
        if( !flag )
        {
            glGetProgramInfoLog(this->Program, 512, NULL, infoLog);    
            cout<<"ERROR::SHADER::PROGRAM::LINKING_FAILED\n"<<infoLog<<endl;
        }
        // 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
        glDeleteShader(vertex);        
        glDeleteShader(fragment);
    }

    // Deconstructor(析构函数)
    ~Shader()
    {
        glDetachShader(this->Program, vertex);
        glDetachShader(this->Program, fragment);
        glDeleteShader(vertex);
        glDeleteShader(fragment);
        glDeleteProgram(this->Program);
    }

void Use()
    {
        glUseProgram(this->Program);
    }
};

四、绘制彩色三角形

        常规的绘制流程如下:

        引入相应的库-->编写顶点位置和颜色-->编写顶点着色器-->编写片元着色器(也称片段着色器)-->编写着色器程序-->设置链接顶点属性-->设置顶点缓冲对象(VBO)-->设置顶点数组对象(VAO) (也称顶点阵列对象)-->绘制三角形。

        实现代码如下:

/* 引入相应的库 */
#include <iostream>
using namespace std;
#define GLEW_STATIC    
#include <glew.h>    
#include <glfw3.h> 
#include "Shader.h"

/* 编写各顶点位置与颜色 */
GLfloat vertices_1[] = 
{    // position                // color
    0.0f, 0.5f, 0.0f,        1.0f, 0.0f, 0.0f,                // 上顶点(红色)
    -0.5f, -0.5f, 0.0f,        0.0f, 1.0f, 0.0f,        // 左顶点(绿色)
    0.5f, -0.5f, 0.0f,        0.0f, 0.0f, 1.0f        // 右顶点(蓝色)
};

const GLint WIDTH = 800, HEIGHT = 600;        // 窗口的长和宽
int main()
{
    /* 初始化 */
    glfwInit();
    GLFWwindow* window_1 = glfwCreateWindow(WIDTH, HEIGHT, "A Beautiful Triangle", nullptr, nullptr);
    int screenWidth_1, screenHeight_1;
    glfwGetFramebufferSize(window_1, &screenWidth_1, &screenHeight_1);
    glfwMakeContextCurrent(window_1);
    glewInit();

    /* 将我们自己设置的着色器文本传进来 */
    Shader ourShader = Shader("shader_v.txt", "shader_f.txt");    // 相对路径

    /* 设置顶点缓冲对象(VBO) + 设置顶点数组对象(VAO)  */
    GLuint VAO, VBO;                    
    glGenVertexArrays(1, &VAO);        
    glGenBuffers(1, &VBO);            
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);    
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_1), vertices_1, GL_STATIC_DRAW);    

    /* 设置链接顶点属性 */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);    // 通道 0 打开
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));
    glEnableVertexAttribArray(1);    // 通道 1 打开

    // draw loop 画图循环
while (!glfwWindowShouldClose(window_1))
    {
        glViewport(0, 0, screenWidth_1, screenHeight_1);
        glfwPollEvents();
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        /*  第九步:绘制三角形 */
        ourShader.Use();                    // 图形渲染
        glBindVertexArray(VAO);                // 绑定 VAO
        glDrawArrays(GL_TRIANGLES, 0, 3);    // 画三角形  从第0个顶点开始 一共画3次
        glBindVertexArray(0);                // 解绑定

        glfwSwapBuffers(window_1);
    }

    glDeleteVertexArrays(1, &VAO);            // 释放资源    
    glDeleteBuffers(1, &VBO);
    glfwTerminate();                        // 结束
    return 0;
}

运行效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大王算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值