OpenGL Shaders

引言

上一节我们绘制了自己的三角形,今天来聊聊 OpenGL 的着色器。

具体来讲,在经典的渲染情景中通常以顶点和索引缓冲区的形式向 GPU 发送一堆数据,然后我们要做的实际上是在 GPU 上获取数据,然后对其进行处理(主要是通过不同类型的着色器)。

着色器的类型多种多样,但有两种类型的着色器使用得最多:

  • 顶点着色器
  • 片段着色器

在我们的渲染 API OpenGL 中也被称为像素着色器。

https://www.khronos.org/opengl/wiki/Shader_Compilation

着色器实现

Infinite/Render 目录下新建 Shader.h

#pragma once

#include <string>

namespace Infinite {

class Shader
{
public:
Shader(const std::string& vertexSrc, const std::string& fragmentSrc);
~Shader();

void Bind() const;
void Unbind() const;

private:
uint32_t m_RendererID;
};

}

构造函数目前使用字符串传递,在之后我们可能会改为文件流传输。

在相同的目录下新建 Shader.cpp,快速构造头文件中函数的定义:

#include "ifnpch.h"
#include "Shader.h"

#include <glad/glad.h>

namespace Infinite {

Shader::Shader(const std::string& vertexSrc, const std::string& fragmentSrc)
{

}

Shader::~Shader()
{

}

void Shader::Bind() const
{

}

void Shader::Unbind() const
{

}

}

拷贝示例函数,并且修改为 vertexSrcfragmentSrc

Shader::Shader(const std::string& vertexSrc, const std::string& fragmentSrc)
{
// Read our shaders into the appropriate buffers
// std::string vertexSource = // Get source code for vertex shader.
// std::string fragmentSource = // Get source code for fragment shader.

// Create an empty vertex shader handle
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);

// Send the vertex shader source code to GL
// Note that std::string's .c_str is NULL character terminated.
const GLchar* source = vertexSrc.c_str();
glShaderSource(vertexShader, 1, &source, 0);

// Compile the vertex shader
glCompileShader(vertexShader);

GLint isCompiled = 0;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &isCompiled);
if (isCompiled == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength);

// The maxLength includes the NULL character
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(vertexShader, maxLength, &maxLength, &infoLog[0]);

// We don't need the shader anymore.
glDeleteShader(vertexShader);

// Use the infoLog as you see fit.
IFN_CORE_ERROR("{0}", infoLog.data());
IFN_CORE_ASSERT(false, "Vertex Shader compilation failure!");

// In this simple program, we'll just leave
return;
}

// Create an empty fragment shader handle
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

// Send the fragment shader source code to GL
// Note that std::string's .c_str is NULL character terminated.
source = (const GLchar*)fragmentSrc.c_str();
glShaderSource(fragmentShader, 1, &source, 0);

// Compile the fragment shader
glCompileShader(fragmentShader);

glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &isCompiled);
if (isCompiled == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength);

// The maxLength includes the NULL character
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(fragmentShader, maxLength, &maxLength, &infoLog[0]);

// We don't need the shader anymore.
glDeleteShader(fragmentShader);
// Either of them. Don't leak shaders.
glDeleteShader(vertexShader);

// Use the infoLog as you see fit.
IFN_CORE_ERROR("{0}", infoLog.data());
IFN_CORE_ASSERT(false, "Fragment Shader compilation failure!");

// In this simple program, we'll just leave
return;
}

// Vertex and fragment shaders are successfully compiled.
// Now time to link them together into a program.
// Get a program object.
m_RendererID = glCreateProgram();
GLuint program = m_RendererID;

// Attach our shaders to our program
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);

// Link our program
glLinkProgram(program);

// Note the different functions here: glGetProgram* instead of glGetShader*.
GLint isLinked = 0;
glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
if (isLinked == GL_FALSE)
{
GLint maxLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);

// The maxLength includes the NULL character
std::vector<GLchar> infoLog(maxLength);
glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);

// We don't need the program anymore.
glDeleteProgram(program);
// Don't leak shaders either.
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

// Use the infoLog as you see fit.
IFN_CORE_ERROR("{0}", infoLog.data());
IFN_CORE_ASSERT(false, "Shader link failure!");

// In this simple program, we'll just leave
return;
}

// Always detach shaders after a successful link.
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
}

我们做的第一件事情是创建着色器:

// Create an empty vertex shader handle
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);

一个小插曲,之前我们使用过的 API 是以 glGen...() 开头的,而现在调用的是 glCreate...()

// Send the vertex shader source code to GL
// Note that std::string's .c_str is NULL character terminated.
const GLchar *source = vertexSrc.c_str();
glShaderSource(vertexShader, 1, &source, 0);

// Compile the vertex shader
glCompileShader(vertexShader);

这部分我们发送顶点着色器源码至 GL,并且正确编译它。

GLint isCompiled = 0;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &isCompiled);
if(isCompiled == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength);

// The maxLength includes the NULL character
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(vertexShader, maxLength, &maxLength, &infoLog[0]);

// We don't need the shader anymore.
glDeleteShader(vertexShader);

// Use the infoLog as you see fit.

