본문 바로가기
-----ETC-----/C++로 배우는 게임 엔진 개발

[C++로 배우는 게임 엔진 개발] Day 27: 포스트 프로세싱 효과

by cogito21_cpp 2024. 8. 1.
반응형

포스트 프로세싱 효과

오늘은 포스트 프로세싱 효과를 적용하여 3D 씬의 시각적 품질을 더욱 향상시키는 방법을 학습하겠습니다. 포스트 프로세싱은 렌더링된 이미지를 후처리하여 다양한 그래픽 효과를 추가하는 기술입니다. 흔히 사용되는 포스트 프로세싱 효과로는 블러, 블룸, 색상 보정 등이 있습니다.

1. 프레임 버퍼 객체 (FBO) 설정

포스트 프로세싱을 위해 먼저 프레임 버퍼 객체 (FBO)를 설정합니다. FBO는 렌더링 결과를 텍스처로 저장할 수 있게 해줍니다.

 

헤더 파일 작성

include/PostProcessing.h 파일을 생성하고 다음과 같이 작성합니다.

#ifndef POSTPROCESSING_H
#define POSTPROCESSING_H

#include <GL/glew.h>
#include <glm/glm.hpp>

class PostProcessing {
public:
    PostProcessing();
    ~PostProcessing();

    bool Initialize(int width, int height);
    void BeginRender();
    void EndRender();
    void Render(const glm::mat4& view, const glm::mat4& projection);

private:
    GLuint fbo;
    GLuint textureColorBuffer;
    GLuint rbo;
    GLuint quadVAO, quadVBO;
    GLuint shaderProgram;

    void setupQuad();
    GLuint LoadShader(const std::string& vertexPath, const std::string& fragmentPath);
};

#endif // POSTPROCESSING_H

 

소스 파일 작성

src/PostProcessing.cpp 파일을 생성하고 다음과 같이 작성합니다.

#include "PostProcessing.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <stb_image.h>

PostProcessing::PostProcessing() : fbo(0), textureColorBuffer(0), rbo(0), quadVAO(0), quadVBO(0), shaderProgram(0) {}

PostProcessing::~PostProcessing() {
    glDeleteFramebuffers(1, &fbo);
    glDeleteTextures(1, &textureColorBuffer);
    glDeleteRenderbuffers(1, &rbo);
    glDeleteVertexArrays(1, &quadVAO);
    glDeleteBuffers(1, &quadVBO);
}

bool PostProcessing::Initialize(int width, int height) {
    shaderProgram = LoadShader("shaders/post_processing_vertex.glsl", "shaders/post_processing_fragment.glsl");
    if (shaderProgram == 0) {
        return false;
    }

    // Framebuffer 생성
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // Color attachment 텍스처 생성
    glGenTextures(1, &textureColorBuffer);
    glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorBuffer, 0);

    // Renderbuffer 생성 및 depth/stencil attachment 추가
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

    // Framebuffer 완성 여부 확인
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        std::cerr << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
        return false;
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    setupQuad();

    return true;
}

void PostProcessing::BeginRender() {
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void PostProcessing::EndRender() {
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void PostProcessing::Render(const glm::mat4& view, const glm::mat4& projection) {
    glUseProgram(shaderProgram);
    glBindVertexArray(quadVAO);
    glDisable(GL_DEPTH_TEST);
    glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindVertexArray(0);
    glEnable(GL_DEPTH_TEST);
}

void PostProcessing::setupQuad() {
    float quadVertices[] = {
        // positions   // texCoords
        -1.0f,  1.0f,  0.0f, 1.0f,
        -1.0f, -1.0f,  0.0f, 0.0f,
         1.0f, -1.0f,  1.0f, 0.0f,

        -1.0f,  1.0f,  0.0f, 1.0f,
         1.0f, -1.0f,  1.0f, 0.0f,
         1.0f,  1.0f,  1.0f, 1.0f
    };

    glGenVertexArrays(1, &quadVAO);
    glGenBuffers(1, &quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
}

GLuint PostProcessing::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;
}

 

쉐이더 파일 작성

포스트 프로세싱을 위한 쉐이더 파일을 작성합니다.

 

버텍스 쉐이더

shaders/post_processing_vertex.glsl 파일을 생성하고 다음과 같이 작성합니다.

#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main() {
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}

 

프래그먼트 쉐이더

shaders/post_processing_fragment.glsl 파일을 생성하고 다음과 같이 작성합니다.

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;

void main() {
    vec3 col = texture(screenTexture, TexCoords).rgb;
    FragColor = vec4(col, 1.0);
}

2. 게임 엔진에 포스트 프로세싱 통합

이제 게임 엔진에 포스트 프로세싱을 통합하고, 포스트 프로세싱 효과를 적용합니다.

 

헤더 파일 수정

include/GameEngine.h 파일을 수정하여 PostProcessing 클래스를 포함합니다.

#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 "Model.h"
#include "Camera.h"
#include "PhysicsManager.h"
#include "Skybox.h"
#include "PostProcessing.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;
    Model* model;
    Camera* camera;
    PhysicsManager* physicsManager;
    Skybox* skybox;
    PostProcessing* postProcessing;
    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;

float lastX = 400, lastY = 300;
bool firstMouse = true;

GameEngine::GameEngine()
    : window(nullptr), glContext(nullptr), renderer(nullptr), shaderProgram(0), 
      particleSystem(nullptr), uiManager(nullptr), luaManager(nullptr), networkManager(nullptr),
      graphics3D(nullptr), model(nullptr), camera(nullptr), physicsManager(nullptr), skybox(nullptr),
      postProcessing(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_3d.glsl", "shaders/fragment_shader_3d.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;
    }

    model = new Model("path/to/your/model.obj");
    camera = new Camera(glm::vec3(0.0f, 0.0f, 3.0f));
    physicsManager = new PhysicsManager();
    physicsManager->Initialize();

    std::vector<std::string> faces{
        "path/to/right.jpg",
        "path/to/left.jpg",
        "path/to/top.jpg",
        "path/to/bottom.jpg",
        "path/to/front.jpg",
        "path/to/back.jpg"
    };
    skybox = new Skybox();
    if (!skybox->Initialize(faces)) {
        return false;
    }

    postProcessing = new PostProcessing();
    if (!postProcessing->Initialize(width, height)) {
        return false;
    }

    // 물리 엔진에 오브젝트 추가
    btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(0, 1, 0), 1);
    btCollisionShape* fallShape = new btSphereShape(1);

    btDefaultMotionState* groundMotionState = new btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, -1, 0)));
    btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI(0, groundMotionState, groundShape, btVector3(0, 0, 0));
    btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);
    physicsManager->GetDynamicsWorld()->addRigidBody(groundRigidBody);

    btDefaultMotionState* fallMotionState = new btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, 50, 0)));
    btScalar mass = 1;
    btVector3 fallInertia(0, 0, 0);
    fallShape->calculateLocalInertia(mass, fallInertia);
    btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(mass, fallMotionState, fallShape, fallInertia);
    btRigidBody* fallRigidBody = new btRigidBody(fallRigidBodyCI);
    physicsManager->GetDynamicsWorld()->addRigidBody(fallRigidBody);

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

    vertex

