
지금까지 learnOpenGL을 공부하면서
Color, Depth, Stencil buffer를 알아봤음
매 프레임마다 buffer를 clear해주고 머시기 하고 ㅇㅇ
이런 프레임별로 화면에 띄울 fragment를 결정짓는 버퍼를 뭉뚱그려서 스크린버퍼 라고 부름
Color buffer, Depth buffer, Stencil buffer가 스크린 버퍼들인거지비 ㅇㅇ
그리고 이러한 버퍼들이 GPU 메모리에 하나로 모이면,
그 버퍼들의 집합을 frame buffer라고 부르는거임
LearnOpenGL(1) - 창 띄우기
여기에서 glfwSwapBuffers라는걸 사용하는걸 볼 수 있음
이게 위에서 설명한 프레임 버퍼를 프레임단위로 swap하는거임
이렇게 glfw의 기본 프레임버퍼만 사용 할 수도 있지만
우리가 직접 추가적으로 프레임 버퍼를 만들어서
거울에 따로 비치는 장면을 만든다던지,
원하는 특수효과(포스트 프로세싱)를 넣을 수 잇음
binding해서 쓰던것처럼 쓰면 됨
uint32_t fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
아주 easy~~
Bind할때 bind 가능한 버퍼종류가 있긴함
GL_FRAMEBUFFER : 읽기, 쓰기 모두 가능한 frame bufferGL_READ_FRAMEBUFFER : 읽기만 가능한 frame bufferGL_FRAW_FRAMEBUFFER : 쓰기만 가능한 frame buffer보통은 읽기, 쓰기 모두 가능한 프레임 버퍼를 쓰긴함ㅇㅇ
그리고 프레임 버퍼를 저상태로 쓸수있는건 아님
몇가지 조건이 만족되어야 그때부터 커스텀 프레임 버퍼를 쓸 수 있음
이게 조건이 모두 충족되면 이제 커스텀 프레임 버퍼를 사용할 수 있는거지비
그럼 사용가능한지 판단을 어떻게 하냐??
glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
이렇게 COMPLETE가 반환되면 사용할 수 있다는거지비 ㅇㅇ
커스텀 프레임 버퍼를 바인딩하면
이후 모든 attachments들은 커스텀 버퍼에게 연결되게 됨
그래서 기본 프레임 버퍼는 렌더링 되지 않음
따라서 기본 프레임 버퍼를 렌더링하려고 하면
glBindFramebuffer(GL_FRAMEBUFFER, 0);
를 해주어야함
glDeleteFramebuffers(1, &fbo);
무조건무조건!
uint32_t texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 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);
texture를 만들고 GL_TEXTURE_2D에 바인딩한 후,
glTexImage2D를 이용해 texture를 묶어줄건데
텍스쳐의 크기는 보통 화면크기(거울과 같으면 거울의 크기)를 사용하고,
texture data는 NULL을 사용함
왜냐면 지금당장 texture를 넣을게 아니고,
화면에 그릴 프레임이기때문에, 렌더링단계에서 텍스쳐(화면)이 채워질거임ㅇㅇ
더 작은 화면에 그리고 싶으면
프레임 버퍼에 그려지기 전에(렌더링)
원하는 텍스쳐(화면)크기에 맞춰glViewPort를 호출해줘야함!
그리고 마찬가지로 texture parameter의 WRAP, MIPMAP모두 필요없음
그냥 축소, 확대될때 어떻게 필터링할건지만 지정해주자
이제 프레임버퍼의 필수조건인 색상 부착물(텍스쳐)을 프레임버퍼에 바인딩해주면 됨
uint32_t texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 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);
uint32_t fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glFramebufferTexture2D의 파라미터는 다음과 같음
GL_COLOR_ATTACHMENT?, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT, GL_DEPTH_STENCIL_ATTACHMENT 등각 attachment마다 코드가 어떻게 달라지는지 보여줌
GL_COLOR_ATTACHMENT
GLuint colorTexture;
glGenTextures(1, &colorTexture);
glBindTexture(GL_TEXTURE_2D, colorTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 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, colorTexture, 0);
GL_DEPTH_ATTACHMENT
GLuint depthTexture;
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 800, 600, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 프레임버퍼에 깊이 텍스처 연결
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
GL_STENCIL_ATTACHMENT
GLuint stencilTexture;
glGenTextures(1, &stencilTexture);
glBindTexture(GL_TEXTURE_2D, stencilTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_STENCIL_INDEX8, 800, 600, 0, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 프레임버퍼에 스텐실 텍스처 연결
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, stencilTexture, 0);
GL_DEPTH_STENCIL_ATTACHMENT
GLuint depthStencilTexture;
glGenTextures(1, &depthStencilTexture);
glBindTexture(GL_TEXTURE_2D, depthStencilTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 프레임버퍼에 깊이-스텐실 결합 텍스처 연결
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencilTexture, 0);
중요한건 glTexImage2D에서 3번째 인자인 internal format이 바뀌는걸 확인할수있도록
| 텍스쳐 종류 | internal format | format |
|---|---|---|
GL_COLOR_ATTACHMENT | GL_RGB or something RGB related | GL_RGB or something RGB related |
GL_DEPTH_ATTACHMENT | GL_DEPTH_COMPONENT | GL_DEPTH_COMPONENT |
GL_STENCIL_ATTACHMENT | GL_DEPTH24_STENCIL8 | GL_STENCIL_INDEX |
GL_DEPTH_STENCIL_ATTACHMENT | GL_DEPTH24_STENCIL8 | GL_DEPTH_STENCIL |
gl이 도입한 개념임
위의 texture attachment는 gpu에서 읽기,쓰기 모두 가능한 텍스쳐(color, depth, stencil)에 사용하는거임
프레임버퍼에 저장된 텍스쳐를 glsl에서texture()를 이용해 불러와서 쓸수있다는게 읽기 가능하다는거임
반면 지금 설명할 render buffer object는 주로 쓰기만 가능한 객체임
읽기가 필요없는 객체??
뭐지 그게?
자자 바로 Advanced OpenGL시리즈를 시작할때 설명한
depth, stencil buffer가 대표적인 읽기가 필요없는 객체임
depth, stencil buffer는 정말 중요한 자원임
물체의 앞뒤를 구분해주고, 필요한 부분만 잘라서 렌더링할 수 있도록 해주니깐 ㅇㅇ
근데 이 depth, stencil buffer를 꼭 읽기 가능해야만 할까?
사실 그렇지 않음
읽기가 없고 쓰기만 가능해도 바로 렌더링 결과에서 확인가능하고,
실제로 쓰기만 잘하면 되는 buffer임
따라서 이 render buffer object는 depth, stencil을 위해서만 사용됨
또, 쓰기가 엄청 빠름
사용자의 GPU에서 지원하는 자체적인 format으로 데이터가 저장이 됨
그래서 쓰기가 엄청 빠르고,
또, 자체적인 format을 이용하기때문에 copy하기도 용이함
따라서 프레임버퍼 교체를 해야할때, 속도가 어마무시한거지 ㅇㅇ
따라서 프레임버퍼에 render buffer object를 사용하면 성능 이점이 확실함
render buffer object도 읽기가 가능함
glReadPixel을 이용해서 프레임버퍼 전체에게 해당 픽셀의 데이터를 cpu로 읽어오도록 할 수 있는거임
그리고 이 방식은 texture attachment에서도 사용가능함하지만, rbo는 gpu에서 바로 값을 읽어 수정할 수 없다는거임
그래서 glsltexture()이 불가능함
uint32_t rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
이렇게 buffer생성, 바인딩하는것처럼 사용하면 됨
그리고 위 texture attachment에서 GL_DEPTH_STENCIL을 이용하던것처럼
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
이렇게 glRenderbufferStorage를 이용해 RenderBuffer에 사용할 형식을 지정해주고
glFramebufferRenderbuffer를 이용해 프레임버퍼에 rbo를 attach해주면 됨
texture attachment는 gpu에서 읽기,쓰기 모두 가능한 텍스쳐(color, depth, stencil)에 사용함
프레임버퍼에 저장된 텍스쳐를 glsl에서 texture()를 이용해 불러와서 쓸수있다는게 읽기 가능함
render buffer object attachment는 gpu에서 쓰기만 가능한 버퍼임
gpu에서 데이터를 읽어 수정하는 glsl의 texture()이 불가능함
하지만 gpu의 값을 cpu로 읽어서 사용하는건
texture attachment와 render buffer object attachment 모두 가능함
glReadPixel을 이용해서 프레임버퍼 전체에게 특정 픽셀의 데이터를 cpu로 읽어온 후, 원하는 작업을 수행할 수 있음
예를들어 사용자의 클릭 위치의 픽셀을 찾아 이벤트 수행 등등...
즉, 기존프레임 버퍼는 한번 렌더링하면 수정하기가 힘들어짐
따라서 해당 화면 전체에 특수효과(블러, 머시기, 그레이스케일, 머시기)등을 입히고 싶다고 해도 쉽지않음...
따라서 커스텀 프레임 버퍼는 기존프레임 버퍼에서 하지 못하는
렌더링된 화면 전체를 픽셀마다 캡쳐해서 텍스쳐로 활용해
특수효과(포스트 프로세싱)을 할 수 있는거임 ㄷㄷㄷ
위에서 말한대로
기존 프레임버퍼에서의 fragment를
우리가 만들 커스텀 프레임버퍼로 복사를하고
그 위에 특수효과를 입혀볼거임
// 커스텀 프레임버퍼
// -------------------------
unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// 텍스쳐 프레임버퍼
unsigned int textureColorbuffer;
glGenTextures(1, &textureColorbuffer);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_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, textureColorbuffer, 0);
// 뎁스/스텐실용 렌더 버퍼 오브젝트
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
//커스텀 프레임버퍼 체킹
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
이렇게 텍스쳐 프레임버퍼, 렌더 버퍼 오브젝트를 만들어서
프레임버퍼에 바인딩 시켜주고
제대로 바인딩 되었는지 확인 후 기존 프레임버퍼로 버퍼 바꿔주기!
이렇게 하는 이유가 있음
일단 간단하게 다시 설명하면
가 됨
특히, 2번과정에서 포스트 프로세싱 효과를 커스텀 프레임버퍼에 주게되면,
그걸 그대로 기존 프레임 버퍼위에 덧씌우기만 하는거니까
여러 효과를 줄 수 있게 되는거임
먼저 기존 프레임버퍼용 vertex shader, fragment shader를 만들어주자
#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);
}
//----------------------------------------
#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);
}
그냥 간단함
위에서 사용 요구사항에서 설명했듯이
커스텀 프레임버퍼에 렌더링 된거를
그대로 화면을 가득 채우는 사각형 위에 그릴거임
이제 렌더링 코드를 보자
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
};
while (!glfwWindowShouldClose(window))
{
//.....
// render
// ------
// 커스텀 프레임버퍼
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glEnable(GL_DEPTH_TEST);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.use();
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
shader.setMat4("view", view);
shader.setMat4("projection", projection);
// cubes
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
//기존 프레임버퍼
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
screenShader.use();
glBindVertexArray(quadVAO);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
기존 프레임버퍼를 채울 삼각형 2개로 사각형을 만들어줌
이때 NDC를 이용해서 가득 채우면 됨

그럼 이런 화면이 렌더링됨
근데 뭐가 다른거지??
glPolygonMode를 이용해서 와이어프레임을 그려보면 티가남
화면의 오브젝트들이

와이어프레임으로 표시되지않고
화면 전체가 2개의 삼각형으로 표시되어있음
그니까 커스텀 프레임버퍼는 진짜 딱 그리고 빠진거지
그리고 화면에 표시되는건
기존 프레임버퍼로 복사된 커스텀 프레임버퍼에서 렌더링된 화면인거고ㅇㅇ
뭐 여러 효과가 있음
블룸, 블러, 그레이스케일, 샤프닝 등등...
이 중 커널을 이용한걸 해보겠음
그래픽스 - 블러, 블룸
여기서 kernel에 대해 설명함ㅇㅇ
kernel은 간단하게 말하면
현재 픽셀이 중앙에 오는 행렬을 이용해서
주변 픽셀의 색상에 값을 더하고 곱하고 별짓을 하여 만드는 특수한 필터임
fragment shader에서 수행하면 끝인거임ㅇㅇ
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D screenTexture;
const float offset = 1.0 / 300.0;
void main()
{
vec2 offsets[9] = vec2[](
vec2(-offset, offset), // top-left
vec2( 0.0f, offset), // top-center
vec2( offset, offset), // top-right
vec2(-offset, 0.0f), // center-left
vec2( 0.0f, 0.0f), // center-center
vec2( offset, 0.0f), // center-right
vec2(-offset, -offset), // bottom-left
vec2( 0.0f, -offset), // bottom-center
vec2( offset, -offset) // bottom-right
);
float kernel[9] = float[](
-1, -1, -1,
-1, 9, -1,
-1, -1, -1
);
vec3 sampleTex[9];
for(int i = 0; i < 9; i++)
{
sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
}
vec3 col = vec3(0.0);
for(int i = 0; i < 9; i++)
col += sampleTex[i] * kernel[i];
FragColor = vec4(col, 1.0);
}
기본 예시로 sharpen 커널을 이용한거임
offset은 간단하게 수행되는 색상계산의 세기로
값이 작아지면 더 좁은 범위의 픽셀만 계산 -> 필터 강도 심해짐
값이 커지면 더 넓은 범위 픽셀 계산 -> 필터 강도 약해짐
| offset : 1/3000 | offset : 1/300 |
|---|---|
![]() | ![]() |
이런 결과가 나옴
float kernel[9] = float[](
1.0 / 16, 2.0 / 16, 1.0 / 16,
2.0 / 16, 4.0 / 16, 2.0 / 16,
1.0 / 16, 2.0 / 16, 1.0 / 16
);
위 코드에서 kernel만 수정해주면 됨
극단적으로 offset을 1/30으로만 했을때

이렇게 심한 블러링이 됨
float kernel[9] = float[](
1, 1, 1,
1, -8, 1,
1, 1, 1
);

ㅇㅇ끄읕~~