눈팅하는 게임개발자 블로그

OpenGL Advanced 5-11 Anti aliasing 본문

공부한거/OpenGL

OpenGL Advanced 5-11 Anti aliasing

Palamore 2020. 12. 12. 19:09

원문 사이트

learnopengl.com/Advanced-OpenGL/Anti-Aliasing

 

LearnOpenGL - Anti Aliasing

Anti Aliasing Advanced-OpenGL/Anti-Aliasing Somewhere in your adventurous rendering journey you probably came across some jagged saw-like patterns along the edges of your models. The reason these jagged edges appear is due to how the rasterizer transforms

learnopengl.com

번역 사이트

gyutts.tistory.com/160?category=755809

 

Learn OpenGL - Advanced OpenGL : Anti Aliasing

link : https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing Anti Aliasing 아마 렌더링을 하다보면 당신의 모델의 가장자리를 따라 톱니 모양의 패턴을 보게 될 것이다. 이 들쭉날쭉한 가장자리가 나타나는..

gyutts.tistory.com

Anti Aliasing

아마 렌더링을 하다보면 모델의 가장자리를 따라 톱니 모양의 패턴을 볼 수 있을 것이다.

이 들쭉날쭉한 가장자리가 나타나는 이유는 래스터라이저가 정점 데이터를 장면 뒤의 실제 조각으로 변환하는 방법 때문이다.

이 들쭉날쭉한 가장자리가 어떻게 생겼는지 보여주는 예제는 다음과 같다.

바로 볼수는 없지만 큐브의 가장자리를 면밀히 살펴보면 지그재그 패턴을 볼 수 있다. 

확대하면 다음과 같다.

이는 분명히 응용 프로그램의 최종 버전에서 품질을 떨어트리는 요인 중 하나이다.

가장자리가 구성하는 픽셀 형성을 명확하게 보는 이 효과를 Aliasing이라고 한다.

anti aliasing 기술 이라고 하는 꽤 많은 기술이 있어서 aliasing 동작이 정확하게 매끄러운 가장자리를 생성하도록

한다.

처음에는 Super Sample Anti Aliasing(SSAA)라는 기술을 사용해 장면을 렌더링하는 데 훨씬 높은 해상도를 임시로

사용하고, 시각적 출력이 프레임 버퍼에서 업데이트되면 해상도가 정상으로 다시 다운 샘플링해 해결했다.

이 여분의 해상도는 이러한 들쭉날쭉한 가장자리를 방지하는 데 사용되었다.

앨리어싱 문제에 대한 해결책을 제공했지만 평소보다 많은 조각을 그려야했기 때문에 성능에 큰 단점이 있었다.

그러므로 이 기술은 그리 큰 매력을 가지지 못했고, 길게 사용되지 못했다.

 

이 기술은 SSAA의 개념에서 차용한 MSAA(Multisample antil-aliasing)또는 더 효율적인 접근 방식을 구현하는

보다 현대적인 기술을 탄생시켰다. 이 강좌에서는 OpenGL에 내장된 이 MSAA 기술에 대해 광범위하게 다룰 것이다.

 

Multisampling

멀티 샘플링이 무엇인지, 앨리어싱 문제를 해결하는 방법을 이해하려면 먼저 OpenGL의 래스터라이저의 내부 동작을

더 깊이 파고 들어야 한다.

 

래스터라이저는 최종 처리된 vertex shader와 fragment 쉐이더 사이에 있는 모든 알고리즘과 프로세스의 조합이다.

래스터라이저는 단일 프리미티브에 속하는 모든 정점을 가져와서 이를 fragment set로 변환한다.

정점 좌표는 이론적으로 좌표를 가질 수 있지만 fragment는 창 해상도에 구속되므로 절대 좌표를 지정할 수 없다.

정점 좌표와 fragment 사이에 일대일 매핑이 거의 존재하지 않으므로 래스터라이저는 각 특정 정점이 어떤 조각/화면

좌표에 어떤 식으로 매칭될 지를 결정해야 한다.

여기서 각 픽셀의 중심에는 픽셀이 삼각형으로 덮여 있는지를 결정하는데 사용되는 샘플 점이 들어있는 스크린 픽셀 격자가 있다.

빨간색 샘플 포인트는 삼각형으로 덮여 있으며 해당 커버리지 픽셀에 대해 프래그먼트가 생성된다.

