게임엔진/크로스플랫폼 : HazelEngine
230503 자체 엔진 개발 : Shader Asset File System
mrawesome
2023. 5. 7. 20:45
https://github.com/ohbumjun/GameEngineTutorial/tree/0422ce9729ceefe6010ed09b90444ea2884fff44
https://www.youtube.com/watch?v=vunANt-I3pk&list=PLkaVDtEaS2nYqfACk9Cx4JgP6nW0kY5o-&index=2
과정
이제 glsl 이라는 파일에 내가 사용할 Shader Code 를 작성
-> 하드웨어에 존재하는 glsl 파일로부터 Shader Code Load
-> Shader Code 를 Vertex, Fragment Shader 코드를 분리해서, 각각을 OpenGL 에 매핑시키기
쉐이더 코드
#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);
}
관련 API
// 1)
m_TextureShader.reset(Hazel::Shader::Create("assets/shaders/Texture.glsl"));
// 2)
Shader* Shader::Create(const std::string& path)
{
switch (Renderer::GetAPI())
{
case RendererAPI::API::None:
HZ_CORE_ASSERT(false, "no api set");
return nullptr;
case RendererAPI::API::OpenGL:
return new OpenGLShader(path);
}
HZ_CORE_ASSERT(false, "Unknown RendererAPI");
return nullptr;
}
// 3) 특정 파일로 부터 Shader 코드를 읽어오기 + 컴파일 + OpenGL 에 매핑시키기
OpenGLShader::OpenGLShader(const std::string& filePath)
{
std::string shaderCode = std::move(ReadFile(filePath));
auto shaderSource = std::move(PreProcess(shaderCode));
Compile(shaderSource);
}
// 4) 특정 glsl 파일에 작성된 ShaderCode 를 String 형태로 읽어오기
std::string OpenGLShader::ReadFile(std::string_view filePath)
{
std::ifstream in(filePath.data(), std::ios::in, std::ios::binary);
std::string result;
if (in)
{
// 파일 포인터를 파일 끝으로 보낸다.
in.seekg(0, std::ios::end);
// 파일 크기만큼 resize
result.resize(in.tellg());
// 다시 파일 처음으로 돌아가기
in.seekg(0, std::ios::beg);
// 파일 정보 읽어들이기
in.read(&result[0], result.size());
}
else
{
HZ_CORE_ERROR("Could not open file : '{0}'", filePath);
}
return result;
}
// 5) Shader Source Code 가 들어있는 string 으로부터 Vertex, Fragment Shader 분리
std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
{
std::unordered_map<GLenum, std::string> shaderSources;
// keyword looking for
const char* typeToken = "#type";
size_t typeTokenLength = strlen(typeToken);
// 맨 처음에는 0 이 될 것이다. (만약 glsl 맨 윗줄에 #type 을 적어두었다면)
size_t pos = source.find(typeToken, 0);
// 모든 #type token 을 찾을 것이다.
while (pos != std::string::npos)
{
// 위에서 찾은 pos 이후부터 new line 으로 넘어가는 지점을 찾는다.
size_t eol = source.find_first_of("\r\n", pos);
HZ_CORE_ASSERT(eol != std::string::npos, "Syntax error");
// read 시작 위치
size_t begin = pos + typeTokenLength + 1;
std::string type = source.substr(begin, eol - begin);
HZ_CORE_ASSERT(ShaderTypeFromString(type), "Invalid shader type specified");
// read 끝 위치
size_t nextLinePos = source.find_first_not_of("\r\n", eol);
// 그 다음 #type 직전 위치까지 찾는다. ex) fragment [이부분부터] ~~ [여기까지]#type
pos = source.find(typeToken, nextLinePos);
// 해당 Type 의 Shader 코드를 읽는다.
shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));
}
return shaderSources;
}
// 6) Vertex, Fragment 에 해당하는 string 형태의 소스코드를 이용하여 Shader 코드 컴파일
void OpenGLShader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources)
{
// Vertex and fragment OpenGLShaders are successfully compiled.
// Now time to link them together into a program.
// Get a program object.
// (참고 : OpenGLShader 라는 소스코드를 컴파일 하면 실행시킬 수 있는 프로그램을 얻는다)
// GLuint program = glCreateProgram();
GLuint program = glCreateProgram();
std::vector<GLenum> glShaderIDs(shaderSources.size());
for (auto& key : shaderSources)
{
GLenum shaderType = key.first;
const std::string& shaderString = key.second;
// Create an empty OpenGLShader handle (create OpenGLShader)
GLuint shader = glCreateShader(shaderType);
// Send the vertex OpenGLShader source code to GL
// Note that std::string's .c_str is NULL character terminated.
const GLchar* sourceCStr = shaderString.c_str();
glShaderSource(shader, 1, &sourceCStr, 0);
// Compile the vertex OpenGLShader
glCompileShader(shader);
// Check if compliation succeded or failed
GLint isCompiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
// Fail Compile
if (isCompiled == GL_FALSE)
{
GLint maxLength = 0;
// Get Length of logs (error message)
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
// The maxLength includes the NULL character
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);
// We don't need the OpenGLShader anymore.
// (어차피 return 해서 나갈 것임)
glDeleteShader(shader);
HZ_CORE_ERROR("{0}", infoLog.data());
HZ_CORE_ASSERT(false, "shader compliation failure !");
return;
}
// Attach our OpenGLShaders to our program
glAttachShader(program, shader);
glShaderIDs.push_back(shader);
}
// Link our program
glLinkProgram(program);
// Note the different functions here: glGetProgram* instead of glGetOpenGLShader*.
// Check Linking Failure
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 OpenGLShaders either.
for (auto& shaderID : glShaderIDs)
{
glDeleteShader(shaderID);
}
HZ_CORE_ERROR("OpenGLShader link failure");
HZ_CORE_ERROR("{0}", infoLog.data());
HZ_CORE_ASSERT(false, "!");
return;
}
// Always detach OpenGLShaders after a successful link.
// Don't leak OpenGLShaders either.
for (auto& shaderID : glShaderIDs)
{
glDetachShader(program, shaderID);
}
// 모두 성공할 경우 이때 비로소 m_RendererID 에 값을 세팅한다.
m_RendererID = program;
}