参考资料:
https://learnopengl.com/
https://learnopengl-cn.github.io/
这次实现一个Shader着色器类,让之前以字符串形式写的顶点、片元着色器编码能够通过.txt文件读入,并把编译、链接着色器等过程移动到着色器类中来,使主程序中的代码变得简洁一些。
首先,glShaderSource函数接收的是一个const char* 类型,这不变,为了得到这样一种类型,要读取.txt文件的缓冲内容到数据流中,再将数据流转换到String,最后通过.c_str()方法转换为const char* 。
之前在主函数中的一系列定义、创建、附加、编译顶点/片元着色器的过程,以及创建程序对象,将着色器附加到这个对象上并链接的过程都可以拿到Shader类中来实现,以优化main函数中的代码量。
除此之外,还实现了一个打印错误信息的方法,思路是使用glGetShaderiv和glGetProgramiv两个方法检验着色器是否编译、链接成功,以引用的形式传入一个int类型success,返回结果将传递给success,根据success的值判断是否出现错误,若出现错误,则分别调用glGetShaderInfoLog和glGetProgramInfoLog,传入一个字符数组接收信息,使用cout将信息打印出来即可。
建立好着色器类后,原本的用const char* 表示的着色器编码就可以移动到各自的.txt文件中了,修改也更加方便。在main函数中使用这个类时,只需new一个Shader类对象,传入顶点和片元着色器的文件名称即可,在循环渲染阶段,直接使用着色器对象的use()方法就可以了。
.h:
#pragma once
#include <string>
class Shader
{
public:
Shader(const char* vertexPath, const char* fragmentPath);
//读取文件的缓冲内容到数据流中->数据流转换到String->String转换到const char*
//String用作中转
std::string vertexString;
std::string fragmentString;
//着色器最终需要的是const char*
const char* vertexSource;
const char* fragmentSource;
unsigned int ID; //程序ID
void use(); //glUseProgram激活程序
private:
void checkCompileErrors(unsigned int ID, std::string type);
};
.cpp:
#include "Shader.h"
#include <fstream>
#include <sstream>
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
//从文件路径中获取顶点/片段着色器
std::ifstream vertexFile;
std::ifstream fragmentFile;
std::stringstream vertexSStream;
std::stringstream fragmentSStream;
vertexFile.open(vertexPath); //打开文件
fragmentFile.open(fragmentPath);
vertexFile.exceptions(std::ifstream::failbit || std::ifstream::badbit); //保证ifstream对象可以抛出异常
fragmentFile.exceptions(std::ifstream::failbit || std::ifstream::badbit);
try
{
if (!vertexFile.is_open() || !fragmentFile.is_open())
{
throw std::exception("open file error."); //打开失败抛出异常
}
vertexSStream << vertexFile.rdbuf(); //读取文件的缓冲内容到数据流中
fragmentSStream << fragmentFile.rdbuf();
vertexString = vertexSStream.str(); //数据流转换到String
fragmentString = fragmentSStream.str();
vertexSource = vertexString.c_str(); //String转换到const char*
fragmentSource = fragmentString.c_str();
unsigned int vertex, fragment; //声明顶点/片元着色器
//编译顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vertexSource, nullptr);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX"); //检查错误
//编译片元着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fragmentSource, nullptr);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT"); //检查错误
//创建、链接着色器程序对象
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM"); //检查错误
glDeleteShader(vertex); //着色器已经链接到程序中了,不再需要所以删除
glDeleteShader(fragment);
}
catch (const std::exception& ex)
{
printf(ex.what());
}
}
void Shader::use()
{
glUseProgram(ID);
}
//打印错误
void Shader::checkCompileErrors(unsigned int ID, std::string type)
{
int success;
char infoLog[512];
if (type != "PROGRAM")
{
glGetShaderiv(ID, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(ID, 512, nullptr, infoLog);
std::cout << "Shader compile error: " << infoLog << std::endl; //打印编译错误
}
}
else
{
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(ID, 512, nullptr, infoLog);
std::cout << "Program link error: " << infoLog << std::endl; //打印链接错误
}
}
}