05_LearnOpenGL 我们自己的着色器类
我们自己的着色器类
编写、编译、管理着色器是件麻烦事。在着色器主题的最后,我们会写一个类来让我们的生活轻松一点,它可以从硬盘读取着色器,然后编译并链接它们,并对它们进行错误检测,这就变得很好用了。这也会让你了解该如何封装目前所学的知识到一个抽象对象中。
我们会把着色器类全部放在在头文件里,主要是为了学习用途,当然也方便移植。我们先来添加必要的include,并定义类结构:
#ifndef SHADER_H
#define SHADER_H
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <GL/glew.h>
class Shader
{
public:
//指定顶点作色器源码路径、颜色片段源码路径,构建着色器对象
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
// 启动当前做色器程序
void Use();
private:
GLuint Program; //作色器程序
};
#endif
在上面,我们在头文件顶部使用了几个预处理指令(Preprocessor Directives)。这些预处理指令会告知你的编译器只在它没被包含过的情况下才包含和编译这个头文件,即使多个文件都包含了这个着色器头文件。它是用来防止链接冲突的。
着色器类储存了着色器程序的ID。它的构造器需要顶点和片段着色器源代码的文件路径,这样我们就可以把源码的文本文件储存在硬盘上了。我们还添加了一个Use函数,它其实不那么重要,但是能够显示这个自造类如何让我们的生活变得轻松(虽然只有一点)。
从文件读取
我们使用C++文件流读取着色器内容,储存到几个string
对象里:
Shader::Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
std::ifstream vShaderFileInputStream;
std::ifstream fShaderFileInputStream; //源码文件输入流
std::string vertexCode;
std::string fragmentCode; //源码文件写入的缓存区
const GLchar* vShaderCode = nullptr;
const GLchar* fShaderCode = nullptr;
GLuint vertexShader;
GLuint fragmentShader; //顶点作色器,片段作色器
GLchar szInfoLog[512];
GLint bSuc;
vShaderFileInputStream.exceptions(std::ifstream::badbit);
fShaderFileInputStream.exceptions(std::ifstream::badbit); // 确保ifStream对象可以引发异常
try
{
std::stringstream vShaderStream;
std::stringstream fShaderStream;
// 判断流
if(!vShaderFileInputStream.is_open())
{
vShaderFileInputStream.open(vertexPath);
}
if (!fShaderFileInputStream.is_open())
{
fShaderFileInputStream.open(fragmentPath);
}
// 读取文件输入流内容,并转到内存中
vShaderStream << vShaderFileInputStream.rdbuf();
fShaderStream << fShaderFileInputStream.rdbuf();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
//关闭文件输入流
vShaderFileInputStream.close();
fShaderFileInputStream.close();
}
catch (std::ifstream::failure e)
{
std::cout << "错误!Share源码文件不能有效被读取!" << std::endl;
}
vShaderCode = vertexCode.c_str();
fShaderCode = fragmentCode.c_str();
[...]
下一步,我们需要编译和链接着色器。注意,我们也将检查编译/链接是否失败,如果失败则打印编译时错误,调试的时候这些错误输出会及其重要(你总会需要这些错误日志的):
// 2. 编译着色器
// 顶点着色器
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vShaderCode, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &bSuc);
if (!bSuc)
{
glGetShaderInfoLog(vertexShader, 512, NULL, szInfoLog);
std::cout << "错误!无法编译顶点着色器,请核查顶点着色器源码!\n" << szInfoLog << std::endl;
}
// 片段着色器也类似
[...]
// 着色器程序
// 打印连接错误(如果有的话)
glAttachShader(this->Program, vertexShader);
glAttachShader(this->Program, fragmentShader);
glLinkProgram(this->Program);
glGetProgramiv(this->Program, GL_LINK_STATUS, &bSuc);
if (!bSuc)
{
glGetProgramInfoLog(this->Program, 512, NULL, szInfoLog);
std::cout << "错误! 无法完成链接任务,请核查着色器程序链接着色器情况!\n" << szInfoLog << std::endl;
}
// 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
最后我们也会实现Use函数:
void Use()
{
glUseProgram(this->Program);
}
在我们就写完了一个完整的着色器类。使用这个着色器类很简单;只要创建一个着色器对象,从那一点开始我们就可以开始使用了:
Shader ourShader("default.vs", "default.frag");
...
while(...)
{
ourShader.Use();
glUniform1f(glGetUniformLocation(ourShader.Program, "someUniform"), 1.0f);
DrawStuff();
}
我们把顶点和片段着色器储存为两个叫做default.vs
和default.frag
的文件。你可以使用自己喜欢的名字命名着色器文件;我自己觉得用.vs
和.frag
作为扩展名很直观。
实现源码
- default.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;
}
- default.frag
#version 330 core
in vec3 ourColor;
out vec4 color;
void main()
{
color = vec4(ourColor, 1.0f);
}
- Shader.h
#ifndef SHADER_H
#define SHADER_H
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <GL/glew.h>
class Shader
{
public:
//指定顶点作色器源码路径、颜色片段源码路径,构建着色器对象
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
// 启动当前做色器程序
void Use();
private:
GLuint Program; //作色器程序
};
#endif
- Shader.cpp
#include "Shader.h"
Shader::Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
std::ifstream vShaderFileInputStream;
std::ifstream fShaderFileInputStream; //源码文件输入流
std::string vertexCode;
std::string fragmentCode; //源码文件写入的缓存区
const GLchar* vShaderCode = nullptr;
const GLchar* fShaderCode = nullptr;
GLuint vertexShader;
GLuint fragmentShader; //顶点作色器,片段作色器
GLchar szInfoLog[512];
GLint bSuc;
vShaderFileInputStream.exceptions(std::ifstream::badbit);
fShaderFileInputStream.exceptions(std::ifstream::badbit); // 确保ifStream对象可以引发异常
try
{
std::stringstream vShaderStream;
std::stringstream fShaderStream;
// 判断流
if(!vShaderFileInputStream.is_open())
{
vShaderFileInputStream.open(vertexPath);
}
if (!fShaderFileInputStream.is_open())
{
fShaderFileInputStream.open(fragmentPath);
}
// 读取文件输入流内容,并转到内存中
vShaderStream << vShaderFileInputStream.rdbuf();
fShaderStream << fShaderFileInputStream.rdbuf();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
//关闭文件输入流
vShaderFileInputStream.close();
fShaderFileInputStream.close();
}
catch (std::ifstream::failure e)
{
std::cout << "错误!Share源码文件不能有效被读取!" << std::endl;
}
vShaderCode = vertexCode.c_str();
fShaderCode = fragmentCode.c_str();
//依次编译顶点作色器源码、颜色片段作色器源码。
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vShaderCode, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &bSuc);
if (!bSuc)
{
glGetShaderInfoLog(vertexShader, 512, NULL, szInfoLog);
std::cout << "错误!无法编译顶点着色器,请核查顶点着色器源码!\n" << szInfoLog << std::endl;
}
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fShaderCode, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &bSuc);
if (!bSuc)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, szInfoLog);
std::cout << "错误!无法编译片段着色器,请核查片段着色器源码!\n" << szInfoLog << std::endl;
}
//创建作色器程序
this->Program = glCreateProgram();
//作色器程序和作色器组链接整合
glAttachShader(this->Program, vertexShader);
glAttachShader(this->Program, fragmentShader);
glLinkProgram(this->Program);
glGetProgramiv(this->Program, GL_LINK_STATUS, &bSuc);
if (!bSuc)
{
glGetProgramInfoLog(this->Program, 512, NULL, szInfoLog);
std::cout << "错误! 无法完成链接任务,请核查着色器程序链接着色器情况!\n" << szInfoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
void Shader::Use()
{
glUseProgram(this->Program);
}
- Application.cpp
#include <iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include "Shader.h"
#include <GLFW/glfw3.h>
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
// 窗口维度
const GLuint WIDTH = 800;
const GLuint HEIGHT = 600;
int main()
{
GLFWwindow* window = nullptr;
int viewportWidth;
int viewportHeight; //视图维度
GLint bSuc;
GLuint VBO; //顶点缓冲对象
GLuint VAO; //顶点数组对象
// 建立顶点数据集
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 // 顶部
};
//OPENGL 初始化工作
std::cout << "启动 GLFW 上下文,版本 OpenGL 3.3" << std::endl;
glfwInit();
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);
window = glfwCreateWindow(WIDTH, HEIGHT, "JacksonWang", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glewExperimental = GL_TRUE;
glewInit();
glfwGetFramebufferSize(window, &viewportWidth, &viewportHeight);
glViewport(0, 0, viewportWidth, viewportHeight);
Shader ourShader("default.vs", "default.frag");
//VAO=>管理=>VBO=>缓冲并管理=>顶点数据集
glGenVertexArrays(1,&VAO);
glGenBuffers(1, &VBO);
//首先棒i的那个顶点数组对象,然后绑定顶点缓冲对象 和属性指针
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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//顶点颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
//解绑VAO
glBindVertexArray(0);
while (!glfwWindowShouldClose(window))
{
//渲染初始化工作
glfwPollEvents();
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);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
}
再来一些练习下吧
需求:修改顶点着色器让三角形上下颠倒
实现:
- 修改
default.vs
文件,在Y位置加上负号即可,其余代码不做调整。
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 ourColor;
void main()
{
gl_Position = vec4(position.x, -position.y, position.z,1.0f); //只需要在Y位置加个负号即可
ourColor = color;
}
需求:使用uniform定义一个水平偏移量,在顶点着色器中使用这个偏移量把三角形移动到屏幕右侧
实现:
-
改写
default.vs
文件,X位置增加xOffset偏置。 -
Shader
类继续抽象SetFloat()
,以便实现 在程序层面更新着色器程序的全局变量。 -
并在游戏循环while中,更新uniform全局变量
xOffset
default.vs
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 ourColor;
uniform float xOffset; //定义全局变量 xOffset
void main()
{
gl_Position = vec4(position.x + xOffset, position.y, position.z,1.0f); //X位置增加xOffset偏置
ourColor = color;
}
Share.hpp
#ifndef SHADER_H
#define SHADER_H
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <GL/glew.h>
class Shader
{
public:
//指定顶点作色器源码路径、颜色片段源码路径,构建着色器对象
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
public:
//根据全局变量名,更新值
void SetFloat(const GLchar* uniformName,GLfloat value);
public:
// 启动当前做色器程序
void Use();
private:
GLuint m_program; //作色器程序
};
#endif
Share.cpp
#include "Shader.h"
Shader::Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
std::ifstream vShaderFileInputStream;
std::ifstream fShaderFileInputStream; //源码文件输入流
std::string vertexCode;
std::string fragmentCode; //源码文件写入的缓存区
const GLchar* vShaderCode = nullptr;
const GLchar* fShaderCode = nullptr;
GLuint vertexShader;
GLuint fragmentShader; //顶点作色器,片段作色器
GLchar szInfoLog[512];
GLint bSuc;
vShaderFileInputStream.exceptions(std::ifstream::badbit);
fShaderFileInputStream.exceptions(std::ifstream::badbit); // 确保ifStream对象可以引发异常
try
{
std::stringstream vShaderStream;
std::stringstream fShaderStream;
// 判断流
if(!vShaderFileInputStream.is_open())
{
vShaderFileInputStream.open(vertexPath);
}
if (!fShaderFileInputStream.is_open())
{
fShaderFileInputStream.open(fragmentPath);
}
// 读取文件输入流内容,并转到内存中
vShaderStream << vShaderFileInputStream.rdbuf();
fShaderStream << fShaderFileInputStream.rdbuf();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
//关闭文件输入流
vShaderFileInputStream.close();
fShaderFileInputStream.close();
}
catch (std::ifstream::failure e)
{
std::cout << "错误!Share源码文件不能有效被读取!" << std::endl;
}
vShaderCode = vertexCode.c_str();
fShaderCode = fragmentCode.c_str();
//依次编译顶点作色器源码、颜色片段作色器源码。
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vShaderCode, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &bSuc);
if (!bSuc)
{
glGetShaderInfoLog(vertexShader, 512, NULL, szInfoLog);
std::cout << "错误!无法编译顶点着色器,请核查顶点着色器源码!\n" << szInfoLog << std::endl;
}
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fShaderCode, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &bSuc);
if (!bSuc)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, szInfoLog);
std::cout << "错误!无法编译片段着色器,请核查片段着色器源码!\n" << szInfoLog << std::endl;
}
//创建作色器程序
this->m_program = glCreateProgram();
//作色器程序和作色器组链接整合
glAttachShader(this->m_program, vertexShader);
glAttachShader(this->m_program, fragmentShader);
glLinkProgram(this->m_program);
glGetProgramiv(this->m_program, GL_LINK_STATUS, &bSuc);
if (!bSuc)
{
glGetProgramInfoLog(this->m_program, 512, NULL, szInfoLog);
std::cout << "错误! 无法完成链接任务,请核查着色器程序链接着色器情况!\n" << szInfoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
void Shader::SetFloat(const GLchar* uniformName, GLfloat value)
{
GLint location = glGetUniformLocation(m_program, uniformName);
glUniform1f(location,value);
}
void Shader::Use()
{
glUseProgram(this->m_program);
}
Application.cpp
#include <iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include "Shader.h"
#include <GLFW/glfw3.h>
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
// 窗口维度
const GLuint WIDTH = 800;
const GLuint HEIGHT = 600;
int main()
{
GLFWwindow* window = nullptr;
int viewportWidth;
int viewportHeight; //视图维度
GLint bSuc;
GLuint VBO; //顶点缓冲对象
GLuint VAO; //顶点数组对象
// 建立顶点数据集
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 // 顶部
};
//OPENGL 初始化工作
std::cout << "启动 GLFW 上下文,版本 OpenGL 3.3" << std::endl;
glfwInit();
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);
window = glfwCreateWindow(WIDTH, HEIGHT, "JacksonWang", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glewExperimental = GL_TRUE;
glewInit();
glfwGetFramebufferSize(window, &viewportWidth, &viewportHeight);
glViewport(0, 0, viewportWidth, viewportHeight);
Shader ourShader("default.vs", "default.frag");
//VAO=>管理=>VBO=>缓冲并管理=>顶点数据集
glGenVertexArrays(1,&VAO);
glGenBuffers(1, &VBO);
//首先棒i的那个顶点数组对象,然后绑定顶点缓冲对象 和属性指针
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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//顶点颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
//解绑VAO
glBindVertexArray(0);
while (!glfwWindowShouldClose(window))
{
//渲染初始化工作
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ourShader.Use();
//=======更新X偏置
float offset = 0.5f;
ourShader.SetFloat("xOffset", offset);
//======更新X偏置
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
}
需求:用out
关键字把顶点位置输出到片段着色器,并将片段的颜色设置为与顶点位置相等(来看看连顶点位置值都在三角形中被插值的结果)。做完这些后,尝试回答下面的问题:为什么在三角形的左下角是黑的?
- 修改
default.vs
输出点位置。
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourPosition;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourPosition = aPos;
}
- 修改
default.frag
接收点位置并作为,RGB颜色值输入。
#version 330 core
out vec4 FragColor;
in vec3 ourPosition;
void main()
{
FragColor = vec4(ourPosition, 1.0); //点坐标X,Y,Z 对应赋值给了R,G,B
}
解释疑问,为啥左下角是黑色的?
想一想:我们片段的颜色的输出等于(内插的)坐标。
那个三角形。我们三角形的左下角的坐标是多少?这是(-0.5F,-0.5F,0.0F)。自.以来。
XY值为负值,它们被钳制为0.0f的值。这种情况会一直发生到。
三角形,因为从这一点开始,这些值将再次被正插补。0.0f的值当然是黑色。
ndow, GL_TRUE);
}
}
需求:用`out`关键字把顶点位置输出到片段着色器,并将片段的颜色设置为与顶点位置相等(来看看连顶点位置值都在三角形中被插值的结果)。做完这些后,尝试回答下面的问题:为什么在三角形的左下角是黑的?
- 修改 `default.vs` 输出点位置。
```c
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourPosition;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourPosition = aPos;
}
- 修改
default.frag
接收点位置并作为,RGB颜色值输入。
#version 330 core
out vec4 FragColor;
in vec3 ourPosition;
void main()
{
FragColor = vec4(ourPosition, 1.0); //点坐标X,Y,Z 对应赋值给了R,G,B
}
[外链图片转存中…(img-zh13gGGS-1703688138179)]
解释疑问,为啥左下角是黑色的?
想一想:我们片段的颜色的输出等于(内插的)坐标。
那个三角形。我们三角形的左下角的坐标是多少?这是(-0.5F,-0.5F,0.0F)。自.以来。
XY值为负值,它们被钳制为0.0f的值。这种情况会一直发生到。
三角形,因为从这一点开始,这些值将再次被正插补。0.0f的值当然是黑色。
这就解释了三角形的黑色一面。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!