Dragon Engine:材质系统
这一节介绍材质系统,主要是进行着色器类的抽象:
namespace Dragon
{
class Shader
{
public:
virtual ~Shader() = default;
virtual void Bind() const = 0;
virtual void Unbind() const = 0;
virtual const std::string& GetName() const = 0;
static std::shared_ptr<Shader> Create(const std::string& filepath);
static std::shared_ptr<Shader> Create(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc);
};
class ShaderLibrary
{
public:
void Add(const std::string& name, const std::shared_ptr<Shader>& shader);
void Add(const std::shared_ptr<Shader> shader);
std::shared_ptr<Shader> Load(const std::string& filepath);
std::shared_ptr<Shader> Load(const std::string& name, const std::string& filepath);
std::shared_ptr<Shader> Get(const std::string& name);
bool Exists(const std::string& name) const;
private:
std::unordered_map<std::string, std::shared_ptr<Shader>> m_Shaders;
};
}
这里设计了两个类,一个着色器类,一个着色器库类。着色器类的成员函数很简单,绑定,解绑,获取着色器名字,根据文件或者字符串源创建着色器。着色器库类可以管理着色器,主要数据结构是一个unordered_map
,可以添加现有着色器,从文件中加载着色器,根据存储的着色器名字获取着色器对象等。
相关实现,着色器类:
namespace Dragon
{
std::shared_ptr<Shader> Shader::Create(const std::string& filepath)
{
switch (Renderer::GetAPI())
{
case RendererAPI::API::None: DG_CORE_ASSERT(false, "RendererAPI::None is currently not supported!"); return nullptr;
case RendererAPI::API::OpenGL: return std::make_shared<OpenGLShader>(filepath);
}
DG_CORE_ASSERT(false, "Unknown RendererAPI!");
return nullptr;
}
std::shared_ptr<Shader> Shader::Create(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc)
{
switch (Renderer::GetAPI())
{
case RendererAPI::API::None :
DG_CORE_ASSERT(false, "RendererAPI::None is currently not supported!");
return nullptr;
case RendererAPI::API::OpenGL:
return std::make_shared<OpenGLShader>(name,vertexSrc, fragmentSrc);
}
DG_CORE_ASSERT(false, "RendererAPI!");
return nullptr;
}
两个创建着色器的静态方法和之前的类似,根据API创建对应的着色器。
着色器类库实现方法:
void ShaderLibrary::Add(const std::string& name, const std::shared_ptr<Shader>& shader)
{
DG_CORE_ASSERT(!Exists(name), "Shader already exists");
m_Shaders[name] = shader;
}
void ShaderLibrary::Add(const std::shared_ptr<Shader> shader)
{
auto& name = shader->GetName();
Add(name, shader);
}
std::shared_ptr<Shader> ShaderLibrary::Load(const std::string& filepath)
{
auto shader = Shader::Create(filepath);
Add(shader);
return shader;
}
std::shared_ptr<Shader> ShaderLibrary::Load(const std::string& name, const std::string& filepath)
{
auto shader = Shader::Create(filepath);
Add(name, shader);
return shader;
}
std::shared_ptr<Shader> ShaderLibrary::Get(const std::string& name)
{
DG_CORE_ASSERT(Exists(name), "Shader not found!");
return m_Shaders[name];
}
bool ShaderLibrary::Exists(const std::string& name) const
{
return m_Shaders.find(name) != m_Shaders.end();
}
Exits()
函数使用unordered_map
的find
函数来查找是否存在某一着色器名字,Add
和Load
、Get
函数很简单。
接下来看具体的OpenGL实现,类声明:
namespace Dragon
{
class OpenGLShader : public Shader
{
public:
OpenGLShader(const std::string& filepath);
OpenGLShader(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc);
virtual ~OpenGLShader();
virtual void Bind() const override;
virtual void Unbind() const override;
virtual const std::string& GetName() const override { return m_Name; }
void UploadUniformInt(const std::string& name, int value);
void UploadUniformBool(const std::string& name, bool 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& matrix);
void UploadUniformMat4(const std::string& name, const glm::mat4& matrix);
private:
std::string ReadFile(const std::string& filepath);
std::unordered_map<unsigned int, std::string> PreProcess(const std::string& source);
void Compile(const std::unordered_map<unsigned int, std::string>& shaderSources);
private:
uint32_t m_RendererID;
std::string m_Name;
bool m_FirstCreated = false;
};
}
这里定义了多个Upload**
函数,用于向着色器对象传递uniform变量。ReadFile
可以读取文件,PreProcess
可以处理着色器源代码,Compile
函数可以编译着色器。
相关实现:
static unsigned int ShaderTypeFromString(const std::string& type)
{
if (type == "vertex")
return GL_VERTEX_SHADER;
if (type == "fragment")
return GL_FRAGMENT_SHADER;
DG_CORE_ASSERT(false, "Unknown shader type!");
return 0;
}
首先定义了一个着色器类型转为OpenGL类型的函数,这里只定义了顶点和片元,大家还可以额外添加几何、细分曲面等。
OpenGLShader::OpenGLShader(const std::string& filepath)
{
std::string source = ReadFile(filepath);
auto shaderSources = PreProcess(source);
Compile(shaderSources);
//获取名字
auto lastSlash = filepath.find_last_of("/\\");
lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;
auto lastDot = filepath.rfind('.');
auto count = lastDot == std::string::npos ? filepath.size() - lastSlash : lastDot - lastSlash;
m_Name = filepath.substr(lastSlash, count);
}
OpenGLShader::OpenGLShader(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc)
:m_Name(name)
{
std::unordered_map<unsigned int, std::string> sources;
sources[GL_VERTEX_SHADER] = vertexSrc;
sources[GL_FRAGMENT_SHADER] = fragmentSrc;
Compile(sources);
}
读取文件的构造函数,首先ReadFile
读取文件,接着PreProcess
处理源代码字符串, ShaderParameterMapInit,SetShaderParameterName(source),CreateParameter()
用于自动创建着色器参数,Compile
用于编译着色器。下方的一串字符串分割代码用于找到文件的根目录。
第二个构造函数很简单,设置好map即可。
OpenGLShader::~OpenGLShader()
{
glDeleteProgram(m_RendererID);
}
析构函数删除着色器程序。
std::string OpenGLShader::ReadFile(const std::string& filepath)
{
std::string result;
std::ifstream in(filepath, std::ios::in, std::ios::binary);
if (in)
{
in.seekg(0, std::ios::end);
result.resize(in.tellg());
in.seekg(0, std::ios::beg);
in.read(&result[0], result.size());
in.close();
}
else
{
DG_CORE_ERROR("Could not open file '{0}'", filepath);
}
return result;
}
读取文件方法,std::ios::in
和std::ios::binary
表示只读二进制读取文件。seekg
函数第一个参数表示偏移位置,第二个参数表示流搜索开始的位置,相当于将流指针定位到文件尾部,tellg
获取文件长度,并用resize
改变字符串长度。接着使用seekg
将文件流指针定位到起始位置,接着用read
函数将文件从字符串起始位置读取并存储到字符串中。
std::unordered_map<unsigned int, std::string> OpenGLShader::PreProcess(const std::string& source)
{
std::unordered_map<unsigned int, std::string> shaderSources;
const char* typeToken = "#type";
size_t typeTokenLength = strlen(typeToken);
size_t pos = source.find(typeToken, 0);
while (pos != std::string::npos)
{
size_t eol = source.find_first_of("\r\n", pos);
DG_CORE_ASSERT(eol != std::string::npos, "Syntax error");
size_t begin = pos + typeTokenLength + 1;
std::string type = source.substr(begin, eol - begin);
DG_CORE_ASSERT(ShaderTypeFromString(type), "Invalid shader type specified");
size_t nextLinePos = source.find_first_not_of("\r\n", eol);
pos = source.find(typeToken, nextLinePos);
shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));
}
return shaderSources;
}
该预处理函数与我们规定的着色器代码编写规则有关,我们使用#type ***
标识着色器类型,比如一个兰伯特材质(无阴影):
#type vertex
#version 330 core
layout (location = 0) in vec3 a_Position;
layout (location = 1) in vec2 a_TexCoords;
layout (location = 2) in vec3 a_Normal;
out vec3 v_WorldPos;
out vec3 v_Normal;
uniform mat4 u_Model;
uniform mat4 u_View;
uniform mat4 u_Projection;
void main()
{
v_Normal = vec3(transpose(inverse(u_Model)) * vec4(a_Normal, 0.0));
v_WorldPos = vec3(u_Model * vec4(a_Position, 1.0));
gl_Position = u_Projection * u_View * vec4(v_WorldPos, 1.0);
}
#type fragment
#version 330 core
layout (location = 0) out vec4 f_Color;
in vec3 v_WorldPos;
in vec3 v_Normal;
uniform vec3 u_Albedo;
uniform vec3 u_Ambient;
//平行光
struct DirLight
{
vec3 direction;
vec3 diffuse;
};
//点光源
struct PointLight
{
vec3 position;
vec3 diffuse;
vec3 attenuation;
};
//光源
uniform DirLight u_DirLight;
uniform int u_PointLightNum;
#define MAX_POINT_LIGHT_NUM 10
uniform PointLight u_PointLights[MAX_POINT_LIGHT_NUM];
//函数定义
vec3 CalDirLight(DirLight dirLight);
vec3 CalPointLight(PointLight pointLight);
void main()
{
//计算平行光
vec3 result = CalDirLight(u_DirLight);
//计算点光源
for(int i = 0; i < u_PointLightNum; i++)
{
result += CalPointLight(u_PointLights[i]);
}
f_Color = vec4(result, 1.0);
}
vec3 CalDirLight(DirLight dirLight)
{
vec3 normal = normalize(v_Normal);
vec3 lightDir = normalize(-dirLight.direction);
//环境光
vec3 ambient = u_Ambient*0.01 * u_Albedo;
//漫反射因数
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * dirLight.diffuse * u_Albedo;
vec3 result = ambient + diffuse;
return result;
}
vec3 CalPointLight(PointLight pointLight)
{
vec3 normal = normalize(v_Normal);
vec3 lightDir = normalize(pointLight.position - v_WorldPos);
//环境光
vec3 ambient = u_Ambient*0.01 * u_Albedo;
//漫反射因数
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * pointLight.diffuse * u_Albedo;
//衰减
float distance = length(pointLight.position - v_WorldPos);
float attenuation = 1.0 / (pointLight.attenuation.x + pointLight.attenuation.y * distance + pointLight.attenuation.z * distance * distance);
vec3 result = (ambient + diffuse) * attenuation;
return result;
}
编译着色器的函数,非常的常规,基本来自于官网的示例:
void OpenGLShader::Compile(const std::unordered_map<unsigned int, std::string>& shaderSources)
{
GLuint program = glCreateProgram();
std::vector<unsigned int> glShaderIDs(shaderSources.size());
for (auto& kv : shaderSources)
{
unsigned int type = kv.first;
const std::string& source = kv.second;
GLuint shader = glCreateShader(type);
const GLchar* sourceCStr = source.c_str();
glShaderSource(shader, 1, &sourceCStr, 0);
glCompileShader(shader);
GLint isCompiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
if (isCompiled == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);
glDeleteShader(shader);
DG_CORE_ERROR("{0}", infoLog.data());
DG_CORE_ASSERT(false, "Shader compilation failure!");
break;
}
glAttachShader(program, shader);
glShaderIDs.push_back(shader);
}
m_RendererID = program;
glLinkProgram(program);
GLint isLinked = 0;
glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);
if (isLinked == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(program, GL_INFO_LOG_LENGTH, &maxLength);
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(program, maxLength, &maxLength, &infoLog[0]);
glDeleteProgram(program);
for (auto id : glShaderIDs)
glDeleteShader(id);
DG_CORE_ERROR("{0}", infoLog.data());
DG_CORE_ASSERT(false, "Shader program linking failure!");
}
for (auto id : glShaderIDs)
glDetachShader(program, id);
}
各种Upload**
函数很简单, 以下面一个为例:
void OpenGLShader::UploadUniformInt(const std::string& name, int value)
{
GLint location = glGetUniformLocation(m_RendererID, name.c_str());
glUniform1i(location, value);
}
绑定函数和解绑函数很简单:
void OpenGLShader::Bind() const
{
glUseProgram(m_RendererID);
}
void OpenGLShader::Unbind() const
{
glUseProgram(0);
}
至此,一个着色器类和一个着色器库类完成,在层类的构造方法中,可以这么用:
//加载着色器文件
m_ShaderLibrary.Load("shaderName","shaderFile");
// 获取着色器
auto shader = m_ShaderLibrary.Get("shaderName");
// 使用着色器程序
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->Bind();
//上传uniform参数
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->UploadUniform***();
在Update()
函数中:
auto shader = m_ShaderLibrary.Get("shaderName");
//设置背景颜色
Dragon::RenderCommand::SetColor({...});
//清除缓冲
Dragon::RenderCommand::Clear();
//开始场景
Dragon::Renderer::BeginScene();
// 使用着色器程序
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->Bind();
//上传uniform参数
std::dynamic_pointer_cast<Dragon::OpenGLShader>(shader)->UploadUniform***();
//提交渲染数据
Dragon::Renderer::Submit(...);
//结束场景
Dragon::Renderer::EndScene();
差不多就是这么一个结构。下一节介绍纹理类的实现。
项目github地址:https://github.com/Dragon-Baby/Dragon