GLSL运行在GPU上,想要调试需要一个用于捕获和显示GLSL错误的模块。
Utils.h
#pragma once
#include "GL\glew.h"
#include <string>
class Utils
{
public:
Utils();
~Utils();
static void printShaderLog(GLuint shader);
static void printProgramLog(int prog);
static bool checkOpenGLError();
static GLuint createShaderProgram();
};
Utils.cpp
#include "Utils.h"
#include <iostream>
#include <fstream>
#include <SOIL2/SOIL2.h>
Utils::Utils()
{}
Utils::~Utils()
{}
// 当GLSL编译失败时,显示OpenGL日志内容
void Utils::printShaderLog(GLuint shader)
{
int len = 0;
int chWritten = 0;
char *log;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); // 提供有关编译过的GLSL着色器的信息
if (len > 0)
{
log = (char *)malloc(len);
glGetShaderInfoLog(shader, len, &chWritten, log);
std::cout << "Shader Info Log: " << log << std::endl;
free(log);
}
}
// 当GLSL链接失败时,显示OpenGL日志内容
void Utils::printProgramLog(int prog)
{
int len = 0;
int chWritten = 0;
char *log;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len); // 提供有关编译过的程序的信息
if (len > 0)
{
log = (char *)malloc(len);
glGetProgramInfoLog(prog, len, &chWritten, log);
std::cout << "Program Info Log: " << log << std::endl;
free(log);
}
}
// 检查OpenGL错误标志,即是否发生OpenGL错误,既用于检测GLSL编译错误,又用于检测OpenGL运行时的错误
bool Utils::checkOpenGLError()
{
bool foundError = false;
int glErr = glGetError();
while (glErr != GL_NO_ERROR)
{
std::cout << "glError: " << glErr << std::endl;
foundError = true;
glErr = glGetError();
}
return foundError;
}
GLuint Utils::createShaderProgram()
{
GLint vertCompiled;
GLint fragCompiled;
GLint linked;
const char* vshaderSource =
"#version 460 \n"
"void main(void) \n"
"{ gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }";
const char* fshaderSource =
"#version 460 \n"
"out vec4 color; \n"
"void main(void) \n"
"{ color = vec4(0.0, 0.0, 1.0, 1.0); }";
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vShader, 1, &vshaderSource, NULL);
glShaderSource(fShader, 1, &fshaderSource, NULL);
// 捕获编译着色器时的错误
glCompileShader(vShader);
checkOpenGLError();
glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
if (vertCompiled != 1)
{
std::cout << "vertex compilation failed" << std::endl;
printShaderLog(vShader);
}
glCompileShader(fShader);
checkOpenGLError();
glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
if (fragCompiled != 1)
{
std::cout << "fragment compilation failed" << std::endl;
printShaderLog(fShader);
}
GLuint vfProgram = glCreateProgram();
// 捕获链接着色器时的错误
glAttachShader(vfProgram, vShader);
glAttachShader(vfProgram, fShader);
glLinkProgram(vfProgram);
checkOpenGLError();
glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
if (linked != 1)
{
std::cout << "linking failed" << std::endl;
printProgramLog(vfProgram);
}
return vfProgram;
}
当程序更复杂时,GLSL着色器代码存储在字符串中就不可取了,我们应当将着色器代码存在文件中并读入它们。
顶点着色器和片段着色器代码分别放在 "vertShader.glsl" 和 "fragShader.glsl" 中。
Utils.h 增加如下代码:
static std::string readShaderSource(const char *filePath); static GLuint createShaderProgram(const char *vp, const char *fp);
Utils.cpp
// 用于读取着色器的代码
std::string Utils::readShaderSource(const char * filePath)
{
std::string content;
std::ifstream fileStream(filePath, std::ios::in);
std::string line = "";
while (!fileStream.eof())
{
getline(fileStream, line);
content.append(line + "\n");
}
fileStream.close();
return content;
}
// 读取着色器代码并构建渲染程序,该类很适合重载
GLuint Utils::createShaderProgram(const char * vp, const char * fp)
{
GLint vertCompiled;
GLint fragCompiled;
GLint linked;
std::string vertShaderStr = readShaderSource(vp);
std::string fragShaderStr = readShaderSource(fp);
const char * vertShaderSrc = vertShaderStr.c_str();
const char * fragShaderSrc = fragShaderStr.c_str();
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vShader, 1, &vertShaderSrc, NULL);
glShaderSource(fShader, 1, &fragShaderSrc, NULL);
glCompileShader(vShader);
checkOpenGLError();
glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
if (vertCompiled != 1)
{
std::cout << "vertex compilation failed" << std::endl;
printShaderLog(vShader);
}
glCompileShader(fShader);
checkOpenGLError();
glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
if (fragCompiled != 1)
{
std::cout << "fragment compilation failed" << std::endl;
printShaderLog(fShader);
}
GLuint vfProgram = glCreateProgram();
glAttachShader(vfProgram, vShader);
glAttachShader(vfProgram, fShader);
glLinkProgram(vfProgram);
checkOpenGLError();
glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
if (linked != 1)
{
std::cout << "linking failed" << std::endl;
printProgramLog(vfProgram);
}
return vfProgram;
}
从文件着色器读取并生成一个三角形的程序,如下:
顶点着色器,vertShader.glsl
#version 460
void main(void)
{ if (gl_VertexID == 0) gl_Position = vec4(0.25, -0.25, 0.0, 1.0);
else if (gl_VertexID == 1) gl_Position = vec4(-0.25, -0.25, 0.0, 1.0);
else gl_Position = vec4(0.25, 0.25, 0.0, 1.0);}
display() 函数中加入代码
... glDrawArrays(GL_TRIANGLES, 0, 3);
结果如下:
可以使三角形动起来实现动画效果,我们在 main() 函数中只调用了 init() 一次,之后重复调用 display(),在循环中可以一次又一次绘制,只要设计 display() 函数随时间改变绘制的东西。
场景的每一次绘制都叫作一帧,调用 display() 的频率叫作帧率。在程序逻辑中移动的速率可以通过自前一帧到目前经过的时间来控制。
display()方法中的变量 "x" 用于偏移三角形的 X 轴位置。每帧的值都不同,当它到达 1.0 或 -1.0 时,会改变方向。在 x 中的值会被复制到顶点着色器的 "offset" 变量中。执行这个复制的机制叫作 Uniform 变量(统一变量)。顶点着色器会将 offset 加给所绘制三角形的 X 坐标。每次调用 display() 时背景都会被清除,以避免三角形移动时留下一串轨迹。
main.cpp
// #include和定义与之前相同
float x = 0.0f; // 三角形在x轴的位置
float inc = 1.0f; // 移动三角形的偏移量
void display(GLFWwindow* window, double currentTime)
{
glClear(GL_DEPTH_BUFFER_BIT);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT); // 每次将背景清除为黑色
glUseProgram(renderingProgram);
x += inc; // 切换至让三角形向右移动
if (x > 1.0f) // 沿x轴移动三角形
inc = -0.01f;
if (x < -1.0f) // 切换至让三角形向右移动
inc = 0.01f;
GLuint offsetLoc = glGetUniformLocation(renderingProgram, "offset"); // 获取"offset"指针
glProgramUniform1f(renderingProgram, offsetLoc, x); // 将"x"中的值传给"offset"
glDrawArrays(GL_TRIANGLES, 0, 3);
}
// 其余函数同前
顶点着色器
#version 460
uniform float offset;
void main(void)
{ if (gl_VertexID == 0) gl_Position = vec4(0.25 + offset, -0.25, 0.0, 1.0);
else if (gl_VertexID == 1) gl_Position = vec4(-0.25 + offset, -0.25, 0.0, 1.0);
else gl_Position = vec4(0.25 + offset, 0.25, 0.0, 1.0);}
结果如下: