참조 : https://learnopengl.com/Advanced-OpenGL/Framebuffers
LearnOpenGL - Framebuffers
Framebuffers Advanced-OpenGL/Framebuffers So far we've used several types of screen buffers: a color buffer for writing color values, a depth buffer to write and test depth information, and finally a stencil buffer that allows us to discard certain fragmen
learnopengl.com
우선 framebuffer라는 것에 대해 짚고 넘어가자면,
GPU는 그림을 화면(모니터)에 바로 그리는 것이 아니고 framebuffer 위에 먼저 그린다.
glDrawArrays, glDrawElements 등의 드로우콜 함수를 호출하면 framebuffer위에 그림을 그린 후, 그걸 모니터 픽셀에 복사하는 식으로 작동하는 것이다.
framebuffer는 그림을 그리는 캔버스와 같다.
기본적으로 OpenGL이 제공해주는 기본 framebuffer를 사용하게 되는데, 개발자가 직접 framebuffer를 따로 만들어 거기에 그리고싶은 경우가 있을 수 있다.
예를 들면, 게임 엔진의 경우 렌더링 결과를 프로그램 메인 윈도우 창에 바로 그리는 것이 아니고 별도의 GUI 창 안에 그리고 싶을 수 있다. (유니티 엔진의 scene view, 언리얼 엔진의 viewport)
또는 거울과 같은 그래픽적 효과, 포스트 프로세싱 이펙트를 위해 사용할 수도 있다.
OpenGL은 이를 위해 자체적으로 프레임버퍼를 만들고 사용하는 API를 제공한다.
unsigned int fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
코드를 보면 알겠지만, 일반 버퍼 오브젝트를 만드는 함수와 이름이 거의 비슷하며 (glGenBuffers), 실제로 거의 비슷한 방식으로 작동한다.
(VRAM 안에 공간을 잡고, 그 공간에 해당하는 id를 저 fbo 변수로 넘겨주는 식)
이를 이용해 지금부터 할 것은,
- 화면을 직접 만든 framebuffer에 그린다.
- framebuffer에 그린 결과물을 텍스쳐에 담는다.
- 이 텍스쳐를 가져와 원하는 곳에 사용한다.
이렇게 되겠다.
2번을 위한 API도 이미 만들어져있다.
// framebuffer에 그려진 그림을 texture에 담는다.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
이를 위해 만들어진 FrameBuffer 클래스는 다음과 같다.
FrameBuffer.h
#pragma once
class FrameBuffer
{
public:
FrameBuffer(float width, float height);
~FrameBuffer();
unsigned int getFrameTexture();
void RescaleFrameBuffer(float width, float height);
void Bind() const;
void Unbind() const;
private:
unsigned int fbo;
unsigned int texture;
unsigned int rbo;
};
FrameBuffer.cpp
// FrameBuffer.cpp
#include "FrameBuffer.h"
#include "GL/glew.h"
#include <iostream>
FrameBuffer::FrameBuffer(float width, float height)
{
// 프레임버퍼 생성
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
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);
// framebuffer에 그려진 그림을 texture에 담는다.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
// 렌더 버퍼 생성
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
// depth와 stencil 정보를 저장한다
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
// 프레임버퍼에 그릴 때 렌더 버퍼의 정보(depth, stencil)를 사용
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
FrameBuffer::~FrameBuffer()
{
glDeleteFramebuffers(1, &fbo);
glDeleteTextures(1, &texture);
glDeleteRenderbuffers(1, &rbo);
}
unsigned int FrameBuffer::getFrameTexture()
{
return texture;
}
void FrameBuffer::RescaleFrameBuffer(float width, float height)
{
glBindTexture(GL_TEXTURE_2D, texture);
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);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
}
void FrameBuffer::Bind() const
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
}
void FrameBuffer::Unbind() const
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
FrameBuffer 생성자 부분을 보면 RenderBuffer도 만들어 사용했다. 사용자가 직접 만든 FrameBuffer의 경우 이렇게 추가적으로 필요한 버퍼를 명시적으로 첨부해주어야 하기 때문이다.
용도는 주석에 적힌대로, depth와 stencil 정보를 사용하기 위해서이다.
프로젝트에서의 사용 방법은 다음과 같다. ( 프레임버퍼에 그린 그림을 가져와 ImGui 윈도우 상에 그림)
int main()
{
// ...
// Frame Buffer 생성
FrameBuffer sceneBuffer(mainWindow->getBufferWidth(), mainWindow->getBufferHeight());
// ...
///////////////////////////////////////////////////////////////////////////
/// main loop
///////////////////////////////////////////////////////////////////////////
while (!mainWindow->GetShouldClose())
{
// ...
// --------------------------------------------------------------------------------
sceneBuffer.Bind(); // 만든 프레임 버퍼에 bind
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// --------------------------------------------------------------------------------
// (렌더링 코드)
// --------------------------------------------------------------------------------
sceneBuffer.Unbind(); // 프레임 버퍼에 그리기 완료, unbind
// --------------------------------------------------------------------------------
// Render ImGui
ImGui::Begin("Scene", NULL, ImGuiWindowFlags_NoMove);
ImVec2 pos = ImGui::GetCursorScreenPos();
const float window_width = ImGui::GetContentRegionAvail().x;
const float window_height = ImGui::GetContentRegionAvail().y;
// 프레임버퍼에서 텍스처를 가져와 ImGui 윈도우에 렌더링
ImGui::GetWindowDrawList()->AddImage(
(void*)(intptr_t)sceneBuffer->getFrameTexture(),
ImVec2(pos.x, pos.y),
ImVec2(pos.x + window_width, pos.y + window_height),
ImVec2(0, 1),
ImVec2(1, 0)
);
ImGui::End();
// ...
mainWindow->swapBuffers();
}
return 0;
}
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 "FrameBuffer.h"
#include "ScenePanel.h"
#include "InspectorPanel.h"
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "ImGuizmo.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;
Model* currModel;
DirectionalLight* directionalLight;
// 쉐이더 변수 핸들
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();
}
glm::mat4 GetPVM(glm :: mat4& modelMat)
{
// PVM 구성
glm::mat4 view = camera->GetViewMatrix();
glm::mat4 projection = camera->GetProjectionMatrix(mainWindow);
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->Initialize();
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);
// Directional Light
directionalLight = new DirectionalLight
(0.5f, 0.5f,
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);
currModel = model_2B;
// 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();
// Frame Buffer 생성
FrameBuffer sceneBuffer(mainWindow->getBufferWidth(), mainWindow->getBufferHeight());
mainWindow->SetSceneBuffer(&sceneBuffer);
// Panel 생성
ScenePanel scenePanel(&sceneBuffer, currModel, camera, mainWindow);
InspectorPanel inspectorPanel(currModel, directionalLight);
///////////////////////////////////////////////////////////////////////////
/// main loop
//////////////////////////////////////////////////////////////////////////
while (!mainWindow->GetShouldClose())
{
GLfloat now = glfwGetTime();
deltaTime = now - lastTime;
lastTime = now;
// Get + Handle User Input
glfwPollEvents();
if (mainWindow->GetMouseButton()[GLFW_MOUSE_BUTTON_2])
MoveCamera();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// Clear the window
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// --------------------------------------------------------------------------------
sceneBuffer.Bind();
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);
Model* currModel = model_2B;
glm::mat4 modelMat = currModel->GetModelMat();
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);
glm::vec4 camPos = glm::vec4(camera->GetPosition(), 1.f);
glm::vec3 camPos_wc = glm::vec3(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);
// --------------------------------------------------------------------------------
sceneBuffer.Unbind();
// --------------------------------------------------------------------------------
scenePanel.Render();
inspectorPanel.Render();
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
mainWindow->swapBuffers();
}
return 0;
}
프로젝트 전체 소스 코드 : https://github.com/sys010611/YsEngine
결과:
렌더링이 메인 윈도우가 아닌 Scene 윈도우 안에 된 것을 볼 수 있다.
'OpenGL > 공부' 카테고리의 다른 글
[OpenGL] Tessellation Shader (0) | 2024.08.09 |
---|---|
[OpenGL] Skybox (0) | 2024.07.18 |
[OpenGL] ImGuizmo 설치, 사용법 (0) | 2024.07.16 |
[OpenGL] Model Loading (Assimp) (1) | 2024.07.13 |
[OpenGL] Phong Reflection Model - 구현 (0) | 2024.06.19 |