게임엔진/크로스플랫폼 : HazelEngine

230503 자체 엔진 개발 : Shader Asset File System

mrawesome 2023. 5. 7. 20:45

https://github.com/ohbumjun/GameEngineTutorial/tree/0422ce9729ceefe6010ed09b90444ea2884fff44

 

GitHub - ohbumjun/GameEngineTutorial

Contribute to ohbumjun/GameEngineTutorial development by creating an account on GitHub.

github.com

 

 

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;
	}