삼각형 모서리의 일부분이 여전히 특정 화면 픽셀을 입력하더라도 픽셀의 샘플 점은 삼각형 안쪽에 포함되지 않으므로

해당 픽셀은 프래그먼트 쉐이더의 영향을 받지 않는다.

 

아마 이것만으로도 aliasing이 어떻게 발생하는지를 파악할 수 있을 것이다.

삼각형의 렌더링된 전체 버전은 화면에 다음과 같이 표시된다

화면 픽셀의 제한된 양 때문에 일부 픽셀들은 가장자리를 따라 렌더링되고, 일부 픽셀들은 그렇게 되지 않는다.

그 결과 위에서 보았던 들쭉날쭉한 가장자리를 발생시키는 부드럽지 않은 가장자리를 가진 프리미티브를 렌더링하고 있다.

멀티 샘플링은 삼각형의 범위를 결정하기 위해 단일 샘플링 포인트를 사용하지 않고 여러 샘플 포인트를 사용한다.

각 픽셀의 중앙에 단일 샘플 지점 대신에 4개의 하위 샘플을 일반적인 패턴으로 배치하고,

이를 픽셀 범위를 결정하는 데 사용한다.

이는 컬러 버퍼의 크기가 픽셀 당 사용하는 하위 샘플 수만큼 증가한다는 것을 의미한다.

해당 이미지의 왼쪽은 보통 삼각형의 범위를 결정하는 방법을 보여준다.

이 특정 픽셀은 샘플 포인트가 삼각형으로 덮여있지 않기 때문에 조각 쉐이더를 실행하지 않으므로 빈 상태로 유지된다.

이미지의 오른쪽에는 각 픽셀에 4개의 샘플 포인트가 포함된 멀티 샘플링 버전이 표시된다.

여기서 샘플 점 중 단지 2점이 삼각형 내부에 포함된다는 것을 알 수 있다.

당연히 샘플 포인트의 양이 많아지면 더 정밀하게 렌더링하는데 도움된다.

멀티 샘플링이 흥미로운 부분이다. 2개의 서브 샘플이 삼각형으로 덮여 있으므로 다음 단계는 이 특정 픽셀의 색상을 결정하는 것이다.

실행 방법을 예상해보자면 각 커버된 서브 샘플에 대해 프래그먼트 쉐이더를 실행한 후에 픽셀당 각 서브 샘플의 색상을 평균화 하는 것이다.(보간)

이 경우 각 하위 샘플의 보간된 정점 데이터에서 fragment 쉐이더를 두 번 실행하고(비용이 두배로 든다.), 그 샘플 지점에 결과 색상을 저장한다.

이는 다행스럽게도 MSAA의 기본 작동 방식이 아니다.

이는 기본적으로 멀티 샘플링 없이 많은 조각 쉐이더를 실행해야 한다는 것을 의미하기 때문에 성능이 크게 저하된다.

 

MSAA가 실제로 작동하는 방식은 fragment shader가 삼각형이 덮고 있는 서브 샘플 수에 관계없이 각 픽셀에 대해 한 번만 실행된다는 것이다.

fragment shader는 픽셀 중심에 보간된 정점 데이터와 함께 실행되고 결과 색상은 덮여있는 각 하위 샘플 내부에 저장된다.

컬러 버퍼의 서브 샘플이 렌더링된 프리미티브의 모든 색상으로 채워지면 모든 색상이 픽셀당 평균화되어 픽셀당 하나의 색상이 된다.

4개의 샘플 중 2개만 이전 이미지에서 다뤄졌기 때문에 픽셀의 색상은 삼각형의 색상과 다른 2개의 샘플 포인트에 저장된 색상으로 평균화되어 연한 파란색을 띄게 된다.

결과는 모든 기본 가장자리가 더 매끄러운 패턴을 생성하는 색상 버퍼이다.

이전 삼각형의 범위를 다시 결정할 때 멀티 샘플링이 어떻게 생겼는가를 확인해보자.

여기서 각 픽셀에는 파란색 하위 샘플이 삼각형으로 덮여있고 회색 샘플 점이 아닌 4개의 하위 샘플이 포함된다.

삼각형의 내부 영역 내에서 모든 픽셀은 fragment shader를 한번 실행하고 색상 출력은 모든 4개의 하위 샘플에

저장된다. 삼각형의 가장자리에서 모든 서브 샘플이 커버되지는 않으므로 fragment shader의 결과는 일부 서브 샘플에만 저장된다.

커버된 서브 샘플의 양에 기초해서 결과적인 픽셀 컬러는 삼각형 컬러 및 다른 서브 샘플의 저장된 컬러에 의해 결정된다.

 

기본적으로 더 많은 샘플 포인트가 삼각형내부에 포함될수록 최종 픽셀 색상은 삼각형의 색상이 된다.

이전에 멀티 샘플링되지 않은 삼각형을 사용한 것처럼 픽셀 색상을 채우면 다음 이미지가 생성된다

각 픽셀에 대해 삼각형의 일부가 적은 서브 샘플일수록 이미지에서 확인할 수 있는 것처럼 삼각형의 색상을 덜 취한다.

삼각형의 가장자리는 이제 실제 가장자리 색상보다 약간 가벼운 색상으로 둘러싸여 먼 거리에서 보았을 때

가장자리가 부드럽게 나타난다.

 

색상 값은 멀티 샘플링의 영향을 받을 뿐만 아니라 depth 및 stencil 테스트에서도 여러 샘플 포인트를 사용한다.

depth test를 위해 이를 수행하기 전에 각 서브 샘플에 정점의 깊이 값을 보간하고 stencil test를 위해 픽셀 당 대신

서브 샘플 당 stencil 값을 저장한다. 이는 현재 depth와 stencil 버퍼의 크기가 픽셀 당 서브 샘플의 양에 의해 증가된다는 것을 의미한다.

 

지금까지 살펴본 내용은 멀티 샘플 안티 앨리어싱이 내부에서 어떻게 작동하는지에 대한 기본적인 개요이다.

래스터라이저 뒤에 있는 실제 논리는 여기에서 설명한 것보다 약간 복잡하지만

MSAA의 개념과 논리를 위와 같이 이해할 수 있어야 한다.

 

MSAA in OpenGL

OpenGL에서 MSAA를 사용하려면 픽셀 당 하나 이상의 색상 값을 저장할 수 있는 색상 버퍼를 사용해야 한다.

따라서 주어진 양의 멀티 샘플을 저장할 수 있는 새로운 유형의 버퍼가 필요하며 이를 멀티 샘플 버퍼라고 한다.

 

대부분의 윈도우 시스템은 기본 색상 버퍼 대신 다중 샘플 버퍼를 제공한다.

GLFW 또한 이 기능을 제공한다. GLFW는 창을 만들기 전에 glfwWindowHint를 호출해 일반 색상 버퍼 대신

N샘플을 사용하는 멀티 샘플 버퍼를 사용하고자 한다.

glfwWindowHint(GLFW_SAMPLES, 4);

이제 glfwCreateWindow를 호출하면 화면당 4개의 하위 샘플이 포함된 색상 버퍼가 있는 렌더링 창이 생성된다.

또한, GLFW는 픽셀당 4개의 하위 샘플을 사용해 depth 및 stencil 버퍼를 자동으로 생성한다.

이는 모든 버퍼의 크기가 4씩 증가한다는 것을 의미한다.

 

GLFW에 멀티 샘플 버퍼를 요청했으므로 glEnable을 호출하고 GL_MULTISAMPLE을 활성화해 멀티 샘플링을 활성화해야 한다.

대부분의 OpenGL 드라이버에서 멀티 샘플링은 기본적으로 활성화되어 있으므로 이 호출은 약간 중복되지만

대개의 경우 활성화하는 것이 좋다. 이 방법으로 모든 OpenGL 구현은 멀티 샘플링이 가능하다.

glEnable(GL_MULTISAMPLE);

기본 프레임 버퍼에 멀티 샘플링된 버퍼 첨부 파일이 있으면 멀티 샘플링을 사용하려면 glEnable을 호출하면 된다.

실제 멀티 샘플링 알고리즘은 OpenGL 드라이버의 래스터라이저에 구현되었기 때문에 직접 해줄 일은 별로 없다.

이제 이 강좌 초반의 녹색 큐브를 렌더링한다면 더 매끄러운 가장자리를 확인할 수 있다.