Shader = 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();

        postProcessing->BeginRender();
        Render();
        postProcessing->EndRender();

        postProcessing->Render(camera->GetViewMatrix(), glm::perspective(glm::radians(camera->Zoom), (float)800 / (float)600, 0.1f, 100.0f));

        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;
                case SDLK_w:
                    camera->ProcessKeyboard(FORWARD, frameTime / 1000.0f);
                    break;
                case SDLK_s:
                    camera->ProcessKeyboard(BACKWARD, frameTime / 1000.0f);
                    break;
                case SDLK_a:
                    camera->ProcessKeyboard(LEFT, frameTime / 1000.0f);
                    break;
                case SDLK_d:
                    camera->ProcessKeyboard(RIGHT, frameTime / 1000.0f);
                    break;
                default:
                    break;
            }
        }
        if (event.type == SDL_MOUSEMOTION) {
            float xpos = static_cast<float>(event.motion.x);
            float ypos = static_cast<float>(event.motion.y);

            if (firstMouse) {
                lastX = xpos;
                lastY = ypos;
                firstMouse = false;
            }

            float xoffset = xpos - lastX;
            float yoffset = lastY - ypos;

            lastX = xpos;
            lastY = ypos;

            camera->ProcessMouseMovement(xoffset, yoffset);
        }
        if (event.type == SDL_MOUSEWHEEL) {
            camera->ProcessMouseScroll(static_cast<float>(event.wheel.y));
        }
    }
}

void GameEngine::Update() {
    float deltaTime = frameTime / 1000.0f;
    particleSystem->Update(deltaTime);
    physicsManager->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);

    // 카메라 뷰 및 프로젝션 설정
    glm::mat4 view = camera->GetViewMatrix();
    glm::mat4 projection = glm::perspective(glm::radians(camera->Zoom), (float)800 / (float)600, 0.1f, 100.0f);

    GLuint viewLoc = glGetUniformLocation(shaderProgram, "view");
    GLuint projLoc = glGetUniformLocation(shaderProgram, "projection");
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

    // 3D 모델 렌더링
    model->Render(shaderProgram);

    // 스카이박스 렌더링
    skybox->Render(view, projection);

    // 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;
    }
    if (model) {
        delete model;
        model = nullptr;
    }
    if (camera) {
        delete camera;
        camera = nullptr;
    }
    if (physicsManager) {
        physicsManager->Shutdown();
        delete physicsManager;
        physicsManager = nullptr;
    }
    if (skybox) {
        delete skybox;
        skybox = nullptr;
    }
    if (postProcessing) {
        delete postProcessing;
        postProcessing = 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();
}

3. 프로젝트 빌드 및 실행

  1. Visual Studio에서 CMake 프로젝트 열기:
    • Visual Studio를 실행하고, File -> Open -> CMake...를 선택합니다.
    • GameEngine 디렉토리를 선택하여 프로젝트를 엽니다.
  2. 프로젝트 빌드:
    • Visual Studio 상단의 Build 메뉴에서 Build All을 선택하여 프로젝트를 빌드합니다.
  3. 프로젝트 실행:
    • Debug 메뉴에서 Start Without Debugging을 선택하여 프로그램을 실행합니다.
    • 윈도우 창이 생성되고, 포스트 프로세싱 효과가 적용된 3D 씬이 렌더링됩니다.

마무리

오늘은 포스트 프로세싱 효과를 적용하여 3D 씬의 시각적 품질을 더욱 향상시키는 방법을 학습했습니다. 이를 통해 게임 엔진에 다양한 그래픽 효과를 추가할 수 있었습니다. 다음 단계에서는 인스턴싱과 최적화 기법을 다루어 게임 성능을 더욱 향상시키는 방법을 배워보겠습니다.

질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요.

Day 28 예고

다음 날은 "인스턴싱과 최적화 기법"에 대해 다룰 것입니다. 3D 오브젝트를 효율적으로 렌더링하고, 게임 성능을 최적화하는 방법을 배워보겠습니다.

반응형