OpenGL/개발 일지

[OpenGL] 게임 엔진 개발 (1) - 시작 + ImGui

ciel45 2024. 7. 14. 18:30

지금까지 학습한 내용을 바탕으로 게임 엔진 제작을 시작하였다.

 

 

우선 엔진의 기본 구성 요소들(Camera, Model, Mesh, Light, Material 등)은 공부 일지에서 사용했던 코드를 사용하되, 내 스타일에 맞게 여기저기 많이 고쳤다.

 

공부할 때 사용했던 코드는 포인터를 거의 사용하지 않았기 때문에 헤더 파일에 #include가 필요하게되어 헤더가 비대화되는 문제가 있었다.

https://ciel45.tistory.com/4 (관련 내용)

 

[언리얼 엔진 5] 헤더를 include할 때 주의할 점 (+ 포워드 선언)

우선, #include "__.h"를 사용하는 것은 해당 헤더의 소스 코드 전체를 include문의 위치에 그대로 복사 & 붙여넣기하는 것과 같다.컴파일 시에 소스 코드의 include문이 해당 헤더 파일 전체로 교체되는

ciel45.tistory.com

그래서 언리얼 엔진 개발을 할 때와 마찬가지로, 포인터 + 포워드 선언을 사용하는 방식으로 바꿨다.

 

 

프로퍼티 이름들이 불필요하게 길거나 직관적이지 못한 부분들도 많아서 많이 개선하였다.

 

지금까지의 현황은 여기서 볼 수 있다. (Clone하면 바로 실행해볼 수 있도록 라이브러리도 같이 커밋하였다.)

https://github.com/sys010611/YsEngine

 

GitHub - sys010611/YsEngine

Contribute to sys010611/YsEngine development by creating an account on GitHub.

github.com

 

 

기본 구성 요소를 적당히 준비한 후, 게임 엔진의 Inspector 역할을 해줄 GUI가 필요했다.

이를 위해 ImGui라는 라이브러리를 사용했다.

https://github.com/ocornut/imgui

 

GitHub - ocornut/imgui: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies - ocornut/imgui

github.com

GUI를 위한 라이브러리는 종류가 많은 걸로 알고있는데, 이게 꽤나 괜찮은 것 같다.

 

설치는 이 동영상을 참고하였다.

https://www.youtube.com/watch?v=VRwhNKoxUtk&t=74s&ab_channel=VictorGordan

 

 

코드 상의 세팅 방법도 제작자가 친절하게 만들어놓은 것을 볼 수 있었다.

https://github.com/ocornut/imgui/wiki/Getting-Started#example-if-you-are-using-glfw--openglwebgl

 

Getting Started

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies - ocornut/imgui

github.com

 

 

 

현재 상황에서, ImGui를 통해 조정할 수 있도록 할 것은 모델의 Transformation, 모델의 Material 속성, Directional Light의 방향과 속성이다.

 

위의 세팅 방법을 참고하여 initialize를 해준 후, 코드를 다음과 같이 짰다.

// ImGui 창을 구성한다.
void compose_imgui_frame(Model* currModel)
{
	// Start the Dear ImGui frame
	ImGui_ImplOpenGL3_NewFrame();
	ImGui_ImplGlfw_NewFrame();
	ImGui::NewFrame();

	// control window
	{
		// Model
		ImGui::Begin("Model");

		ImGui::SliderFloat3("Translate", currModel->GetTranslate(), -100.f, 100.f);
		ImGui::InputFloat3("Rotate", currModel->GetRotate());
		ImGui::SliderFloat3("Scale", currModel->GetScale(), -10.f, 10.f);

		ImGui::End();


		// Directional Light
		ImGui::Begin("DirectionalLight");

		ImGui::SliderFloat("Ambient", &dLight_ambient, 0.f, 5.f);
		ImGui::SliderFloat("Diffuse", &dLight_diffuse, 0.f, 5.f);

		ImGui::End();

		
		// Material
		Material* currMaterial = currModel->GetMaterial();
		ImGui::Begin("Material");

		ImGui::SliderFloat("Specular", &currMaterial->specular, 0.f, 5.f);
		ImGui::SliderFloat("Shininess", &currMaterial->shininess, 0.f, 512.f);

		ImGui::End();
	}
}

 