이 큐브는 실제로 더 부드럽게 보이고 정면에서 그리는 다른 모든 객체에도 동일하게 적용된다.

 

Off-screen MSAA

GLFW가 멀티 샘플 버퍼 생성을 담당하기 때문에 MSAA를 사용하는 것은 매우 쉽다.

그러나 자신의 프레임 버퍼를 직접 사용하고 싶다면, Off-screen 렌더링을 위해 멀티 샘플 버퍼를 생성해야 한다.

이제 다중 샘플 버퍼를 생성한다.

 

프레임 버퍼용 첨부 파일로 작동하는 멀티 샘플 버퍼를 만들 수 있는 두 가지 방법이 있다.

텍스처 첨부 파일과 렌더 버퍼 첨부 파일이다.

프레임 버퍼 강좌에서 설명한 것처럼 일반적인 첨부 파일과 유사하다.

 

Multisampled texture attachments

여러 샘플 포인트의 저장을 지원하는 텍스처를 생성하기 위해서 glTexImage2D 대신

GLTexImage2DMultisample을 사용한다.

GL_TEXTURE_2D_MULTISAMPLE을 텍스처 타겟으로 설정한다.

glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);

이제 두 번째 인수는 텍스처에 포함시킬 샘플 수를 설정한다, 마지막 인수가 GL_TRUE라면 이미지는

동일한 샘플 위치와 각 텍셀에 대해 동일한 수의 하위 샘플을 사용한다.

 

멀티 샘플링된 텍스처를 프레임 버퍼에 첨부하기 위해 glFramebufferTexture2D를 사용했지만

이번에는 GL_TEXTURE_2D_MULTISAMPLE을 텍스처 유형으로 사용했다.

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);

현재 바인드되고 있는 프레임 버퍼에는 텍스처 샘플의 형식으로 멀티 샘플링된 컬러 버퍼가 포함되어 있다.

 

Multisampled renderbuffer objects

텍스처와 마찬가지로 다중 샘플 렌더 버퍼 객체를 만드는 것도 어렵지 않다.

변경할 필요가 있는 것은 glRenderbufferStorage에 대한 호출인데,

glRenderbufferStorageMultisample 렌더 버퍼의 메모리 저장소를 지정하는 것은 매우 쉽다.

glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);

여기서 변경된 한가지는 renderbuffer 대상 다음에 추가 매개 변수를 지정하는 것으로 이 예제에서는 4개의 샘플을 설정한다.

 

Render to multisampled framebuffer

멀티 샘플링된 프레임 버퍼 객체에 대한 렌더링은 자동으로 진행된다.

프레임 버퍼 객체가 바인딩되어 있는 동안 무언가를 그릴 때마다 래스터라이저는 모든 멀티 샘플 작업을 처리한다.

그 다음 멀티 샘플링된 컬러 버퍼 또는 깊이 및 스텐실 버퍼로 끝난다.

다중 샘플 버퍼는 약간 특수한 것이기 때문에 버퍼 이미지를 쉐이더에서 샘플링하는 것과 다른 작업에 직접 사용할 수는 없다.

 

멀티 샘플링된 이미지에는 일반 이미지보다 훨씬 많은 정보가 포함되어 있으므로 이미지를 축소하거나 해결해야 한다.

멀티 샘플링된 프레임 버퍼를 해결하는 것은 일반적으로 glBlitFramebuffer를 통해 이루어지며

이는 한 프레임 버퍼에서 다른 프레임 버퍼로 영역을 복사하는 동시에 멀티 샘플링된 버퍼를 해결한다.

 

glBiltFramebuffer는 4개의 스크린 공간 좌표로 정의된 주어진 소스 영역을 4개의 스크린 공간 좌표로 정의된 주어진 대상 영역으로 전송한다.

프레임 버퍼 강좌에서 GL_FRAMEBUFFER에 바인드하면 프레임 버퍼 대상을 모두 읽고 바인딩할 수 있다는 것을 기억할 것이다.

프레임 버퍼를 GL_READ_FRAMEBUFFER 및 GL_DRAW_FRAMEBUFFER에 각각 바인딩해 개별적으로 대상에 바인딩할 수도 있다.

glBlitFramebuffer 함수는 소스와 대상 프레임 버퍼를 결정하기 위해 두 대상으로부터 읽는다.

