什么是着色器:
着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。是把输入转化为输出的程序。着色器之间的联系,把一个着色器的输出作为另一个着色器的输出。
GLSL:
GLSL的类C语言,为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。
语法:
着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。
结构如下:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
基础数据类型:int,float, double, uint,bool
容器类型:Vector,Matrix
GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n
代表分量的数量):
类型 | 含义 |
---|---|
vecn | 包含n 个float分量的默认向量,如vec2:(0.1f, 0.1f) vec3:(0.0f, 0.1f, 0.0f) |
bvecn | 包含n 个bool分量的向量 |
ivecn | 包含n 个int分量的向量 |
uvecn | 包含n 个unsigned int分量的向量 |
dvecn | 包含n 个double分量的向量 |
大多数时候我们使用vecn
,因为float足够满足大多数要求了。
一个向量的分量可以通过vec.x
这种方式获取,这里x
是指这个向量的第一个分量。你可以分别使用.x
、.y
、.z
和.w
来获取它们的第1、2、3、4个分量。GLSL也允许你对颜色使用rgba
,或是对纹理坐标使用stpq
访问相同的分量。
顶点着色器是从顶点数据中直接接收输入,我们使用location
这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。顶点着色器需要为它的输入提供一个额外的layout
标识,这样我们才能把它链接到顶点数据。(也可以使用glGetAttribLocation查询属性位置值(Location)
片段着色器需要一个vec4
颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。
在输出三角形的例子中,改变顶点着色器的输出颜色,并让片段着色器接收,结果改变了三角形的颜色。在输出三角形的例子中替换下面的代码,得到暗红的三角形
// 顶点着色器代码
const char* vertexShaderSource = "#version 330 core\n"
"layout(location = 0) in vec3 position;\n" // position变量的属性位置值为0
"out vec4 vertexColor;\n"// 为片段着色器指定一个颜色输出
"void main()\n"
"{\n"
"gl_Position = vec4(position, 1.0);\n" //vec3作为vec4的构造器的参数
"vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f); \n" // // 把输出变量设置为暗红色
"}\n\0";
//片段着色器代码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"// 片段着色器输出的变量名可以任意命名,类型必须是vec4
"in vec4 vertexColor;\n"// 从顶点着色器传来的输入变量(名称相同、类型相同)
"void main()\n"
"{\n"
"color = vertexColor;\n"
"}\n\0";
Uniform:
通过uniform直接改变顶点颜色,
颜色随时间改变的代码:
//片段着色器代码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"// 片段着色器输出的变量名可以任意命名,类型必须是vec4
"uniform vec4 ourColor;\n" // 在OpenGL程序代码中设定这个变量
"void main()\n"
"{\n"
"color = ourColor;\n"
"}\n\0";
// 渲染代码
while(!glfwWindowShouldClose(window))
{
// 检测并调用事件
glfwPollEvents();
// 渲染
// 清空颜色缓冲
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 记得激活着色器
glUseProgram(shaderProgram);
// 更新uniform颜色
GLfloat timeValue = glfwGetTime();
GLfloat greenValue = (sin(timeValue) / 2) + 0.5; // 颜色在0.0到1.0之间改变
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//查询uniform ourColor的位置值
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);//设置uniform值
// 绘制三角形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
在顶点数据中加入颜色:
// 顶点着色器代码
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position; \n" // 位置变量的属性位置值为 0
"layout (location = 1) in vec3 color; \n" // 颜色变量的属性位置值为 1
"out vec3 ourColor; \n" // 向片段着色器输出一个颜色
"void main()\n"
"{\n"
"gl_Position = vec4(position, 1.0);\n" //vec3作为vec4的构造器的参数
"ourColor = color; \n" // 把输出变量设置为暗红色
"}\n\0";
//片段着色器代码
const char* fragmentShaderSource = "#version 330 core\n"
"in vec3 ourColor;\n"// 从顶点着色器传来的输入变量(名称相同、类型相同)
"out vec4 color;\n" // 输出颜色
"void main()\n"
"{\n"
"color = vec4(ourColor, 1.0f);\n"
"}\n\0";
// 位置属性
//第一个参数:顶点着色器上点定义的位置,第五个参数,步长,6个glfloat大小
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3* sizeof(GLfloat)));
glEnableVertexAttribArray(1);
写一个Shader类,从文件里读取着色器代码:
需要四个文件,分别是: 1. 渲染 main.cpp、 2. Shader类 shader.h、 3.顶点着色器代码。shader.vs、4.片段着色器代码。shader.frag
shader.h 代码:
#ifndef SHADER_H
#define SHADER_H
#include <string.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <GL/glew.h>
class Shader
{
public:
GLuint Program;
Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
//1. 得到顶点和片段着色器文件中的代码
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// 确保文件流可以捕捉异常
vShaderFile.exceptions(std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::badbit);
try
{
// 打开文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// 读取文件缓存内容到流
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理
vShaderFile.close();
fShaderFile.close();
// 转换流到文件
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure e)
{
std::cout << "Error::SHADER::FILE_NOT_SUCCES_FULLY_READ" << std::endl;
}
const GLchar* vShaderCode = vertexCode.c_str();
const GLchar* fShaderCode = fragmentCode.c_str();
//2. 编译shader
GLuint vertex, fragment;
GLint success;
GLchar 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);
//PRINT ERROR
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "error::shader::fragment::compilation_failed\n" << infoLog << std::endl;
}
//创建着色器程序
this->Program = glCreateProgram();
glAttachShader(this->Program, vertex);
glAttachShader(this->Program, fragment);
glLinkProgram(this->Program);
// 错误日志
glGetProgramiv(this->Program, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(this->Program, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << std::endl;
}
// dele
glDeleteShader(vertex);
glDeleteShader(fragment);
}
void Use()
{
glUseProgram(this->Program);
}
};
#endif
shader.vs 代码:
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
out vec3 ourColor;
void main()
{
gl_Position = vec4(position, 1.0f);
ourColor = color;
}
shader.frag 代码:
#version 330 core
in vec3 ourColor;
out vec4 color;
void main()
{
color = vec4(ourColor, 1.0f);
}
main.cpp 代码:
#include <iostream>
// GLEW 自动识别当前平台所支持的全部OpenGL高级扩展涵数的库
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW 一个跨平台的OpenGL应用框架,支持窗口创建,接受输入和事件等功能。
#include <GLFW/glfw3.h>
// Other includes
#include "Shader.h"
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
// The MAIN function, from here we start the application and run the game loop
int main()
{
std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;
// Init GLFW
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
//GLEW是用来管理OpenGL的函数指针的,在调用任何OpenGL的函数之前我们需要初始化GLEW
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
glewExperimental = GL_TRUE;
// Initialize GLEW to setup the OpenGL Function pointers
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
Shader ourShader("shader.vs", "shader.frag");
// Define the viewport dimensions
// 三角形
// 顶点输入,标准化设备坐标
GLfloat 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 // 顶部
};
GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//顶点缓冲对象, 管理顶点集合
glGenBuffers(1, &VBO);//create VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO); // bind vbo to GL_ARRAY_BUFFER, GL_ARRAY_BUFFER缓冲对象类型
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//将顶点数据vertices复制到缓冲的内存
// 第一个参数 顶点着色器上定义了position顶点属性的位置值layout(location = 0),我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
// 参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
// 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
// 第四个参数 是否希望数据被标准化(Normalize)
/* 第五个参数 步长 它告诉我们在连续的顶点属性组之间的间隔。
由于下个组位置数据在3个GLfloat之后,我们把步长设置为3 * sizeof(GLfloat)。
这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节
*/
// 第六个参数 表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
//glBindBuffer(GL_ARRAY_BUFFER, 0); // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the currently bound vertex buffer object so afterwards we can safely unbind
glBindVertexArray(0); // Unbind VAO (it's always a good thing to unbind any buffer/array to prevent strange bugs), remember: do NOT unbind the EBO, keep it bound to this VAO
//glDrawArrays(GL_TRIANGLES, 0, 3);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
glfwPollEvents();
// Render
// Clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ourShader.Use();
// 绘制三角形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
// Swap the screen buffers
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
// 监听鼠标、键盘事件
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
std::cout << key << std::endl;
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}