모델의 Transformation을 수정하는 창, DirectionalLight를 수정하는 창, 모델의 Material을 수정하는 창을 각각 만드는 코드이다.

 

이걸 main 함수의 while loop 안에서 호출해주면 된다.

 

main.cpp

#define STB_IMAGE_IMPLEMENTATION

#include <iostream>
#include <vector>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <glm/glm.hpp>
#include <glm\gtc\matrix_transform.hpp>
#include <glm\gtc\type_ptr.hpp>

#include <assimp/Importer.hpp>

#include "Camera.h"
#include "Mesh.h"
#include "Shader.h"
#include "Window.h"
#include "Model.h"
#include "Light.h"
#include "DirectionalLight.h"
#include "PointLight.h"
#include "Texture.h"
#include "Material.h"

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

#define WIDTH 1600
#define HEIGHT 900

Window* mainWindow;
Camera* camera;

GLfloat deltaTime = 0.f;
GLfloat lastTime = 0.f;

// Vertex Shader
static const char* vShaderPath = "Shaders/vertex.glsl";

// Fragment Shader
static const char* fShaderPath = "Shaders/fragment.glsl";

std::vector<Shader*> shaderList;

Model* model_2B;

DirectionalLight* directionalLight;

// DirectionalLight Intensity
GLfloat dLight_ambient;
GLfloat dLight_diffuse;

// 쉐이더 변수 핸들
GLuint loc_modelMat = 0;
GLuint loc_PVM = 0;
GLuint loc_sampler = 0;
GLuint loc_normalMat = 0;
GLuint loc_eyePos = 0;

// 쉐이더 컴파일
void CreateShader()
{
	Shader* shader = new Shader;
	shader->CreateFromFiles(vShaderPath, fShaderPath);
	shaderList.push_back(shader);
}

void GetShaderHandles()
{
	// 핸들 얻어오기
	loc_modelMat = shaderList[0]->GetModelMatLoc();
	loc_PVM = shaderList[0]->GetPVMLoc();
	loc_normalMat = shaderList[0]->GetNormalMatLoc();
	loc_eyePos = shaderList[0]->GetEyePosLoc();
}

void compose_imgui_frame(Model* currModel)
{
	// Start the Dear ImGui frame
	ImGui_ImplOpenGL3_NewFrame();
	ImGui_ImplGlfw_NewFrame();
	ImGui::NewFrame();

	// control window
	{
		// Model
		ImGui::Begin("Model");

		ImGui::SliderFloat3("Translate", currModel->GetTranslate(), -100.f, 100.f);
		ImGui::InputFloat3("Rotate", currModel->GetRotate());
		ImGui::SliderFloat3("Scale", currModel->GetScale(), -10.f, 10.f);

		ImGui::End();


		// Directional Light
		ImGui::Begin("DirectionalLight");

		ImGui::SliderFloat("Ambient", &dLight_ambient, 0.f, 5.f);
		ImGui::SliderFloat("Diffuse", &dLight_diffuse, 0.f, 5.f);

		ImGui::End();

		
		// Material
		Material* currMaterial = currModel->GetMaterial();
		ImGui::Begin("Material");

		ImGui::SliderFloat("Specular", &currMaterial->specular, 0.f, 5.f);
		ImGui::SliderFloat("Shininess", &currMaterial->shininess, 0.f, 512.f);

		ImGui::End();
	}
}

glm::mat4 GetModelMat(Model* currModel)
{
	GLfloat* translate = currModel->GetTranslate();
	GLfloat* rotate = currModel->GetRotate();
	GLfloat* scale = currModel->GetScale();

	// model Matrix 구성
	glm::mat4 T = glm::translate(glm::mat4(1.f), glm::vec3(translate[0], translate[1], translate[2]));
	glm::mat4 R = glm::mat4_cast(glm::quat(glm::vec3(rotate[0], rotate[1], rotate[2])));
	glm::mat4 S = glm::scale(glm::mat4(1.f), glm::vec3(scale[0], scale[1], scale[2]));
	glm::mat4 modelMat = T * R * S;

	return modelMat;
}

