[Learn OpenGL 번역] 5-5. 고급 OpenGL - Framebuffers
Framebuffers
고급 OpenGL/Framebuffers
지금까지 우리는 여러가지 유형의 스크린 버퍼들을 사용해왔습니다. 컬러 값들을 작성하는 color buffer, 깊이 정보를 작성하기 위한 depth buffer, 마지막으로 특정한 조건에 의해 해당 fragment들을 폐기하는 stencil buffer가 있었습니다. 이러한 버퍼들을 결합한 것을
지금까지 우리가 수행했던 렌더링 작업들은 모두
Framebuffer에 대한 개념이 잘 이해가 가지 않으실 것입니다. 하지만 여러분의 scene을 여러가지 framebuffer로 렌더링하면 거울을 생성할 수 있고 멋진 전처리 효과들을 생성할 수 있습니다. 먼저 그들이 실제로 어떻게 동작하는지 다루고 그 후에 이러한 멋진 전처리 효과들을 구현해봄으로써 framebuffer를 사용할 것입니다.
Framebuffer 생성
OpenGL의 다른 객체들과 마찬가지로
unsigned int fbo;
glGenFramebuffers (1, &fbo);
이러한 객체 생성과 사용법은 여러번 봤었던 패턴이므로 그들의 함수들은 우리가 봐왔던 다른 객체들과 비슷합니다. 먼저 framebuffer 객체를 생성하고 바인딩하여 framebuffer를 활성화시킵니다. 그 후에 조작을 하고 framebuffer를 언바인딩합니다. framebuffer를 바인딩하기 위해
glBindFramebuffer (GL_FRAMEBUFFER, fbo);
GL_FRAMEBUFFER 타겟에 바인딩함으로써 이후에 나오는 모든 framebuffer 읽기, 작성 명령이 현재 바인딩된 framebuffer에 영향을 미칩니다. 또한 framebuffer를 GL_READ_FRAMEBUFFER나 GL_READ_FRAMEBUFFER 타겟에 바인딩하여 읽기, 작성 명령을 구분할 수도 있습니다. GL_READ_FRAMEBUFFER에 바인딩된 framebuffer는
불행히도, 여기까지만 해서는 아직 우리만의 framebuffer를 사용할 수 없습니다. framebuffer를 완전하게 만들기 위해 다음과 같은 요구사항을 만족해야합니다.
- 최소한 하나의 buffer(color, depth 혹은 stencil buffer)를 첨부해야 합니다.
- 최소한 하나의 color 첨부가 존재해야 합니다.
- 모든 첨부 buffer들은 완적해야 합니다(메모리가 할당).
- 각 buffer들은 샘플의 갯수가 같아야 합니다.
샘플이 무엇인지 몰라도 걱정하지 마세요. 나중의 강좌에서 다룰것입니다.
요구사항에 따르면 framebuffer에 대한 첨부할 것들을 생성하고 첨부해야 합니다. 모든 첨부들을 완료한 후에
if(glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
// 승리의 춤을 실행
이후의 모든 렌더링 작업들은 이제 현재 바인딩된 framebuffer에 첨부된 것들에 렌더링하게 됩니다. 우리의 framebuffer는 기본 framebuffer가 아니기 때문에 렌더링 명령들이 여러분의 윈도우창의 출력에 아무런 영향을 주지 않습니다. 이러한 이유에서 다른 framebuffer에 렌더링하는 것을 0
을 바인딩하여 다시 기본 framebuffer를 활성화 시켜야 합니다.
glBindFramebuffer (GL_FRAMEBUFFER, 0);
모든 framebuffer 작업을 완료하면 framebuffer 객체를 제거하는 것을 잊지 마세요.
glDeleteFramebuffers (1, &fbo);
완전히 생성되었는지를 확인하기전에 우리는 하나 이상의 것들을 framebuffer에 첨부해야 합니다.
Texture 첨부물
텍스처를 framebuffer에 첨부할 때 모든 렌더링 명령들은 마치 일반적인 color/depth 혹은 stencil buffer처럼 텍스처에 작성합니다. 텍스처를 사용하여 얻는 이점은 렌더링 작업의 결과가 텍스처 이미지로 저장되기 때문에 우리의 shader에서 쉽게 사용할 수 있다는 점입니다.
Framebuffer를 위한 텍스처를 생성하는 것은 일반적인 텍스처와 거의 비슷합니다.
unsigned int 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);
glTexParameter i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameter i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
여기에서 큰 차이점은 텍스처의 크기를 스크린 크기로 설정한다는 것과 텍스처의 data
파라미터에 NULL
을 집어넣는다는 것입니다. 이 텍스처에 대해 우리는 오직 메모리만 할당하고 실제로 채워넣진 않고 있습니다. 텍스처를 채우는 것은 우리가 framebuffer에 렌더링을 하면 수행도리 것입니다. 또한 우리는 어떠한 wrapping method나 mimapping을 신경쓰지 않고 있다는 것을 알아두세요. 대부분의 경우에 필요가 없기 때문입니다.
텍스처를 생성했으므로 이제 마지막 해야할 일은 이 텍스처를 실제로 framebuffer에 첨부하는 것입니다.
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
target
: 텍스처를 첨부할 타겟 framebuffer(draw, read 혹은 둘다)attachment
: 첨부할 첨부물의 유형. 지금 우리는 color 첨부물을 첨부하고 있습니다. 마지막에 붙은0
은 우리가 하나 이상의 color 첨부물을 첨부할 수 있다는 것을 암시합니다. 이는 나중에 다루도록 하겠습니다.textarget
: 첨부하기 원하는 텍스처의 유형texture
: 첨부할 실제 텍스처level
: Mipmap 레벨. 우리는0
으로 유지할것입니다.
Color 첨부물 외에 우리는 또한 depth, stencil 텍스처를 framebuffer 객체에 첨부할 수 있습니다. depth 첨부물을 첨부하기 위해 우리는 첨부물 유형을 GL_DEPTH_ATTACHMENT로 지정합니다. 텍스처의
Depth, stencil buffer를 하나의 텍스처로 만들어 첨부할 수도 있습니다. 그러면 텍스처의 각 32비트 값은 24비트의 depth 정보, 8비트의 stencil 정보로 이루어집니다. depth, stencil buffer를 하나의 텍스처로 첨부하기 위해 우리는 GL_DEPTH_STENCIL_ATTACHMENT를 사용하고 텍스처의 형식을 depth와 stencil 값을 결합한 것으로 설정합니다. 이 예제는 다음과 같습니다.
glTexImage2D (
GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0,
GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL
);
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
Renderbuffer 객체 첨부물
Renderbuffer 객체는 모든 렌더링 데이터들을 그들의 buffer에 아무런 텍스처 형식에 따른 변환 없이 직접적으로 저장할 수 있습니다. 따라서 좀 더 빠른 저장공간을 만들 수 있습니다. 하지만 renderbuffer 객체는 일반적으로 작성만 가능합니다. 따라서 그들로부터 데이터를 읽을 수는 없습니다.
데이터가 이미 자연적인 형식안에 들어있기 때문에 데이터를 작성하거나 단순히 데이터를 다른 buffer로 복사할 때 꽤 빠릅니다. 따라서 buffer 교환같은 작업들은 renderbuffer 객체를 사용할 때 빠릅니다.
Renderbuffer 객체를 생성하는 것은 framebuffer의 코드와 비슷합니다.
unsigned int rbo;
glGenRenderbuffers (1, &rbo);
그리고 비슷하게 renderbuffer를 바인딩하므로 이후의 모든 renderbuffer 작업들은 현재의 rbo에 영향을 미칩니다.
glBindRenderbuffer (GL_RENDERBUFFER, rbo);
Renderbuffer 객체가 일반적으로 작성만 가능하기 때문에 depth, stencil 첨부물로 사용됩니다. 대부분의 경우에 depth, stencil buffer로부터 값을 읽어올 필요가 없지만 depth, stencil testing을 할때엔 필요합니다. testing을 위해서는 depth, stencil 값들이 필요합니다. 하지만 이 값들을 sample할 필요는 없으므로 renderbuffer 객체는 이 경우에 아주 딱 맞습니다. 이 buffer들로부터 sampling하고 있지 않다면 renderbuffer 객체가 일반적으로 최적화를 위해 사용됩니다.
Depth, Stencil renderbuffer 객체를 생성하는 것은
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
Renderbuffer 객체를 생성하는 것은 텍스처 객체와 비슷합니다. 여기에서 우리는 internal format을 GL_DEPTH24_STENCIL8로 설정했습니다. 이는 depth, stencil buffer를 24비트와 8비트로 나눈다는 의미입니다.
마지막 남은 일은 실제로 renderbuffer 객체를 첨부하는 것입니다.
glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
Renderbuffer 객체는 여러분의 framebuffer 프로젝트에 최적화를 제공해줍니다. 하지만 renderbuffer 객체를 사용할 때와 텍스처를 사용할 때에 깨닫는게 중요합니다. 일반적인 규칙은 여러분이 특정 버퍼에서 데이터를 절대 sample할 필요가 없다면 그 특정 buffer에 renderbuffer를 사용하는 것이 현명합니다. 언젠간 특정 buffer로부터 color 값이나 깊이 값처럼 데이터를 sample해야 한다면 텍스처 첨부물을 사용해야 합니다.
텍스처에 렌더링
Framebuffer가 어떻게 동작하는지 알게되었으니 이제 사용해볼 시간입니다. 우리는 생성한 framebuffer 객체에 첨부된 color 텍스처에 scene을 렌더링할 것입니다. 그런 다음 이 텍스처를 화면을 가득채운 간단한 사각형에 그릴 것입니다. 시각적인 출력은 정확히 framebuffer 없이 수행했을 때와 동일합니다. 하지만 모든 것이 하나의 사각형 위에 그려집니다. 이제 이게 왜 유용한 것일까요? 다음 섹션에서 이 이유를 알아봅시다.
먼저 해야할 일은 실제 framebuffer 객체를 생성하고 바인딩 하는 것입니다. 이는 비교적 쉽습니다.
unsigned int framebuffer;
glGenFramebuffers (1, &framebuffer);
glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
그 다음 framebuffer에 color 첨부물로서 첨부할 텍스처 이미지를 생성합니다. 텍스처의 크기를 윈도우 창의 크기와 동일하게 설정하고 데이터를 지정하지 않습니다.
// 텍스처 생성
unsigned int texColorBuffer;
glGenTextures (1, &texColorBuffer);
glBindTexture (GL_TEXTURE_2D, texColorBuffer);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameter i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameter i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture (GL_TEXTURE_2D, 0);
// 현재 바인딩된 framebuffer 객체에 첨부
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);
또한 우리는 OpenGL이 depth testing(그리고 원한다면 stencil testing까지)을 할 수 있도록 해야하기 때문에 framebuffer에 depth(그리고 stencil) 첨부물도 추가해야 합니다. 우리는 오직 color buffer만 sampling할 것이기 때문에 이 목적에 renderbuffer를 생성할 수 있습니다. 특정 버퍼를 sample하지 않을 것이라면 이 것이 좋은 선택이라는 것을 기억하나요?
Renderbuffer 객체를 생성하는 것은 어렵지 않습니다. 기억해야할 것은 이 것을 depth 그리고 stencil 첨부물 renderbuffer 객체를 생성한다는 것입니다. internal format을 GL_DEPTH24_STENCIL8로 설정하여 우리의 목적에 대해 충분히 만족하는 옵션을 선택합니다.
unsigned int rbo;
glGenRenderbuffers (1, &rbo);
glBindRenderbuffer (GL_RENDERBUFFER, rbo);
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glBindRenderbuffer (GL_RENDERBUFFER, 0);
이 renderbuffer 객체에 충분한 메모리가 할당되면 이 renderbuffer를 언바운딩할 수 있습니다.
그런 다음 framebuffer를 완성하기 전의 마지막 단계로서 renderbuffer 객체를 framebuffer에 첨부합니다.
glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
그런 다음 마지막 평가로 이 framebuffer가 실제로 완성이 되었는지 확인하고 그게 아니라면 에러 메시지를 출력합니다.
if(glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer (GL_FRAMEBUFFER, 0);
그리고 이제 framebuffer를 언바운딩하여 우연히 잘못된 framebuffer에 렌더링되는 일이 발생하는 것을 방지합니다.
이제 framebuffer가 완성되었으므로 기본 framebuffer 대신에 이 framebuffer에 렌더링하는 것은 간단히 framebuffer 객체를 바운딩하기만 하면 됩니다. 이 후의 모든 렌더링 명령들은 현재 바운딩된 framebuffer에 영향을 미칩니다. 모든 depth, stencil 작업들은 가능하다면 현재 바인딩된 framebuffer의 depth, stencil 첨부물로부터 값을 읽습니다. 예를 들어 depth buffer를 빼먹었다면 모든 depth testing 작업들은 동작하지 않게 됩니다. 현재 바운딩된 framebuffer에 depth buffer가 없기 때문이죠.
그래서 하나의 텍스처에 scene을 그리기 위해 다음과 같은 단계를 거쳐야 합니다.
- 활성화된 framebuffer로서 바인딩된 새로운 framebuffer에 평상시대로 scene을 렌더링합니다.
- 기본 framebuffer를 바인딩합니다.
- 전체 화면에 맞게 늘린 사각형을 그리고 텍스처로 새로운 framebuffer의 color buffer를 사용합니다.
우리는 depth testing 강좌에서 사용했던 것과 동일한 scene을 그릴 것입니다. 하지만 이번에는 올드스쿨 컨테이너 텍스처를 사용합니다.
사각형을 그리기 위해 간단한 shader 세트를 생성할 것입니다. 우리는 어떠한 행렬 변환도 포함시키지 않을 것입니다. vertex 좌표만을 제공할 것이기 때문입니다. 이 vertex shader는 다음과 같습니다.
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
TexCoords = aTexCoords;
}
복잡한 것은 전혀 없습니다. 심지어 이 fragment shader는 더 기본적입니다. 오직 텍스처를 sample하기만 하기때문이죠.
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D screenTexture;
void main()
{
FragColor = texture(screenTexture, TexCoords);
}
사각형에 대한 VAO를 생성하고 설정하는 것은 여러분에 달려있습니다. framebuffer 과정의 반복은 다음과 같은 구조를 가지고 있습니다.
// 첫 번째 단계
glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
glClear Color (0.1f, 0.1f, 0.1f, 1.0f);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 지금은 stencil buffer를 사용하지 않습니다
glEnable (GL_DEPTH_TEST);
DrawScene();
// 두 번째 단계
glBindFramebuffer (GL_FRAMEBUFFER, 0); // 다시 기본값으로
glClear Color (1.0f, 1.0f, 1.0f, 1.0f);
glClear (GL_COLOR_BUFFER_BIT);
screenShader.use();
glBindVertexArray (quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture (GL_TEXTURE_2D, textureColorbuffer);
glDrawArrays (GL_TRIANGLES, 0, 6);
알아두어야 할 것이 몇가지 있습니다. 첫째, 우리가 사용하고 있는 각 framebuffer들은 그들의만 buffer 세트를 가지고 있기때문에
&nbps; 여러분이 아무런 결과를 얻지 못하였다면 디버그하면서 이 강좌의 관련된 섹션을 찾아보세요. 모든것이 성공적으로 수행되었다면 다음과 같은 결과를 볼 수 있습니다.
왼쪽은 depth testing 강좌에서 보았던 것과 정확히 동일한 결과입니다. 하지만 이번에는 사각형에 렌더링을 한것이죠. 이 scene을 wireframe으로 렌더링 한다면 하나의 사각형만 그려질 것입니다.
전체 소스 코드는 여기에서 확인할 수 있습니다.
그래서 이것이 왜 유용한 것일까요? 지금 우리는 하나의 텍스처 이미지로서 완전히 렌더링된 scene을 자유롭게 접근할 수 있기 때문에 fragment shader에서 흥미로운 효과들을 생성할 수 있습니다. 이러한 모든 효과들을 통틀어서
Post-processing
이제 하나의 텍스처에 전체 scene에 렌더링 되었으므로 텍스처 데이터를 조작하여 흥미로운 효과들을 생성할 수 있습니다. 이번 섹션에서 우리는 가장 많이 쓰이는 post-processing 효과들을 보여줄 것입니다.
가장 간단한 post-processing 효과들부터 시작해봅시다.
Inversion(반전)
Fragment shader에서 렌더링 출력의 각 컬러들에 대해 접근하여 이 컬러들을 반전시키는 것은 어렵지 않습니다. 우리는 screen 텍스처의 컬러를 얻어와 1.0
에서 이 값을 빼서 반전시킵니다.
void main()
{
FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}
Inversion은 비교적 간단한 post-processing 효과이지만 펑키한 결과를 만듭니다.
이제 전체 scene이 모두 반전된 컬러를 가지고 있습니다. fragment shader의 한줄에 의해서 말이죠. 아주 멋지죠?
Grayscale
또다른 흥미로운 효과는 scene의 모든 컬러에서 흰색, 회색, 검정색을 제외한 모든 색을 제거하여 전체 이미지를 graysacle 하는 것입니다. 이를 수행하는 쉬운 방법은 모든 컬러 컴포넌트를 얻어서 평균을 내는 것입니다.
void main()
{
FragColor = texture(screenTexture, TexCoords);
float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
FragColor = vec4(average, average, average, 1.0);
}
이 것은 꽤 좋은 결과를 생성합니다. 하지만 인간의 눈은 녹색에 예민하고 파란색에 덜 예민하므로 물리적으로 가장 정확한 결과는 weighted 채널을 사용해야 합니다.
void main()
{
FragColor = texture(screenTexture, TexCoords);
float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;
FragColor = vec4(average, average, average, 1.0);
}
여러분은 아마도 차이를 알아차리지 못했을 것입니다. 하지만 더 복잡한 scene에서는 이러한 weighted grayscaling 효과가 더욱 현실적인 효과를 냅니다.
Kernel 효과들
하나의 텍스처 이미지에 post-processing 하는 것에 대한 또다른 이점은 텍스처의 다른 부분으로부터 실제로 컬러 값을 샘플할 수 있다는 것입니다. 예를 들어 현재 텍스처 좌표 주변의 작은 영역을 가져올 수 있고 여러 텍스처 값들을 가져올 수도 있습니다. 그런 다음 이를 창의적인 방법으로 결합하여 흥미로운 효과를 낼 수 있습니다.
이 kernel은 8개의 둘러싸인 픽셀 값들을 취하고 이들을 2
로 곱합니다. 그리고 현재 픽셀에 -15
를 곱합니다. 이 예로들은 kernel은 기본적으로 주변의 픽셀들을 결정된 weight로 곱하고 현재 픽셀을 큰 음수 weight로 곱함으로써 밸런스를 맞춰줍니다.
1
이 나옵니다. 그들의 합산이 1
이 나오지 않는다면 이는 결과 텍스처 컬러가 원래의 텍스처 값보다 밝아지던지 어두워지던지 하는 것입니다.
Kernel들은 post-processing에 대해 아주 유용한 도구입니다. 사용하거나 실험하기 쉽고 많은 예제들을 온라인에서 찾아볼 수 있기 때문입니다. kernel을 지원하기 위해 우리는 frament shader를 약간 수정해야 합니다. 우리가 사용할 각 kernel은 3x3 kernel이라고 가정합니다(대부분이 kernel이 그렇습니다).
const float offset = 1.0 / 300.0;
void main()
{
vec2 offsets[9] = vec2[](
vec2(-offset, offset), // 좌측 상단
vec2( 0.0f, offset), // 중앙 상단
vec2( offset, offset), // 우측 상단
vec2(-offset, 0.0f), // 좌측 중앙
vec2( 0.0f, 0.0f), // 정중앙
vec2( offset, 0.0f), // 우측 중앙
vec2(-offset, -offset), // 좌측 하단
vec2( 0.0f, -offset), // 중앙 하단
vec2( offset, -offset) // 우측 하단
);
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);
}
Fragment shader에서 우리는 먼저 주변의 각 텍스처 좌표에 대한 9개의 vec2
offset의 배열을 생성합니다. 이 offset은 간단히 여러분이 원하는대로 정할 수 있는 상수 값입니다. 드런 다음 kernel을 정의 합니다. 이 경우에 이 kernel은
이 sharpen kernel은 다음과 같은 효과를 냅니다.
이 것은 약에 취해있는 것처럼 흥미로운 효과를 냅니다.
Blur
모든 값의 합산이 16이기 때문에 간단히 샘플링된 컬러들을 결합하면 매우 밝아지므로 kernel의 각 값들을 16
으로 나눕니다. 최종 kernel 배열은 다음과 같습니다.
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
);
Fragment shader에서 kernel
이러한 blur 효과는 흥미로운 가능성을 만듭니다. 예를 들어 술에 취한 효과를 내기 위해 시간이 지남에 따라 blur 정도를 바꿀 수 있습니다. 또는 메인 캐릭터가 안경을 쓰지 않았을 경우 blur 효과를 높일 수 있습니다. Blur는 우리가 나중에 강좌에서 사용할 컬러 값을 부드럽게 하는데에 유용한 도구가 될 수 있습니다.
우리는 이러한 작은 kernel을 구현함으로써 손쉽게 멋진 post-processing 효과를 낼 수 있었습니다. 마지막으로 효과 하나를 더 보여주고 마치도록 하겠습니다.
Edge detection
아래에서
이 kernel은 모든 모서리를 하이라이트하고 나머지들은 어둡게 만듭니다. 우리가 이미지의 모서리를 신경써야 할때 유용하게 사용할 수 있습니다.
이러한 kerenl들이 Photoshop과 같은 도구에서 이미지 조작 도구/필터로 사용된다는 것은 놀랄 일이 아닙니다. 병렬 기능으로 fragment들을 처리하는 그래픽 카드들의 능력 때문에 우리는 실시간으로 픽셀 마다 이미지를 조작할 수 있습니다. 그러므로 이미지 편집 도구들은 이미지 처리에 대해서 그래픽 카드를 더 자주 사용하는 경향이 있습니다.