게임 최적화 기법
게임 개발에서 최적화는 매우 중요한 작업입니다. 오늘은 게임 성능을 향상시키기 위한 다양한 최적화 기법을 학습하겠습니다.
1. 프로파일링
최적화를 시작하기 전에, 먼저 성능 병목 지점을 찾기 위해 프로파일링을 수행해야 합니다. 프로파일러 도구를 사용하여 CPU와 GPU 사용량, 메모리 사용량, 프레임 속도 등을 분석할 수 있습니다.
프로파일링 도구 예시:
- Visual Studio Profiler
- NVIDIA Nsight
- AMD CodeXL
2. 알고리즘 최적화
효율적인 알고리즘을 사용하면 성능을 크게 향상시킬 수 있습니다. 시간 복잡도와 공간 복잡도를 고려하여 알고리즘을 선택해야 합니다.
3. 메모리 최적화
메모리 최적화는 캐시 친화적인 데이터 구조와 메모리 관리 기법을 사용하여 성능을 향상시킵니다.
캐시 친화적인 데이터 구조
연속적인 메모리 배열을 사용하여 캐시 히트를 증가시킬 수 있습니다.
// 캐시 친화적인 배열 사용
std::vector<int> data(1000000);
for (int i = 0; i < 1000000; ++i) {
data[i] = i;
}
메모리 풀링
메모리 풀링은 메모리 할당과 해제를 줄여 성능을 향상시킵니다.
class MemoryPool {
public:
MemoryPool(size_t size) {
pool = new char[size];
freeList.push(pool);
}
void* allocate(size_t size) {
if (freeList.empty()) return nullptr;
void* ptr = freeList.top();
freeList.pop();
return ptr;
}
void deallocate(void* ptr) {
freeList.push(static_cast<char*>(ptr));
}
~MemoryPool() {
delete[] pool;
}
private:
char* pool;
std::stack<char*> freeList;
};
4. 렌더링 최적화
프러닝
프러닝은 보이지 않는 객체를 렌더링하지 않는 기법입니다. 대표적으로 프러스트럼 컬링과 백페이스 컬링이 있습니다.
// 백페이스 컬링 활성화
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
레벨 오브 디테일 (LOD)
LOD 기법은 객체의 거리에 따라 낮은 해상도의 모델을 사용하여 렌더링 성능을 향상시킵니다.
5. 병렬 처리
멀티스레딩과 GPU 병렬 처리를 사용하여 성능을 향상시킬 수 있습니다.
멀티스레딩
#include <thread>
void task() {
// 작업 코드
}
int main() {
std::thread t1(task);
std::thread t2(task);
t1.join();
t2.join();
return 0;
}
GPU 병렬 처리
CUDA와 OpenCL을 사용하여 GPU를 이용한 병렬 처리를 구현할 수 있습니다.
__global__ void add(int* a, int* b, int* c) {
int index = threadIdx.x + blockIdx.x * blockDim.x;
c[index] = a[index] + b[index];
}
int main() {
int a[1000], b[1000], c[1000];
int *d_a, *d_b, *d_c;
cudaMalloc(&d_a, 1000 * sizeof(int));
cudaMalloc(&d_b, 1000 * sizeof(int));
cudaMalloc(&d_c, 1000 * sizeof(int));
cudaMemcpy(d_a, a, 1000 * sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_b, b, 1000 * sizeof(int), cudaMemcpyHostToDevice);
add<<<100, 10>>>(d_a, d_b, d_c);
cudaMemcpy(c, d_c, 1000 * sizeof(int), cudaMemcpyDeviceToHost);
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
return 0;
}
main.cpp 수정
main.cpp
파일에 프러닝, 멀티스레딩, 그리고 메모리 최적화를 적용합니다.
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <boost/asio.hpp>
#include <thread>
#include "Shader.h"
#include "Camera.h"
#include "Enemy.h"
#include "Server.h"
#include "Client.h"
// 정점 셰이더 소스 코드
const char* vertexShaderSource = "path/to/vertex_shader.glsl";
const char* fragmentShaderSource = "path/to/fragment_shader.glsl";
// 카메라 설정
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = 400, lastY = 300;
bool firstMouse = true;
// 시간 설정
float deltaTime = 0.0f;
float lastFrame = 0.0f;
// 적 캐릭터 생성
Enemy enemy(glm::vec3(0.0f, 0.0f, -5.0f), 1.0f);
// 네트워크 설정
boost::asio::io_service io_service;
std::shared_ptr<Server> server;
std::shared_ptr<Client> client;
// 이전 상태와 현재 상태
glm::vec3 previousPosition;
glm::vec3 currentPosition;
// 콜백 함수
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
bool checkCollision(glm::vec3 pos1, glm::vec3 pos2, float radius);
glm::vec3 interpolate(const glm::vec3& start, const glm::vec3& end, float alpha);
int main(int argc, char* argv[]) {
// 서버 또는 클라이언트 모드 설정
bool isServer = false;
if (argc > 1) {
isServer = std::string(argv[1]) == "server";
}
if (isServer) {
server = std::make_shared<Server>(io_service, 12345);
std::thread serverThread([]() { io_service.run(); });
serverThread.detach();
} else {
client = std::make_shared<Client>(io_service, "localhost", "12345");
std::thread clientThread([]() { io_service.run(); });
clientThread.detach();
}
// GLFW 초기화
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}
// OpenGL 버전 설정 (3.3)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 창 생성
GLFWwindow* window = glfwCreateWindow(800, 600, "3D Game Project", NULL, NULL);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// 마우스 캡처
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// GLEW 초기화
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
std::cerr << "Failed to initialize GLEW" << std::endl;
return -1;
}
// 셰이더 프로그램 생성
Shader ourShader(vertexShaderSource, fragmentShaderSource);
// 정점 데이터
float vertices[] = {
// 위치 // 법선 // 텍스처 좌표
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5
f, 0.0f, 0.0f, -1.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, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.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, // 아래
1, 2, 6, 6, 5, 1, // 왼쪽
0, 3, 7, 7, 4, 0 // 오른쪽
};
GLuint VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, 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);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 텍스처 로딩
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
int width, height;
unsigned char* image = SOIL_load_image("path/to/your/texture.png", &width, &height, 0, SOIL_LOAD_RGB);
if (image) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
} else {
std::cerr << "Failed to load texture" << std::endl;
}
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);
// OpenGL 상태 설정
glEnable(GL_DEPTH_TEST);
// 백페이스 컬링 활성화
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
// 조명 설정
glm::vec3 lightDir(0.2f, 1.0f, 0.3f);
glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
// 메인 루프
while (!glfwWindowShouldClose(window)) {
// 시간 계산
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// 입력 처리
processInput(window);
// 적 캐릭터 이동
enemy.moveTowards(camera.Position, deltaTime);
// 적 캐릭터 공격 범위 확인
if (enemy.isWithinAttackRange(camera.Position, 1.0f)) {
std::cout << "Enemy is attacking!" << std::endl;
}
// 충돌 감지
if (checkCollision(camera.Position, enemy.Position, 0.5f)) {
std::cout << "Collision detected!" << std::endl;
}
// 클라이언트 상태 전송
if (client) {
std::string msg = "Player position: " + std::to_string(camera.Position.x) + " " + std::to_string(camera.Position.y) + " " + std::to_string(camera.Position.z);
client->write(msg);
}
// 서버로부터 데이터 수신
if (client) {
std::string serverMsg = client->read();
if (!serverMsg.empty()) {
std::istringstream iss(serverMsg);
std::string prefix;
float x, y, z;
iss >> prefix >> x >> y >> z;
previousPosition = currentPosition;
currentPosition = glm::vec3(x, y, z);
}
}
// 보간하여 위치 업데이트
float alpha = 0.1f; // 보간 비율
glm::vec3 interpolatedPosition = interpolate(previousPosition, currentPosition, alpha);
camera.Position = interpolatedPosition;
// 화면 지우기
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 셰이더 프로그램 사용
ourShader.use();
// 카메라 변환 행렬 설정
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), 800.0f / 600.0f, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
GLint projectionLoc = glGetUniformLocation(ourShader.Program, "projection");
GLint viewLoc = glGetUniformLocation(ourShader.Program, "view");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
// 모델 변환 행렬 설정
glm::mat4 model = glm::mat4(1.0f);
GLint modelLoc = glGetUniformLocation(ourShader.Program, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
// 조명 설정
glUniform3f(glGetUniformLocation(ourShader.Program, "lightDir"), lightDir.x, lightDir.y, lightDir.z);
glUniform3f(glGetUniformLocation(ourShader.Program, "lightColor"), lightColor.r, lightColor.g, lightColor.b);
glUniform3f(glGetUniformLocation(ourShader.Program, "objectColor"), 1.0f, 0.5f, 0.31f);
// 텍스처 바인딩
glBindTexture(GL_TEXTURE_2D, texture);
// 객체 그리기
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
// 적 캐릭터 그리기
model = glm::translate(glm::mat4(1.0f), enemy.Position);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
// 버퍼 교환
glfwSwapBuffers(window);
glfwPollEvents();
}
// 자원 해제
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
// 창 크기 변경 시 호출되는 콜백 함수
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
// 마우스 이동 시 호출되는 콜백 함수
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
// 마
우스 휠 사용 시 호출되는 콜백 함수
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
camera.ProcessMouseScroll(yoffset);
}
// 키보드 입력 처리
void processInput(GLFWwindow *window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
}
// 충돌 감지 함수
bool checkCollision(glm::vec3 pos1, glm::vec3 pos2, float radius) {
return glm::distance(pos1, pos2) < radius;
}
// 보간 함수
glm::vec3 interpolate(const glm::vec3& start, const glm::vec3& end, float alpha) {
return start + alpha * (end - start);
}
결론
오늘은 게임 성능을 향상시키기 위한 다양한 최적화 기법을 학습했습니다. 프로파일링, 알고리즘 최적화, 메모리 최적화, 렌더링 최적화, 그리고 병렬 처리 기법을 통해 게임 성능을 최적화할 수 있었습니다. 질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "Day 30: 게임 배포와 커뮤니티 관리"에 대해 학습하겠습니다.
'-----ETC----- > C++ 게임 개발 시리즈' 카테고리의 다른 글
[C++ 게임 개발 시리즈] Day 30: 게임 배포와 커뮤니티 관리 (0) | 2024.08.01 |
---|---|
[C++ 게임 개발 시리즈] Day 27: 멀티플레이어 게임 개발 기초 (0) | 2024.08.01 |
[C++ 게임 개발 시리즈] Day 28: 네트워크 동기화와 지연 처리 (0) | 2024.08.01 |
[C++ 게임 개발 시리즈] Day 25: 3D 게임 프로젝트 시작 (2) (0) | 2024.08.01 |
[C++ 게임 개발 시리즈] Day 26: 3D 게임 프로젝트 시작 (3) (0) | 2024.08.01 |