我们之前的所有 Shader 都是在 cpp 文件中进行的,在 Shader 代码比较冗长的时候会让程序变得难以阅读,因此需要将 Shader 单独用文件保存起来,然后使用的时候进行读取和编译。
之前 Shader 的创建是通过传入两个字符串,并对其进行编译之后进行绑定,现在新添加一种创建方式,就是通过文件名来进行读入:
static Shader* Create(const std::string& filepath);
//.cpp
Shader* Shader::Create(const std::string& filepath)
{
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(filepath);
}
HZ_CORE_ASSERT(false, "Unknown RendererAPI!");
return nullptr;
}
同样的,在引擎的 Shader 中我们只是进行 API 的选择,具体的实现放到对应 API 中进行:
在 OpenGLShader 中,需要对传入的文件名进行读取,将文件中的所有字符串保存在一个string 变量当中,这里 Cherno 将 ver 和 frag 的 Shader 放在同一个文件当中,然后添加 type 对字符串进行处理,从而使整个 shader 看起来更加简洁:
// Basic Texture Shader
#type vertex
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;
uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
out vec2 v_TexCoord;
void main()
{
v_TexCoord = a_TexCoord;
gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
}
#type fragment
#version 330 core
layout(location = 0) out vec4 color;
in vec2 v_TexCoord;
uniform sampler2D u_Texture;
void main()
{
color = texture(u_Texture, v_TexCoord);
}
在读取文件函数中进行如下处理:
首先将所有的字符保存在 string 变量中并返回
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
{
HZ_CORE_ERROR("Could not open file '{0}'", filepath);
}
return result;
}
然后对该字符串进行处理:
std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
{
std::unordered_map<GLenum, std::string> shaderSources; //分别保存两个shader并记录,用哈希表方便我们使用的时候找到
const char* typeToken = "#type"; //先找到字符串 type 以此来确定是何种 shader
size_t typeTokenLength = strlen(typeToken);
size_t pos = source.find(typeToken, 0); //从起始位置找 type
while (pos != std::string::npos) //如果找到了,进入循环,没找到直接返回
{
size_t eol = source.find_first_of("\r\n", pos); //从 type 之后找当前行剩下的字符串(vertex/fragment)
HZ_CORE_ASSERT(eol != std::string::npos, "Syntax error");
size_t begin = pos + typeTokenLength + 1;
std::string type = source.substr(begin, eol - begin);
HZ_CORE_ASSERT(ShaderTypeFromString(type), "Invalid shader type specified");
size_t nextLinePos = source.find_first_not_of("\r\n", eol); //确定类型之后,起始位置变为下一行的开始位置
pos = source.find(typeToken, nextLinePos); //然后找下一个 type token
shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));//存储在哈希表中
}
return shaderSources;
}
按照之前的方法进行编译,只不过这次我们把编译放在一个for 循环里面, 遍历哈希表进行编译:
void OpenGLShader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources)
{
GLuint program = glCreateProgram();
std::vector<GLenum> glShaderIDs(shaderSources.size());
for (auto& kv : shaderSources)
{
GLenum 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);
HZ_CORE_ERROR("{0}", infoLog.data());
HZ_CORE_ASSERT(false, "Shader compilation failure!");
break;
}
glAttachShader(program, shader);
glShaderIDs.push_back(shader);
}
m_RendererID = program;
// 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);
for (auto id : glShaderIDs)
glDeleteShader(id);
HZ_CORE_ERROR("{0}", infoLog.data());
HZ_CORE_ASSERT(false, "Shader link failure!");
return;
}
for (auto id : glShaderIDs)
glDetachShader(program, id);
}
构造函数:
OpenGLShader::OpenGLShader(const std::string& filepath)
{
std::string source = ReadFile(filepath);
auto shaderSources = PreProcess(source);
Compile(shaderSources);
}
之后我们添加一个简单的 shader
// Basic Texture Shader
#type vertex
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;
uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
out vec2 v_TexCoord;
void main()
{
v_TexCoord = a_TexCoord;
gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
}
#type fragment
#version 330 core
layout(location = 0) out vec4 color;
in vec2 v_TexCoord;
uniform sampler2D u_Texture;
void main()
{
color = texture(u_Texture, v_TexCoord);
}
然后进行使用:
m_TextureShader.reset(Hazel::Shader::Create("assets/shaders/Texture.glsl"));
这样我们的 Shader 就可以从外部文件导入了!