3D 그래픽스 기초 (OpenGL/DirectX)
오늘은 3D 그래픽스의 기초를 이해하고, OpenGL을 사용하여 간단한 3D 모델을 렌더링하는 방법을 학습하겠습니다. 3D 그래픽스는 2D 그래픽스보다 복잡하지만, 게임 개발에서 중요한 요소입니다.
1. OpenGL 3D 기초 설정
먼저, OpenGL을 사용하여 3D 그래픽스를 렌더링하기 위한 기초 설정을 합니다.
헤더 파일 작성
include/Graphics3D.h
파일을 생성하고 다음과 같이 작성합니다.
#ifndef GRAPHICS3D_H
#define GRAPHICS3D_H
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <string>
class Graphics3D {
public:
Graphics3D();
~Graphics3D();
bool Initialize();
void Shutdown();
void RenderCube();
private:
GLuint shaderProgram;
GLuint VAO, VBO;
GLuint LoadShader(const std::string& vertexPath, const std::string& fragmentPath);
void SetupCube();
};
#endif // GRAPHICS3D_H
소스 파일 작성
src/Graphics3D.cpp
파일을 생성하고 다음과 같이 작성합니다.
#include "Graphics3D.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
Graphics3D::Graphics3D() : shaderProgram(0), VAO(0), VBO(0) {}
Graphics3D::~Graphics3D() {
Shutdown();
}
bool Graphics3D::Initialize() {
shaderProgram = LoadShader("shaders/vertex_shader_3d.glsl", "shaders/fragment_shader_3d.glsl");
if (shaderProgram == 0) {
return false;
}
SetupCube();
return true;
}
void Graphics3D::Shutdown() {
if (VAO) {
glDeleteVertexArrays(1, &VAO);
VAO = 0;
}
if (VBO) {
glDeleteBuffers(1, &VBO);
VBO = 0;
}
if (shaderProgram) {
glDeleteProgram(shaderProgram);
shaderProgram = 0;
}
}
GLuint Graphics3D::LoadShader(const std::string& vertexPath, const std::string& fragmentPath) {
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
vShaderFile.close();
fShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure& e) {
std::cerr << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
return 0;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
GLuint vertexShader, fragmentShader;
GLint success;
GLchar infoLog[512];
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vShaderCode, nullptr);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
return 0;
}
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fShaderCode, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
return 0;
}
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
return 0;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
void Graphics3D::SetupCube() {
float vertices[] = {
// positions // colors // texture coords
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f
};
unsigned int indices[] = {
0, 1, 2, 2, 3, 0,
4, 5, 6, 6, 7, 4,
0, 1, 5, 5, 4, 0,
2, 3, 7, 7, 6, 2,
0, 3, 7, 7, 4, 0,
1, 2, 6, 6, 5, 1
};
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindVertexArray(0);
}
void Graphics3D::RenderCube() {
glUseProgram(shaderProgram);
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f,
-3.0f));
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)800 / (float)600, 0.1f, 100.0f);
GLuint modelLoc = glGetUniformLocation(shaderProgram, "model");
GLuint viewLoc = glGetUniformLocation(shaderProgram, "view");
GLuint projLoc = glGetUniformLocation(shaderProgram, "projection");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
2. 쉐이더 파일 작성
3D 렌더링에 필요한 쉐이더 파일을 작성합니다.
버텍스 쉐이더
shaders/vertex_shader_3d.glsl
파일을 생성하고 다음과 같이 작성합니다.
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
layout(location = 2) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}
프래그먼트 쉐이더
shaders/fragment_shader_3d.glsl
파일을 생성하고 다음과 같이 작성합니다.
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
void main() {
FragColor = vec4(ourColor, 1.0);
}
3. 게임 엔진에 3D 그래픽스 추가
이제 게임 엔진에 3D 그래픽스를 통합하고, 3D 모델을 렌더링하도록 수정합니다.
헤더 파일 수정
include/GameEngine.h
파일을 수정하여 Graphics3D
클래스를 포함합니다.
#ifndef GAMEENGINE_H
#define GAMEENGINE_H
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <GL/glew.h>
#include "ParticleSystem.h"
#include "UIManager.h"
#include "LuaManager.h"
#include "NetworkManager.h"
#include "TextureManager.h"
#include "Graphics3D.h"
#include "AudioManager.h"
class GameEngine {
public:
GameEngine();
~GameEngine();
bool Initialize(const char* title, int width, int height);
void Run();
void Shutdown();
private:
SDL_Window* window;
SDL_GLContext glContext;
SDL_Renderer* renderer;
GLuint shaderProgram;
ParticleSystem* particleSystem;
UIManager* uiManager;
LuaManager* luaManager;
NetworkManager* networkManager;
Graphics3D* graphics3D;
bool isRunning;
Uint32 frameStart;
int frameTime;
bool InitializeOpenGL();
GLuint LoadShader(const std::string& vertexPath, const std::string& fragmentPath);
void HandleEvents();
void Update();
void Render();
};
#endif // GAMEENGINE_H
소스 파일 수정
src/GameEngine.cpp
파일을 다음과 같이 수정합니다.
#include "GameEngine.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
const int FPS = 60;
const int FRAME_DELAY = 1000 / FPS;
GameEngine::GameEngine()
: window(nullptr), glContext(nullptr), renderer(nullptr), shaderProgram(0),
particleSystem(nullptr), uiManager(nullptr), luaManager(nullptr), networkManager(nullptr),
graphics3D(nullptr), isRunning(false), frameStart(0), frameTime(0) {}
GameEngine::~GameEngine() {
Shutdown();
}
bool GameEngine::Initialize(const char* title, int width, int height) {
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
return false;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
if (window == nullptr) {
std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
return false;
}
glContext = SDL_GL_CreateContext(window);
if (glContext == nullptr) {
std::cerr << "SDL_GL_CreateContext Error: " << SDL_GetError() << std::endl;
return false;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (renderer == nullptr) {
std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
return false;
}
if (glewInit() != GLEW_OK) {
std::cerr << "GLEW Initialization failed!" << std::endl;
return false;
}
shaderProgram = LoadShader("shaders/vertex_shader.glsl", "shaders/fragment_shader.glsl");
if (shaderProgram == 0) {
return false;
}
particleSystem = new ParticleSystem(500);
uiManager = new UIManager(renderer);
if (!uiManager->Initialize()) {
return false;
}
uiManager->RenderText("Hello, Game Engine!", 10, 10, 24, { 255, 255, 255, 255 });
luaManager = new LuaManager();
if (!luaManager->Initialize()) {
return false;
}
luaManager->LoadScript("path/to/your/script.lua");
if (!TextureManager::Instance().Load("path/to/your/texture.png", "example", renderer)) {
return false;
}
networkManager = new NetworkManager();
if (!networkManager->Initialize()) {
return false;
}
graphics3D = new Graphics3D();
if (!graphics3D->Initialize()) {
return false;
}
isRunning = true;
return true;
}
GLuint GameEngine::LoadShader(const std::string& vertexPath, const std::string& fragmentPath) {
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
vShaderFile.close();
fShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure& e) {
std::cerr << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
return 0;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
GLuint vertexShader, fragmentShader;
GLint success;
GLchar infoLog[512];
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vShaderCode, nullptr);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
return 0;
}
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fShaderCode, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
return 0;
}
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::PROGRAM::
LINKING_FAILED\n" << infoLog << std::endl;
return 0;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
void GameEngine::Run() {
while (isRunning) {
frameStart = SDL_GetTicks();
HandleEvents();
Update();
Render();
frameTime = SDL_GetTicks() - frameStart;
if (FRAME_DELAY > frameTime) {
SDL_Delay(FRAME_DELAY - frameTime);
}
}
}
void GameEngine::HandleEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
isRunning = false;
}
if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
isRunning = false;
break;
default:
break;
}
}
}
}
void GameEngine::Update() {
float deltaTime = frameTime / 1000.0f;
particleSystem->Update(deltaTime);
luaManager->ExecuteFunction("update", deltaTime);
networkManager->Service();
}
void GameEngine::Render() {
// 화면을 검은색으로 지우기
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(shaderProgram);
// 3D 그래픽 렌더링
graphics3D->RenderCube();
// SDL 렌더러 사용하여 텍스처 렌더링
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
TextureManager::Instance().Draw("example", 100, 100, 64, 64, renderer);
uiManager->Render();
SDL_RenderPresent(renderer);
SDL_GL_SwapWindow(window);
}
void GameEngine::Shutdown() {
if (particleSystem) {
delete particleSystem;
particleSystem = nullptr;
}
if (uiManager) {
uiManager->Shutdown();
delete uiManager;
uiManager = nullptr;
}
if (luaManager) {
luaManager->Shutdown();
delete luaManager;
luaManager = nullptr;
}
if (networkManager) {
networkManager->Shutdown();
delete networkManager;
networkManager = nullptr;
}
if (graphics3D) {
graphics3D->Shutdown();
delete graphics3D;
graphics3D = nullptr;
}
TextureManager::Instance().ClearTextureMap();
if (shaderProgram) {
glDeleteProgram(shaderProgram);
shaderProgram = 0;
}
if (renderer) {
SDL_DestroyRenderer(renderer);
renderer = nullptr;
}
if (glContext) {
SDL_GL_DeleteContext(glContext);
glContext = nullptr;
}
if (window) {
SDL_DestroyWindow(window);
window = nullptr;
}
SDL_Quit();
}
4. 프로젝트 빌드 및 실행
- Visual Studio에서 CMake 프로젝트 열기:
- Visual Studio를 실행하고,
File
->Open
->CMake...
를 선택합니다. GameEngine
디렉토리를 선택하여 프로젝트를 엽니다.
- Visual Studio를 실행하고,
- 프로젝트 빌드:
- Visual Studio 상단의
Build
메뉴에서Build All
을 선택하여 프로젝트를 빌드합니다.
- Visual Studio 상단의
- 프로젝트 실행:
Debug
메뉴에서Start Without Debugging
을 선택하여 프로그램을 실행합니다.- 윈도우 창이 생성되고, 3D 큐브가 화면에 렌더링됩니다.
마무리
오늘은 OpenGL을 사용하여 3D 그래픽스를 렌더링하는 방법을 학습했습니다. 이를 통해 게임 엔진에 3D 그래픽스를 통합할 수 있었습니다. 다음 단계에서는 3D 모델 로딩 및 렌더링을 다루어 실제 게임 오브젝트를 렌더링하는 방법을 배워보겠습니다.
질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요.
Day 23 예고
다음 날은 "3D 모델 로딩 및 렌더링"에 대해 다룰 것입니다. 실제 3D 모델을 로딩하고, 게임 엔진에서 렌더링하는 방법을 배워보겠습니다.
'-----ETC----- > C++로 배우는 게임 엔진 개발' 카테고리의 다른 글
[C++로 배우는 게임 엔진 개발] Day 20: 리소스 관리 시스템 구현 (0) | 2024.08.01 |
---|---|
[C++로 배우는 게임 엔진 개발] Day 21: 네트워크 기초 (ENet) (0) | 2024.08.01 |
[C++로 배우는 게임 엔진 개발] Day 23: 3D 모델 로딩 및 렌더링 (0) | 2024.08.01 |
[C++로 배우는 게임 엔진 개발] Day 24: 카메라 시스템 구현 (0) | 2024.08.01 |
[C++로 배우는 게임 엔진 개발] Day 25: 3D 물리 엔진 기초 (Bullet) (0) | 2024.08.01 |