그 다음 이미지를 기본 프레임 버퍼에 blitting하여 다중 샘플 프레임 버퍼 출력을 실제 화면으로 전송할 수 있다.

glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO); 
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); 
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);

그 다음 응용 프로그램을 렌더링할 경우 프레임 버퍼가 없는 경우와 동일한 출력을 얻을 수 있다.

어째선지 코드에 grayscale이 적용되어 있다.

그러나 다중 샘플 프레임 버퍼의 텍스처 결과를 post-processing과 같은 작업에 사용하려면 어떻게 해야 할까?

fragment shader에서 멀티 샘플 텍스처를 직접 사용할 수는 없다.

할 수 있는 것은 멀티 샘플링되지 않은 버퍼를 다른 멀티 샘플링된 텍스처가 첨부된 FBO에 blit 시키는 것이다.

그 다음 이 일반 색상 첨부 텍스처를 post-processing에 사용해 멀티 샘플링을 통해 렌더링된 이미지를 효과적으로

후처리한다.

이는 다중 샘플 버퍼를 fragment shader에서 사용할 수 있는 일반적인 2D 텍스처로 분해하기 위해

중간 프레임 버퍼 객체로만 작동하는 새로운 FBO를 생성해야 함을 의미한다.

이 프로세스의 의사코드는 다음과 같다.

unsigned int msFBO = CreateFBOWithMultiSampledAttachments(); 
// then create another FBO with a normal texture color attachment 
... 
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);
... 
while(!glfwWindowShouldClose(window)) { 
... 
glBindFramebuffer(msFBO); 
ClearFrameBuffer(); 
DrawScene(); 
// now resolve multisampled buffer(s) into intermediate FBO 
glBindFramebuffer(GL_READ_FRAMEBUFFER, msFBO); 
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO); 
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); 
// now scene is stored as 2D texture image, so use that image for post-processing 
glBindFramebuffer(GL_FRAMEBUFFER, 0); 
ClearFramebuffer(); 
glBindTexture(GL_TEXTURE_2D, screenTexture); 
DrawPostProcessingQuad();
... 
}

이를 프레임 버퍼 튜토리얼의 후 처리 코드에 구현하면 가장자리가 들쭉날쭉한 장면 텍스처에 모든 종류의 멋진 후처리

효과를 만들 수 있다.

grayscale 필터를 적용하면 다음과 같이 보일 것이다.

위에서 미리 해놨다.

화면 텍스처는 단 하나의 샘플 포인트만으로도 보통의 텍스처이기 때문에 모서리 감지와 같은 일부 사후처리 필터는
들쭉날쭉한 모서리를 다시 나타낸다. 이 문제를 해결하기 위해 나중에 텍스처를 흐리게 만들거나 고유한 안티 앨리어싱 알고리즘을 만들 수도 있다.

멀티 샘플링과 오프 스크린 렌더링을 결합할 때 몇 가지 추가 세부사항을 추가해야한다는 것을 알 수 있다.

멀티 샘플링을 사용하면 장면의 시각적 품질을 크게 향상시킬 수 있기 때문에 모든 세부사항을 추가로 테스트할 가치가 있다.

멀티 샘플링을 활성화하면 사용하는 샘플이 많을수록 응용 프로그램의 성능이 현저히 떨어질 수 있다.

이 강좌가 작성되는 시점에서 MSAA를 4개 샘플과 함께 사용하는 것이 일반적으로 선호된다.

 

Custom Anti-Aliasing algorithm

멀티 샘플링된 텍스처 이미지를 먼저 해석하는 대신 쉐이더에 직접 전달하는 것도 가능하다.

그 다음 GLSL은 서브 샘플마다 텍스처 이미지를

샘플링할 수 있는 옵션을 제공하므로 커스텀 anit-aliasing 알고리즘을 만들 수 있다.

 

하위 샘플당 색상 값을 가져오려면 일반 sampler2D 대신에 텍스처 균일화 샘플러를 sampler2DMS로 정의해야 한다.

uniform sampler2DMS screenTextureMS;

texelFetch 함수를 사용하면 샘플당 색상 값을 검색할 수 있다.

vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3);  // 4th subsample

커스텀 안티 앨리어싱 기법을 만드는 방법에 대해서는 자세히 알아보지는 않지만 이와 같은 기능을 구현하는 방법에

대한 지침만을 제공한다.