前言
这章做一点小小的代码改进,将Shader类抽象成接口,实现类OpenGLShader放到Platform/OpenGL下,以保证Renderer层是跨平台的。
Shader抽象与实现
Shader抽象
Shader中的函数设计成纯虚函数,变成一个纯接口的抽象类。
Sandbox/Hazel/src/Hazel/Renderer/Shader.h
c++
class Shader {
public:
~Shader() = default;
virtual void Bind() const = 0;
virtual void Unbind() const = 0;
static Shader* Create(const std::string& vertexSrc, const std::string& fragmentSrc);
};
Sandbox/Hazel/src/Hazel/Renderer/Shader.cpp
c++
Shader *Shader::Create(const std::string &vertexSrc, const std::string &fragmentSrc) {
switch (Renderer::GetAPI()) {
case RendererAPI::API::None:
HZ_CORE_ASSERT(false, "RendererAPI::None is currently not supported!");
return nullptr;
case RendererAPI::API::OpenGL:
return new OpenGLShader(vertexSrc, fragmentSrc);
Shader实现-OpenGLShader
之前Shader中的逻辑挪到OpenGLShader中,除了Bind()、Unbind()方法,再额外补充了一组更新uniform变量的接口:
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.h
c++
#pragma once
#include "Renderer/Shader.h"
namespace Hazel {
class OpenGLShader : public Shader {
public:
OpenGLShader(const std::string& vertexSrc, const std::string& fragmentSrc);
~OpenGLShader();
void Bind() const override;
void Unbind() const override;
void UploadUniformInt(const std::string& name, int value);
void UploadUniformFloat(const std::string& name, float value);
void UploadUniformFloat2(const std::string& name, const glm::vec2& value);
void UploadUniformFloat3(const std::string& name, const glm::vec3& value);
void UploadUniformFloat4(const std::string& name, const glm::vec4& value);
void UploadUniformMat3(const std::string& name, const glm::mat3 value);
void UploadUniformMat4(const std::string& name, const glm::mat4 value);
private:
uint32_t m_RendererID;
};
}
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.cpp
c++
//
// Created by xiatian05 on 2023/10/10.
//
#include "OpenGLShader.h"
#include "glad/glad.h"
#include "Log.h"
#include "Base.h"
#include "glm/gtc/type_ptr.hpp"
namespace Hazel{
OpenGLShader::OpenGLShader(const std::string &vertexSrc, const std::string &fragmentSrc) {
// 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.
//reference to https://www.khronos.org/opengl/wiki/Shader_Compilation#Example
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);
HZ_CORE_ERROR("{0}", infoLog.data());
HZ_CORE_ASSERT(false, std::string("Vertex shader compilation failure! ") + "\n" + "vertex :\n" + vertexSrc)
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 = 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);
HZ_CORE_ERROR("{0}", infoLog.data());
HZ_CORE_ASSERT(false, std::string("Fragment shader compilation failure!") + "\n" + "fragment :\n" + fragmentSrc);
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);
HZ_CORE_ERROR("{0}", infoLog.data());
HZ_CORE_ASSERT(false, "Shader link failure!");
return;
}
// Always detach shaders after a successful link.
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
}
OpenGLShader::~OpenGLShader() {
glDeleteProgram(m_RendererID);
}
void OpenGLShader::Bind() const {
glUseProgram(m_RendererID);
}
void OpenGLShader::Unbind() const {
glUseProgram(0);
}
void OpenGLShader::UploadUniformInt(const std::string &name, int value) {
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniform1i(location, value);
}
void OpenGLShader::UploadUniformFloat(const std::string &name, float value) {
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniform1f(location, value);
}
void OpenGLShader::UploadUniformFloat2(const std::string &name, const glm::vec2 &value) {
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniform2f(location, value.x, value.y);
}
void OpenGLShader::UploadUniformFloat3(const std::string &name, const glm::vec3 &value) {
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniform3f(location, value.x, value.y, value.z);
}
void OpenGLShader::UploadUniformFloat4(const std::string &name, const glm::vec4 &value) {
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniform4f(location, value.x, value.y, value.z, value.w);
}
void OpenGLShader::UploadUniformMat3(const std::string &name, const glm::mat3 value) {
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniformMatrix3fv(location, 1, GL_FALSE, glm::value_ptr(value));
}
void OpenGLShader::UploadUniformMat4(const std::string &name, const glm::mat4 value) {
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
}
}
更新SandBoxApp中调用Shader的逻辑
Sandbox/src/SandBoxApp.cpp
c++
...
m_Shader.reset(Hazel::Shader::Create(vertexSrc, fragmentSrc));
...
m_BlueShader.reset(Hazel::Shader::Create(vertexSrc, fragmentSrc));
...
到这步,能运行起来了,和之前的效果一样。
imGui设置颜色到Shader
增加一个额外的功能,加一个imGui控件,设置矩形棋盘的颜色。
blueShader命名不准确,改成flatColorShader(单色shader)。对应的shader、vertexSrc、fragmentSrc都改成成flatColor开头的命名。
flatColorShaderFragmentSrc中增加uniform变量 vec3 v_Color,用来控制棋盘矩阵颜色
Sandbox/src/SandBoxApp.cpp
c++
std::string flatColorShaderVertexSrc = R"(
#version 330 core
layout(location = 0) in vec3 a_Position;
...
}
)";
std::string flatColorShaderFragmentSrc = R"(
#version 330 core
layout(location=0) out vec4 color;
in vec3 v_Position;
uniform vec3 u_Color;
void main() {
color = vec4(u_Color, 1.0);
}
)";
private:
std::shared_ptr<Hazel::Shader> m_Shader;
std::shared_ptr<Hazel::VertexArray> m_VertexArray;
std::shared_ptr<Hazel::Shader> m_FlatColorShader;
std::shared_ptr<Hazel::VertexArray> m_SquareVA;
...
运行起来,棋盘是黑色的,此时u_Color为默认值(0, 0, 0)
绑定u_Color
声明m_SquareColor,和shader中的u_Color对应
c++
...
float m_CameraRotationSpeed = 180.0f;
glm::vec3 m_SquareColor = {0.2f, 0.3f, 0.8f};
OnUpdate中,每帧更新m_CameraRotationSpeed到Shader中
c++
Hazel::Renderer::BeginScene(m_Camera);
static glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(0.1f));
std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_FlatColorShader)->Bind();
std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_FlatColorShader)->UploadUniformFloat3("u_Color", m_SquareColor);
ExampleLayer的OnImGuiRenderer()派上用场了,增加imGui颜色选择器的控件
c++
virtual void OnImGuiRender() override {
ImGui::Begin("Settings");
ImGui::ColorEdit3("Square Color", glm::value_ptr(m_SquareColor));
ImGui::End();
}
运行没问题的话,出现一个颜色选择的面板,随意选择颜色,会同步更新棋盘矩阵的颜色。
完整代码&总结
本次代码修改参考:
github.com/summer-go/H...
文章会同步更新到掘进平台,有问题欢迎留言交流:
juejin.cn/column/7156...