// In this simple program, we'll just leave
return;
}

这段代码则是检测编译成功还是失败。倘若编译失败,我们获取日志的长度提取信息,输出日志为什么我们的着色器无法编译,最好我们删除着色器。

这里我们加入引擎的报错和断言:

...

// Use the infoLog as you see fit.
+ IFN_CORE_ERROR("{0}", infoLog.data());
+ IFN_CORE_ASSERT(false, "Vertex Shader compilation failure!");

// In this simple program, we'll just leave
return;
}

后面的片段着色器其实在做相同的事情,加入引擎自己的调试报错和断言:

// Create an empty fragment shader handle
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

// Send the fragment shader source code to GL
// Note that std::string's .c_str is NULL character terminated.
source = (const GLchar *)fragmentSrc.c_str();
glShaderSource(fragmentShader, 1, &source, 0);

// Compile the fragment shader
glCompileShader(fragmentShader);

glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &isCompiled);
if (isCompiled == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength);

// The maxLength includes the NULL character
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(fragmentShader, maxLength, &maxLength, &infoLog[0]);

// We don't need the shader anymore.
glDeleteShader(fragmentShader);
// Either of them. Don't leak shaders.
glDeleteShader(vertexShader);

// Use the infoLog as you see fit.
IFN_CORE_ERROR("{0}", infoLog.data());
IFN_CORE_ASSERT(false, "Fragment Shader compilation failure!");

// In this simple program, we'll just leave
return;
}

然后我们创建程序,它将是我们在 GPU 上运行的两个着色器的组合总线,然后编译并链接程序:

// Vertex and fragment shaders are successfully compiled.
// Now time to link them together into a program.
// Get a program object.
GLuint program = glCreateProgram();

// Attach our shaders to our program
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);

// Link our program
glLinkProgram(program);

最后与之前类似,就是检测编译成功与否:

// Note the different functions here: glGetProgram* instead of glGetShader*.
GLint isLinked = 0;
glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked);
if (isLinked == GL_FALSE)
{
GLint maxLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);

// The maxLength includes the NULL character
std::vector<GLchar> infoLog(maxLength);
glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);

// We don't need the program anymore.
glDeleteProgram(program);
// Don't leak shaders either.
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

// Use the infoLog as you see fit.
IFN_CORE_ERROR("{0}", infoLog.data());
IFN_CORE_ASSERT(false, "Shader link failure!");

// In this simple program, we'll just leave
return;
}

// Always detach shaders after a successful link.
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);

最后删除着色器文件即可。

返回 Application.h,加入着色器头文件和着色器:

#pragma once

#include "Core.h"
#include "../Window.h"
#include "LayerStack.h"
#include "Events/Event.h"
#include "Events/ApplicationEvent.h"

#include "./ImGui/ImGuiLayer.h"

+#include "./Renderer/Shader.h"

namespace Infinite {
class INFINITE_API Application
{
public:
Application();
virtual ~Application();

void Run();
void OnEvent(Event& e);

void PushLayer(Layer* layer);
void PushOverlay(Layer* layer);

inline Window& GetWindow() { return *m_Window; }

inline static Application& Get() { return *s_Instance; }
private:
bool OnWindowClose(WindowCloseEvent& e);

std::unique_ptr<Window> m_Window;
ImGuiLayer* m_ImGuiLayer;
bool m_Running = true;
LayerStack m_LayerStack;

unsigned int m_VertexArray, m_VertexBuffer, m_IndexBuffer;
+ std::unique_ptr<Shader> m_Shader;
private:
static Application* s_Instance;
};

//To be define in CLIENT
Application* CreateApplication();
}

最后写入着色器即可:

Application::Application() 
{
IFN_CORE_ASSERT(!s_Instance, "Application already exists!");
s_Instance = this;

m_Window = std::unique_ptr<Window>(Window::Create());
m_Window->SetEventCallback(BIND_EVENT_FN(OnEvent));

m_ImGuiLayer = new ImGuiLayer();
PushOverlay(m_ImGuiLayer);

// Vertex Array
glGenVertexArrays(1, &m_VertexArray);
glBindVertexArray(m_VertexArray);

// Vertex Buffer
glGenBuffers(1, &m_VertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_VertexBuffer);


float vertices[3 * 3] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);

glGenBuffers(1, &m_IndexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer);

unsigned int indices[3] = { 0, 1, 2 };
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);


std::string vertexSrc = R"(

layout(location = 0) in vec3 a_Position;

void main()
{
gl_Position = vec4(a_Position, 1.0);

}
)";


std::string fragmentSrc = R"(

layout(location = 0) out vec4 color;

void main()
{
color = vec4(0.8, 0.2, 0.3, 1.0);

}
)";



m_Shader.reset(new Shader(vertexSrc, fragmentSrc));

}

预生成文件

-include "Infinite/vendor/GLFW"
-include "Infinite/vendor/Glad"
-include "Infinite/vendor/imgui"

+group "Dependencies"
+ include "Infinite/vendor/GLFW"
+ include "Infinite/vendor/Glad"
+ include "Infinite/vendor/imgui"

+group ""

调试