◾ Multisample anti-aliasing (MSAA)
Anti-aliasing
- edge가 픽셀에 걸쳐졌을 때, 픽셀 중심의 한 점을 기준으로 컬러를 결정하며 생기는 계단효과를 없애는 것
- 경계 부분에서 각 픽셀마다 여러개의 점을 sampling하고, sample들의 픽셀값을 평균내서 컬러 결정
- OpenGL에서 MSAA를 지원하지만, 개발자가 크게 관여할 순 없고 활성화/비활성화 및 sample 개수 정도만 설정할 수 있음 (하지만 속도가 느림)
- window system API와 관련 (GLFW)
- 시스템마다 지원해주는 최대 샘플 개수가 다를 수 있다 (확인 가능)
- sampling했다고해서 sample 개수만큼 fragment shader가 실행되지 않음
- 원래는 배경에 해당하는 픽셀은 fragment shader가 적용되지 않는데, MSAA에서는 배경에 해당하는 픽셀이라도 그 픽셀의 sample이 오브젝트 안에 있다면 fragment shader가 적용된다
- but, 오브젝트 안에 있는 sample은 픽셀의 중앙이 아니지만, fragment shader의 input인 vertex shader의 output은 각 버텍스의 값을 픽셀의 중심을 기준으로 Interpolation하여 넘겨주기 때문에.. 픽셀 중심을 기준으로 한 값을 가지게 됨
-> Polygon 바깥의 값을 가지게 되며 아래와 같은 문제가 생길 수 있음
- sample의 위치과 fragment shader의 값이 계산되는 위치가 일치하지 않아 생기는 문제
centroid/sample qualifier
centroid qualifier
- polygon 안쪽을 기준으로 interpolation 해라
centroid out vec2 TexCoord;
centroid in vec2 TexCoord;
sample qualifier
- sample을 기준으로 interpolation 해라
- 각 sample마다 fragment가 실행되야하므로 느림
sample out vec2 TexCoord;
sample in vec2 TexCoord;
- 퀄리티는 centroid < sample
- 속도는 centroid > sample
◾ Deferred shading
- 첫 pass에서 렌더링 후, depth test를 통과한 geometry 정보(color, normal, pos등)를 texture 형태로 저장 (Geometry buffer에 저장
GBuffer
)
- 이후 pass에서 GBuffer의 데이터만을 사용하여 lighting/shading 연산
- 보이는 부분에 대해서만 정보가 저장되므로 연산 절약
code
1. OpenGL Application 세팅 과정
- FBO 생성 (이 예제에서는 1pass, 2pass의 결과를 한 FBO에 모두 담는다..)
- Position, Normal, Color 버퍼 생성 및 FBO에 연결 (프레임버퍼에 채널을 지정해서 저장)
-> 1pass에서 저장할 3개의 정보를 3개의 texture에 담기 위해 3개의 output
- fragment output을 관리하는 배열에 GL-NONE(2pass 결과)을 포함한 3개의 프레임버퍼 채널을 등록하여 output 설정)
2. Fragment shader
- pass1에서는 geometry 정보를 내보내 texture에 저장
- pass2에서는 그 texture를 받아 texture로부터 geometry 정보를 읽어온다!!
- 원래는 pos, normal 정보를 vertex shader에서 변환된 output을 받아서 사용하는데, deferred shading에서는 texture에서 정보를 받아옴
3. OpenGL Application 렌더링 과정
- pass1: FBO 바인딩 및 depth test 활성화, uniform변수 넘겨주기, 오브젝트 그리기
- pass2: default FBO로 설정하고 depth test 비활성화, 쿼드 그리기
👀 전체 흐름... (보라색 선)
- deferred shading으로 Multi-sample anti-aliasing 가능
GL_TEXTURE_2D_MULTISAMPLE
- 텍스처로 저장된 depth 정보를 활용하여 이미지 프로세싱 가능
(depth of field(=depth blur), screen space ambient occlusion, volumetric particels, ...)
- but, 보이는 것만 계산하기 때문에 투명한 것 다룰 수 X (blending, transparency)
◾ Screen space ambient occlusion
Ambient occlusion
- Ambient light는 환경광으로 주변에서 균일하게 들어오는 빛
- 하지만 가려진다면 (occlusion) 상대적으로 적은 빛을 받아 어두워진다
(아래 그림의 화살표를 반대로 생각하자..)
- 광원 없이 ambient occlusion만 사용해서 렌더링하면, 사방으로 균일하게 빛이 들어와 평평할수록 밝게, occlusion있는 곳은 어둡게 나타난다
- 이는 형태로만 결정되는 값이라 모양이 변화하지 않으면 바뀌지 않아서 미리 계산해서 만들어 둘 수 있다
ambient occlusion factor
- 가려진 정도에 따라 ambient occlusion factor 설정
- 반구로 들어오는 빛 중 반이 가려졌다면 0.5정도..
- 한 점에서 사방으로 ray를 쏴서(반사벡터) 만나는 점이 있으면 occlusion
- 물체가 많고 복잡하면 계산이 많아져 real time에는 적절하지 않다..
- 하지만 물체가 static하다면 미리 계산 가능! (그래도 많다 ㅠ,ㅠ)
Screen Space Ambient Occlusion (SSAO)
- ambient occlusion을 screen space에서 계산하여 근사
- SS는 depth test를 통과한 정보만 남아있어 모든 값을 계산하지 않기 때문에 계산 복잡도가 낮아 real time에 사용 가능
- 2D상에서 3D물체의 occlusion을 찾겠다
Approximation
- SS가 아닌 경우, 점P를 기준으로 ray를 쏴서 sample들이 보이는지 안보이는지 확인했다
- 위 그림에서 점들: 반구 위 sample들 (occlusion을 확인하기 위해 ray를 쏘는 방향에 있는 점..)
- 검정점: occlusion X
- 하얀점: occlusion O
- 특정 점이 카메라에서 보인다면, 그 점은 표면에서도 보인다고 가정한다면, 카메라에서 보이는 점은 occlusion 안된 검정점이고, 카메라에서 보이지 않는 점은 occlusion된 하얀점이다.
- 특정 점이 카메라에서 보이는지 확인하기 위해 depth 사용
- Sample점의 depth는 CC에서의 z값
- 그 위치에서 표면의 depth는 depth buffer에 저장된 값
- 두 값을 비교하여 표면보다 점이 아래에 있으면 occlusion이라고 판단 가능
Random point generation
- 점P를 기준으로한 반구의 sample들을 랜덤하게 뽑아야함
- 쉐이더 코드에선 랜덤 연산이 어려우므로, random point들의 set을 저장한 Random kernel을 생성
- 그렇다고 모든 point마다 같은 sample을 사용하면 패턴이 생길 수 있으니, normal을 기준으로 kernel을 회전시켜 사용한다
- normal기준 회전 또한 랜덤해야하므로,, random number texture를 만들어서 사용
- 반구 안에서 random point를 생성하는 함수로 kernSize개의 sample을 뽑아 position값을 kernel에 넣는다
- sample들은 기준 점과 가까운 곳에 많이 있을 수 있도록 weighting하는 과정 존재
- 생성한 random kernel을 random rotation하여 재사용
- 4x4의 아주 작은 텍스처 생성 (random rotation vector 포함)
Code
- 1pass
- 렌더링 및 g-buffer로 camera space로 변환된 데이터 저장
- 2pass
- 얼마나 가려졌는지 비율을 계산하여 각 픽셀마다의 AO factor 계산
- random kernel 반구의 z축이 표면의 normal이 되도록 point들의 좌표계 변환 필요
(1st pass에서의 normal(CC)을 기준으로한 tangent space로 random kernl을 옮겨주는 것)
- normal
n
과 randDir
을 사용해 bitangent와 tangent를 구한다 -> CC기준의 tangent space 생성
randDir
을 사용했기 때문에, normal 기준 랜덤 회전을 적용시킨 것
- CC로 변환하는 행렬 생성
- random kernel의 점들을 변환하고 projection 시켜 tangent coord를 얻는다
surfaceZ
와 samplePos.z
를 비교하여 Occlusion을 확인하고 비율을 따져 AOfactor 계산
- 3pass
- ambient occlusion 데이터의 튀는 값들을 제거하기 위해 simple blur 적용
- 3x3 unweighted average
- final pass
- ambient occlusion으로 라이팅 계산~
Comparisons
- 3pass에서 블러를 해준 다른 이유:
- pass2에서
RandTex
의 값을 가져올 때, texCoord에 randScale을 곱했는데, 이는 스크린 크기를 다 채울때까지 4x4 texture로 반복했다는 것
- 이로인해 규칙적인 artifacts가 생길 수 있어서 블러
- Without/with ambient occlusion
◾ Depth test
- depth가 더 작은 픽셀만 프레임버퍼에 그린다
- fragment shader까지 끝나면 depth test를 실행 -> 계산하는게 많다
Early depth test
- fragment shader 실행 전에 depth test
(vertex shader까지만 해도 depth는 미리 계산할 수 있으므로)
- OpenGL pipeline에 계속 early depth test 하라고 명령
- 하지만, fragment shader에서 depth값을 바꾸는 경우 사용할 수 없다 (ex-discard)
- fragment shader에서 depth는 수정하지만 early depth test의 장점을 갖고싶다면
- depth가 커지거나/작아지거나/바뀌지 않을 때만 빼고 early depth test를 해라라고 할 수 있음
◾ Order-dependent transparency
- depth test로 물체를 그리는 경우, 모든 물체가 불투명하면 문제없지만 투명한 물체가 있는 경우에는 올바르게 그릴 수 없음
- depth test하여 불투명한 물체를 먼저 그리고,
- depth test 없이 투명한 물체들을 카메라 기준으로 sorting해서 뒤에서부터 앞으로 그린다 (sorting은 OpenGL Application에서)
- 하지만, 카메라 달라질 때마다 매번 sorting하면 오래걸릴 수 있고, 물체/폴리곤 단위로 sorting이 애매하여 제대로 안되는 경우가 발생할 수 있음(왼-순서지킴, 오-순서안지킴)
◾ Order-independent transparency (OIT)
- sorting없이 투명한 오브젝트를 그린다 (그래도 GPU에서는 sorting함)
- fragment shader에서 픽셀 단위로 sorting
- 한 픽셀에 그려지는 fragment가 여러개일 때, 이 픽셀에서 그려지는 fragment들만 sorting
- 근데 이걸 GPU에서 계산하니까 모든 픽셀에 대해 병렬적으로 수행 가능!
- 각 픽셀 위치에 어떤 fragment가 그려질 것인지 list 설정
- 같은 픽셀 위치에 그려지는 fragment들을 sorting
-> 물체 단위가 아닌 픽셀 단위 soring
memory objects
- 3개의 memory object가 필요
- fragment shader에서 같은 위치에 있는 fragment 데이터를 읽고 쓰기위해 정보를 저장할 버퍼
- ex) 3x3 프레임버퍼에 대한 linked list
- atomic counter
- head pointer texture
- linked list들의 head pointer값을 가질 텍스처 버퍼
- screen size와 동일한 head-pointer texture
- linked list buffer
- 각 노드는 한 fragment에 해당
- 그 fragment의 color/depth값과 다음 fragment의 index값을 가짐
Implemention
OpenGL Application 세팅 과정
- 3개의 메모리 오브젝트와 초기화를 위한 clear Buffer 생성
- 매 씬마다(프레임마다) 메모리 오브젝트를 초기화해줘야하는데, head pointer texture를 초기화 하기 위해 사용할
clearBuf
clearBuf
에 headPtrTex
를 binding해서 초기화
Fragment shader
- early depth test를 하는 이유는, 어짜피 불투명한 물체는 미리 그려놨기 때문에 불투명한 물체에 가려진 투명한 물체는 그릴 필요가 없기 때문
- 1st pass
- 3D씬 그리기 (이 코드에서는 불투명 오브젝트 그리는건 스킵함)
- atomic counter의 값을 증가시키고, 현재 위치의 head pointer texture 값을 전 atomic counter값으로 수정
- linked list에 node 추가
- 1pass와 2pass 사이에서
- 모든 데이터가 다 쓰일 때까지 다음 pipeline으로 넘어가지 않도록, OpenGL Application에서 모든 데이터가 버퍼에 쓰여지는 것을 보장하는 함수 사용
(Linked list가 완성된 뒤에 2nd pass로 넘어가야한다는 뜻~)
- 2nd pass
- 현재 위치의 fragment에 해당하는 linked list만 사용하기 위해, 현재 위치의 head pointer값을 사용해 linked list의 head node에 접근
- 그 head node의 next값을 따라가며 linked list를 다른 배열에 copy (마지막 노드까지)
- depth에 따라 fragment insertion sort 진행
- 정렬된 fragment들을 하나씩 읽어오면서 배경 color와 mix를 반복해서(
color
recursive) 최종 컬러 계산
- 모든 fragment shader가 같은 버퍼를 병렬로 처리하기 때문에, 같은 위치에 동시에 node를 저장해서 충돌될 수 있다 -> 방지 필요
- 방지하면 느려질 수 밖에 없다 (수행이 멈춰지니까)
- 설계를 잘 해야한다!
걍 적어둔건데 아까워서
1st pass
- 3D씬 그리기
- 각 픽셀에 매핑될 fragment들의 linked list 만들기
2nd pass
- 모든 픽셀에 대해서 fragment shader로 수행시키기 위해 full screen quad 그리기
- fragment shader에서
- Head Pointer texture의 본인 자리의 head pointer값을 얻어와서
- 자기가 해당하는 위치의 정보를 가진 linked list buffer에 접근해서 fragment를 sorting
- fragment shader에서 linked list를 얻어 depth에 따라 fragment를 sorting
- sorting해서 큰것부터 작은순으로 그리고, blend