glm::mat4 GetPVM(glm :: mat4& modelMat)
{
	// PVM 구성
	glm::mat4 view = camera->calculateViewMatrix();
	glm::mat4 projection = glm::perspective(glm::radians(45.0f),
		(GLfloat)mainWindow->getBufferWidth() / mainWindow->getBufferHeight(), 0.1f, 100.0f);
	glm::mat4 PVM = projection * view * modelMat;

	return PVM;
}

glm::mat3 GetNormalMat(glm::mat4& modelMat)
{
	return glm::mat3(glm::transpose(glm::inverse(modelMat)));
}

void MoveCamera()
{
	camera->keyControl(mainWindow->GetKeys(), deltaTime);
	camera->mouseControl(mainWindow->getXChange(), mainWindow->getYChange());
}


int main()
{
    // GLFW 초기화
    if (!glfwInit())
    {
        printf("GLFW 초기화 실패\n");
        glfwTerminate();
        return 1;
    }

    mainWindow = new Window(WIDTH, HEIGHT);
    mainWindow->Initialise();

	CreateShader();

	GLfloat initialPitch = 0.f;
	GLfloat initialYaw = -90.f; // 카메라가 -z축을 보고 있도록
	camera = new Camera(glm::vec3(0.f, 0.f, 20.f), glm::vec3(0.f, 1.f, 0.f), initialYaw, initialPitch, 10.f, 0.3f);
	
	dLight_ambient = 0.5f;
	dLight_diffuse = 0.5f;
	directionalLight = new DirectionalLight
		(dLight_ambient, dLight_diffuse,
		glm::vec4(1.f, 1.f, 1.f, 1.f), 
		glm::vec3(1.f, 1.5f, -1.f));

	model_2B = new Model();
	std::string modelPath = "2b_nier_automata/scene.gltf";
	model_2B->LoadModel(modelPath);

	// Setup Dear ImGui context
	IMGUI_CHECKVERSION();
	ImGui::CreateContext();
	ImGuiIO& io = ImGui::GetIO();
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls

	// Setup Platform/Renderer backends
	ImGui_ImplGlfw_InitForOpenGL(mainWindow->GetGLFWwindow(), true); // Second param install_callback=true will install GLFW callbacks and chain to existing ones. 
	ImGui_ImplOpenGL3_Init();

    /////////////////////////
    /// while loop
    ////////////////////////
    while (!mainWindow->GetShouldClose())
    {
		GLfloat now = glfwGetTime();
		deltaTime = now - lastTime;
		lastTime = now;

		// Get + Handle User Input
		glfwPollEvents();

		const auto& io = ImGui::GetIO();
		if (!io.WantCaptureMouse && !io.WantCaptureKeyboard)
			MoveCamera();

		// Clear the window
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		// -------------------------------------------------------------------

		shaderList[0]->UseShader();

		// 쉐이더 내부 변수 위치들 가지고오기
		GetShaderHandles();
		glUniform1i(loc_sampler, 0); // sampler를 0번 텍스쳐 유닛과 연결

		Model* currModel = model_2B;

		// imgui 창 그리기
		compose_imgui_frame(currModel);

		glm::mat4 modelMat = GetModelMat(currModel);
		glm::mat4 PVM = GetPVM(modelMat);
		glm::mat4 normalMat = GetNormalMat(modelMat);
		glUniformMatrix4fv(loc_modelMat, 1, GL_FALSE, glm::value_ptr(modelMat));
		glUniformMatrix4fv(loc_PVM, 1, GL_FALSE, glm::value_ptr(PVM));
		glUniformMatrix3fv(loc_normalMat, 1, GL_FALSE, glm::value_ptr(normalMat));

		shaderList[0]->UseDirectionalLight(directionalLight, dLight_ambient, dLight_diffuse);

		glm::vec4 camPos = glm::vec4(camera->GetPosition(), 1.f);
		glm::vec3 camPos_wc = modelMat * camPos;
		glUniform3f(loc_eyePos, camPos_wc.x, camPos_wc.y, camPos_wc.z);

		shaderList[0]->UseMaterial(model_2B->GetMaterial());

		model_2B->RenderModel();

		//-------------------------------------------------------------------

		glUseProgram(0);

		ImGui::Render();
		ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

		mainWindow->swapBuffers();
    }
    return 0;
}

 

전체 코드 : https://github.com/sys010611/YsEngine

 

 

실행 결과:

https://youtu.be/J0BIgfQuPxQ