원문 : http://www.cemyuksel.com/cyCodeBase/soln/poisson_disk_sampling.html


소스 코드 : https://github.com/cemyuksel/cyCodeBase/


논문 : http://www.cemyuksel.com/research/sampleelimination/


주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.


주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.



푸아송 디스크 샘플링은 컴퓨터 그래픽스와 다른 분야들에서 다양한 용도로 사용됩니다. 여기에서 필자는 cySampleElimination 코드 릴리스를 사용하여 모든 샘플링 문제를 위해 푸아송 디스크 샘플 집합을 생성하는 방법에 대해서 설명하고자 합니다.


1. What is Poisson Disk Sampling?


푸아송 디스크 샘플 셋( set )에서 두 개의 샘플들은 서로 가까워지지 않습니다. 가장 가까운 거리가 푸아송 디스크 반지름( radius )로 정의되는데, 이는 두 개의 가장 가까운 샘플들 사이의 거리의 절반입니다. 일반 랜덤( regular random ) 샘플링과 비교했을 때, 푸아송 디스크 샘플 셋들은 샘플링 영역( domain ) 전체에서 더 많은 균일한( uniform ) 샘플 분포( distribution )을 제공합니다.



1000 Random Samples                      1000 Poisson Disk Samples


위의 그림은 1000 개의 랜덤 샘플들과 1000 개의 푸아송 디스크 샘플들을 비교한 것입니다. 푸아송 디스크 샘플 점들은 샘플링 영역에서 더 고르게 분포되어 있으며, 너무 가까워지는 샘플 점들이 존재하지 않음을 알 수 있습니다. 하지만 랜덤 샘플들은 클러스터( cluster )를 생성하며, 샘플 사이에 상대적으로 큰 빈 공간들이 존재합니다. 이는 이 예제에서 사용된 특정 랜덤 넘버 생성기에 국한된 것은 아닙니다. 균일한( uniform ) 랜덤 샘플들은 고른 샘플 배치 확률( probability )을 제공하지만, 샘플링 영역에 대한 고른 커버리지( coverage )를 제공하지는 않습니다.


푸아송 디스크 샘플 셋들은 샘플 점들에 대한 훌륭한 분포를 제공하는 것 외에, 몬테 카를로( Monte Carlo ) 샘플링을 사용해 우수한 수렴( convergence ) 특성을 제공합니다. 같은 개수의 푸아송 디스크 샘플들은 일반적으로 랜덤 샘플링에 비해 더 적은 노이즈를 생성합니다.


2. Generating Poisson Disk Sample Sets


푸아송 디스크 샘플 셋들을 사용할 때 문제는 그것들을 생성하는 것이 눈에 띄게 어렵다는 것입니다. 왜냐하면 이 샘플들은 독립적으로 생성될 수가 없기 때문입니다; 각 샘플은 다른 샘플의 위치에 의존합니다. ( dart throwing 과 같은 ) 단순한 알고리즘들은 계산 비용이 비쌉니다. 계산적으로 효율적인 알고리즘들은 복잡하며 특정 샘플링 영역에 국한됩니다. 고차원( high-dimensional ) 샘플링은 6 차원이나 7 차원이 넘어 가는 것들에 대해서는 경험적으로 불가능합니다.


푸아송 디스크 샘플 셋을 생성하는 데 있어서 이런 모든 문제들은 필자가 2015 년에 소개한 weighted sample elimination 알고리즘에 의해 해결되었습니다. 이 알고리즘의 세부사항에 대해서는 다음 링크에서 찾아보실 수 있습니다: http://www.cemyuksel.com/research/sampleelimination/.


이 솔루션( solution )의 나머지 부분에서, 필자는 weighted sample elimination 알고리즘에 대한 기본 정보를 제공할 것이고 cySampleElimination 코드 릴리스에서 모든 차원의 샘플링 도메인을 위한 푸아송 디스크 샘플 셋들을 생성하기 위해서 소스 코드를 사용하는 방법에 대해서 설명할 것입니다.


3. Weighted Sample Elimination


Weighted sample elimination 은 푸아송 디스크 샘플 셋들을 생성하기 위한 간단한 알고리즘입니다. 이것의 추가적인 이점은 푸아송 디스크 반지름 속성을 지정할 필요가 없다는 것입니다. 그것의 입력은 ( 랜덤하게 생성된 ) 샘플 집합이며, 결과 샘플 셋이 푸아송 디스크 속성을 가지도록 이런 샘플들의 일부분을 제거합니다. 다시 말해, weighted sample elimination 알고리즘은 큰 집합의 입력 샘플들을 받아서 더 작은 집합의 출력 샘플들을 반환합니다. 새로운 샘플들을 생성하는 것이 아닙니다. 주어진 입력 집합으로부터 그냥 하위 집합을 선택하는 것입니다.


입력 샘플들은 모든 차원의 모든 샘플링 영역을 위해서 생성될 수 있습니다. 제공된 입력 샘플이 많을수록 더 좋은 품질의 푸아송 디스크 샘플 셋들을 얻을 수 있습니다만, 특정 비율을 넘어 서게 되면 결과가 나빠질 수 있습니다. 입력 집합의 크기를 출력 집합의 크기의 3 배를 넘도록 하는 것을 권장합니다. 필자는 일반적으로 출력 집합 크기보다 5 배 정도 큰 입력 집합을 사용합니다. 이것은 계산 속도와 샘플링 품질 사이의 좋은 균형을 제공합니다. 말하자면, 출력 샘플 집합 크기를 고려하지 않고 매우 작은 입력 샘플 집합을 사용하는 것은 좋지 않은 생각입니다. 예를 들어, 단지 10 개의 샘플들을 생성하려고 하는데, 50 개의 입력 샘플들을 사용하면 샘플 분포가 좋지 않을 것입니다. 이 경우에는, 입력 샘플 집합 크기보다 더 많은 샘플들을 사용해서 시작하는 것이 좋다고 생각합니다.


4. Using cySampleElimination


cySampleElimination 코드 릴리스는 어떠한 유형의 샘플 클래스 유형에 대해서도 사용가능한 템플릿 클래스를 포함하고 있습니다. 물론 cyPoint 코드 릴리스에 있는 클래스( 2D 를 위해  Point2, 3D 를 위해 Point3, 4D 를 위해 Point4, 임의의 차원을 위해 Point )들을 사용하도록 설계되어 있습니다.


첫 번째로 할 일은 입력 샘플들을 생성하는 것입니다. 1000 개의 출력 샘플들을 만들기 원한다고 가정합시다. 그러므로 우리는 5000 개의 입력 샘플들을 생성하는 것으로부터 시작할 것입니다. 이 샘플들을 생성하는 방법은 온전히 여러분과 여러분의 샘플링 문제에 달려 있습니다. 아래에 Point3f  를 사용해서 3D 에서 랜덤 샘플을 생성하는 간단한 코드를 삽입했습니다.



모든 샘플 값들은 0 에서 1 사이라는 점에 주의하세요. 이것은 중요합니다. 샘플링 영역이 다르다면 그것의 차원을 지정할 필요가 있습니다. 아래에서 이에 대해 다루겠습니다. 지금은 모든 샘플 값들을 0 에서 1 사이로 한다고 가정합니다.


Weighted sample elimination 을 사용하려면, 먼저 WeightedSampleElimination 클래스의 인스턴스를 생성할 필요가 있습니다. 



여기에서 템플릿 파라미터들은 샘플 클래스 타입( 이 경우에는 Point3f ), 부동 소수점 타입( float 혹은 double ), 포인트 타입의 차원성( 이 경우 3 ), 샘플 인덱스를 위한 타입( 이 경우 int )을 정의합니다. 2 십억 개가 넘는 샘플들을 가지고 작업하지 않는 이상에는 샘플 인덱스 타입은 int 가 적절할 것입니다.


생성자는 기본 파라미터들을 설정할 것입니다. 원한다면 수정해도 좋습니다. 아래에서 파라미터들에 대해서 설명하겠습니다. 지금은 기본값만으로도 충분하다고 간주하겠습니다. 푸아송 디스크 샘플 셋을 생성하려면,  WeightedSampleElimination 클래스의 Eliminate() 메서드를 호출할 필요가 있습니다.



됐습니다. 출력 점들은 푸아송 디스크 속성들을 가지게 될 것입니다. 우리가 푸아송 디스크 반지름을 지정할 필요가 없다는 것을 기억하십시오. 제거 기법은 몇 개의 파라미터들을 더 받을 수 있으며, 아래에서 설명하도록 하겠습니다.


5. Progressive sampling


샘플들이 하나씩 생성되는 것이 기대되는데 일단 원하는 속성에 도달하면 새로운 샘플들을 생성하는 것을 중지해야 하는 샘플링 문제가 있습니다. 이는 점진적( progressive ) 샘플링( 혹은 순응 샘플링( adaptive sampling ) ) 이라 불립니다. 이 Eliminate() 메서드는 progressive 인자를 true 로 설정함으로써 점진적 샘플을 쉽게 생성할 수 있게 해 줍니다.



이 경우 출력 집합에 있는 샘플들이 정렬되는데, 이 순서대로 샘플들이 생성되는 경우에, 시퀀스( sequence )에 있는 각 샘플들은 푸아송 디스크 샘플 셋이 됩니다. 아래의 애니메이션은 예제 시퀀스을 보여 줍니다.


Progressive Samples


6. Sampling Arbitrary Volumes


위에서 언급했듯이, 우리 예제 샘플 점들은 0 에서 1 사이의 값을 가집니다. 만약 다양한 영역을 샘플링하기 원한다면, 그 영역의 경계를 지정해야 합니다. 예를 들어, 샘플링 영역이 3D 로 된 박스라고 가정합시다. 여기에서 최소 경계는 (-2, -1, 0 ) 이고 최대 경계는 (2, 1, 1 )입니다. 이 경계들을 다음과 같이 설정할 수 있습니다.



이 경계들은 박스 모양의 영역을 지정한다는 것에 주목하십시오. 3D 상의 임의의 부피를 샘플링하고자 한다면, 그 경계들은 도움이 되지 않습니다. 대신에 Eliminate() 메서드의 d_max 인자를 직접 지정해야 합니다. d_max 인자는 요구된 출력 샘플 개수를 위해 샘플링 영역 내의 두 샘플들 간의 최대 거리를 지정합니다. 이 값은 2D 와 3D 에서는 잘 정의되어 있으며, 더 높은 차원에서는 근사계산될 수 있습니다. GetMaxPoissonDiskRadius() 메서드는 이 파라미터를 설정하는데 도움이 됩니다. d_max 인자는 이 메서드가 반환하는 반지름 값의 두 배여야 합니다.



GetMaxPoissonDiskRadius() 메서드의 파라미터들은 차원, 출력 샘플 크기, 샘플링 영역의 부피입니다. 만약 부피가 ( 기본값인 ) 0 으로 주어진다면, 그 부피는 경계들을 사용해서 계산됩니다. 그렇지 않으면 경계들은 완전히 무시됩니다.


7. Sampling Surfaces in 3D


Eliminate() 메서드의 마지막 인자는 3D 에서 서피스( surface )( 혹은 고차원 공간에서의 저차원 매니폴드( manifold ) )를 샘플링하기 위해서 사용될 수 있습니다. 먼저 입력 점들이 원하는 서피스 상에서 생성되어야 합니다. 그리고 나서 Eliminate() 메서드를 호출할 때, 올발른 d_max 인자와 샘플링 영역의 차원성을 지정해야 합니다. 우리 예제에서는 3D 상의 서피스를 샘플링하고 있기 때문에, 샘플링 영역은 2D 로 간주되어야 합니다.



이 경우에 area 값은 샘플링 영역의 전체 서피스 면적을 저장합니다.


WeightedSampleElimination 오브젝트가 3D 샘플러로서 생성( 3D 샘플 상에서 동작 )되었지만, 샘플링 영역을 2D 에 대한 차원성으로 지정했다는 것에 주목하십시오. 그 이유는 우리가 서피스를 샘플링하기 원했기 때문입니다. 즉, d_max 가 지정되고 점진적 샘플링이 사용되지 않았다면, 차원성을 지정하는 마지막 인자는 사용되지 않는다는 것입니다.


스탠포드 토끼 모델 상에 생성된 샘플들.


8. Tiling


가끔 샘플 셋을 생성한 다음에 어떤 영역에서 반복( tiling )시키고 싶을 때가 있습니다. 또한 파워 스펙트럼( power spectrum )을 활용하는 통계적 분석( statistical analysis )를 사용했을 때, 생성된 샘플 셋이 반복적이어야만 하는 경우가 있습니다. 반복 작업은 추가적인 계산을 포함합니다; 그러므로 그것은 기본값으로 꺼져 있습니다. SetTiling() 메서드를 사용해서 타일링을 켤 수 있습니다.


타일링이 꺼져 있다면, weighted sample elimination 은 샘플링 영역의 경계들 근처에서 더 많은 출력 샘플 집합들을 생성할 것입니다. 그러므로 박스 모양의 샘플링 영역을 위해서는, 샘플링 문제가 타일링을 포함하고 있지 않다고 해도, 타일링을 켜 두는 것이 좋습니다.


9. Adaptive Sampling


가끔은 영역의 다양한 영역에서 다양한 샘플링 밀도( density )를 가져야 할 때가 있습니다. Weight 함수를 적절하게 수정함으로써 이를 달성할 수 있습니다. 커스텀 weight 함수를 사용할 수 있다고 언급하는 것 외에 더 세부적인 정보를 제공하지는 않겠습니다. 이 커스텀 weight 함수는 더 듬성듬성하게( sparsely ) 샘플링되야 하는 영역보다 더 큰 값들을 반환해야 합니다. 그리고 더 조밀하게( denser ) 샘플링 분포가 생겼으면 하는 영역보다는 더 작은 값을 반환해야 합니다. 가장 듬성듬성하게 샘플링되는 영역을 위해서 커스텀 d_max 인자를 제공할 필요도 있습니다.


밀도( density ) 맵을 사용해서 생성된 샘플들.


10. Additional Parameters


기본 weight 함수는 세 개의 파라미터들을 가지고 있습니다; alpha, beta, gamma. alpha 파라미터는 weight 함수를 위해 사용되는 지수( exponent )입니다. 그것의 기본값은 8 입니다. 아마도 이 파라미터를 수정할 일은 없을 것입니다.


beta 파라미터는 weight limiting 의 양을 지정합니다. Weight limiting 은 더 높은 품질의 샘플 셋들을 제공할 수 있는 기법이지만, 공격적인( aggressive ) weight limiting 은 가끔 가깝게 배치된 샘플이 몇 개 밖에 생성되지 않도록 만들 수가 있습니다. 이것은 보통 원하지 않는 결과입니다. 기본적으로 weight limiting 은 0.65 로 설정되어 있는 beta 파라미터에 의존합니다. beta 파라미터의 ㄱ밧은 0 에서 1 까지입니다. 값이 커질수록 더 공격적인 weight limiting 이 제공되며 값이 작아질수록 weight limiting 의 요과가 줄어듭니다. beta 를 0 으로 설정하는 것은 weight limiting 을 끄는 것과 동일합니다.


마지막으로 gamma 파라미터는 weight limiting 의 한계를 입력 셋 크기와 출력 셋 크기의 상대적인 비율에 의존해 설정하기 위해 사용됩니다.


원래 논문에서의 이런 파라미터들의 세부사항들을 찾아 볼 수 있습니다. weighted sample elimination 에 대한 추가적인 정보를 원하신다면, 해당 프로젝트 페이지를 참고하시기 바랍니다: http://www.cemyuksel.com/research/sampleelimination/.

원문 : https://developer.nvidia.com/sites/default/files/akamai/gameworks/samples/DeinterleavedTexturing.pdf


샘플 페이지 : https://docs.nvidia.com/gameworks/content/gameworkslibrary/graphicssamples/d3d_samples/deinterleavedtexturingsample.htm


주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.


주의 : 허락받고 번역한 것이 아니므로 언제든지 내려갈 수 있습니다.



Louis Bavoil

lbavoil@nvidia.com


March 2014


Overview


이 DirectX 11 샘플은 Deinterleaved Texturing 접근법을 사용하여 큰, 저밀도( sparse )의, 지터링된( jittered ) 포스트 프로세싱( post processing ) 필터( 여기에서 SSAO 패스는 4x4 의 랜덤 텍스쳐를 사용합니다 )를 더욱 캐시-효율적으로 만들 수 있는 방법에 대해서 설명합니다. 먼저 전체 해상도 입력 텍스쳐를 16 개의 쿼터-해상도( quarter-resolution ) 텍스쳐의 배열로 재구성됩니다. 다음으로 패스 당 하나의 쿼터-해상도 텍스쳐를 소스로 사용하는 16 개의 개별 쿼터-해상도 패스들을 사용하여 필터를 렌더링합니다.


그림 1. Deinterleaved Texturing 은 픽셀당 랜덤화( randomization )을 사용하는 단일 패스보다 2.3 배가 더 빠르지만 결과는 매우 비슷합니다.


Deinterleaved Texturing


Introduction


우리가 속도를 올리려고 하는 포스트 프로세싱 필터들은 큰, 저밀도의, 지터링된 필터들입니다. 예를 들어 픽셀 단위의 디스크 샘플링( disk sampling, 역주 : 어떤 거리 r 을 이용하여 랜덤하게 임의의 점을 찍는 샘플링 기법입니다. Poisson disk-sampling 을 줄여서 disk-sampling 이라 부르는 것 같습니다 ) 과 랜덤 텍스쳐 좌표( randomized texture coordinate ) [McGuire et al. 2012 ]를 사용하는 SSAO 픽셀 셰이더가 있습니다. 이 접근법으로부터의 이점을 얻을 수 있는 알고리즘은 SSDO [Ritschel et al. 2009] 와 SSR [Kasyan et al.2011]입니다. 이 모든 알고리즘들은 구부러짐( bending )을 노이즈( noise )와 교환하기 위해서 텍스쳐 좌표를 랜덤화합니다. 필터의 원래 구현이 가진 성능이 주로 텍스쳐 지연에 의해서 제한되고 있다고 가정합니다. 우리의 목표는 품질을 희생시키지 않고 그런 큰 필터들의 속도를 개선하는 일반적인 접근법을 찾아내는 것입니다.


그림 2. 고정 샘플링 패턴.


그림 2 에 있는 예제 샘플링 패턴에서, 인접 픽셀들은 인접 샘플들을 페칭( fetching )합니다. 이 경우에, 각 픽셀들은 중심 픽셀에 대해 상대적으로 고정된 옵셋( offset ) 위치에 있는 4 개의 텍셀들을 수집합니다. 잠금 단계( lock step )에서 실행되고 있는 인접 픽셀들의 쌍을 보면( 픽셀들은 0 으로 표시되어 있습니다 ), 각 샘플들을 위한 샘플 좌표들은 서로에 대해 인접해 있습니다( 샘플들은 1, 2, 3, 4 로 표시되어 있습니다 ). 그러므로 이 샘플링 패턴은 공간적인 텍스쳐 지역성( locality )을 가지고 있으며, 이는 텍스쳐-캐시 하드웨어에 대해 친화적입니다( 역주 : 왼쪽 그림은 고정 크기 샘플링 패턴의 옵셋을 보여 줍니다. 오른쪽은 녹색 0 을 중심으로 샘플링을 한 후에, 노란색 0 을 중심으로 샘플링을 수행했을 때, 각각의 샘플링 텍셀들이 이전에 샘플링 했던 텍셀들과 인접해 있음을 보여 줍니다. 메모리상 위치가 가깝기 때문에 지역성이 높고 캐시 적중도( cache hit )가 높다고 하는 것 같습니다 ).


그림 3. (a) 랜덤 샘플링. (b) 영역( sectored ) 샘플링.


이제 샘플링 패턴에 대한 임의의 픽셀 단위 랜덤화를 사용하는 그림 3 (a) 의 2 개의 픽셀들을 살펴 봅시다. 2 개의 인접한 픽셀들을 위한 첫 번째 샘플의 족적( footprint )이 퍼져 있습니다. 이는 텍스쳐 하드웨어에서 비효율성을 발생시킬 것입니다. 이 경우에 인접한 픽셀들이 멀리 떨어져 있는 텍셀들을 페칭해야 하므로 공간적 지역성이 떨어집니다.


일반적인 전략은 완전히 랜덤화된 샘플링보다는 영역화된 떨리는 샘플링을 사용하는 것입니다. 이 경우에, 우리는 4 개의 샘플을 가지고 있으며 커널 영역을 네 개의 쿼터들로 하위분할할 수 있습니다. 좌상단 쿼터에서 샘플 1 을 취하며, 우상단 쿼터에서 샘플 2 를 취하는 식입니다. 이런 방식으로 잠금 단계에서 실행되고 있는 샘플들이 각 영역 안에서 서로 가깝게 존재하게 됩니다. 그러므로 그것들은 공간적 지역성이 높아집니다. 하지만 큰 커널들에 대해서는 인접 픽셀들이 여전히 그것들의 영역 안에서 멀리 떨어져 있는 텍셀들을 페칭하게 될 것이며 텍스쳐-캐시 적중율 문제를 겪게 될 것입니다.


Previous Art


단순한 전략은 혼합-해상도 입력들을 사용하고, 입력에 대한 고해상도와 저해상도의 버전을 둘 다 바인딩하는 것입니다. 그리고 :


  • 커널의 중심 탭( tap )을 위해 전체 해상도 텍스쳐를 사용합니다.
  • 분산된 멀리 떨어진 탭들을 위해, 저해상도 텍스쳐를 사용합니다.


다른 전략은 밉맵화( mip-mapped )된 입력 텍스쳐를 [McGuire et al.2012] 에 있는 것처럼 사용하여 현재 픽셀에서의 스텝( step ) 크기에 기반한 샘플링된 LOD 를 적용하는 것입니다. 이 방식을 사용하면, 인접 픽셀들은 같은 밉 레벨에 있는 서로에게 더 가까운 샘플들을 페칭할 것입니다.


이 두 전략들은 여전히 공간적 지역성의 관점에서 덜 최적화되어 있습니다. 왜냐하면 그것들은 밴딩( banding, 역주 : 띠같은 모양의 아티팩트 )을 피하기 위해서 여전히 픽셀당 지터링 메커니즘을 사용할 필요가 있기 때문입니다.


Our Approach


우리의 접근법은 단일 샘플링 패턴 당 하나의 저해상도 이미지를 렌더링하는 것입니다. 그것은 [Keller and Heidrich 2001], [Segovia et al. 2006], [Bavoil and Jansen 2013] 에서 나온 오래된 아이디어입니다.


그림 4. 2x2-interleaved 샘플링 패턴.


우리는 그림 4 의 왼쪽에 나온 2x2 크기의 interleaved 샘플링 패턴에서부터 시작했습니다( 역주 : 신호처리에서 interleaving 은 비트를 일정한 단위로 재배열하여 전송하는 것을 의미합니다 ). 그리고 하나의 개별 패스에서 녹색 픽셀들을 모두 함께 처리합니다( 그것은 녹색 샘플링 패턴을 가집니다 ). 그리고 나서 붉은색 샘플링 패턴을 다른 개별 패스에서 처리하는 식입니다.


그 결과들을 모두 중간( intermediate ) 텍스쳐들에 저장합니다. 그래서 이러한 작업의 끝에서는 전체 해상도 출력을 처리한 것입니다. 샘플링 패턴 당 개별 패스들에서 모든 픽셀들을 처리했으므로, 픽셀 셰이더에서는 픽셀 당 랜덤화가 더 이상 존재하지 않습니다. 더욱이 다운샘플링된( down-sampled ) 입력 텍스쳐들을 사용하기도 했으므로 인접 픽셀들은 인접한 텍셀들을 페칭할 수 있습니다.


요약하면:


  • 샘플링 패턴들을 개별적으로 렌더링합니다.
  • 각 패스들에서, 다운샘플링된 입력 텍스쳐들을 사용합니다.

우리는 이 접근법을 Deinterleaved( 역주 : 신호처리에서 디인터리빙은 인터리빙되어 전송된 비트들을 처리하는 것을 의미합니다 ) Texturing 이라고 부릅니다.


Algorithm


그림 5. [ 1 단계 ] 입력 텍스쳐를 deinterleaving.


전체 해상도 입력 텍스쳐로부터 시작해서 ( 이 경우에는 2x2 interleaved ) 그림 5 에서처럼 deinterleave 합니다.


샘플링 패턴 당 하나의 텍스쳐를 생성합니다. 2x2 샘플링 패턴을 사용하는 이 예제에서, 텍스쳐들은 절반( half )-해상도입니다. 우리는 MRT 를 사용해 deinterleaved data 를 렌더링하고 그 것들을 4 개의 슬라이스( slice )를 가진 절반-해상도 2D 텍스쳐 배열에 저장합니다.


이 deinterleaving 단계는 구조체 배열( Array Of Struct )을 배열들의 구조( Struct Of Arrays )로 변환하는 것으로 볼 수 있습니다. 우리는 동일한 입력 정보와 출력 정보를 가지고 있으며, 그냥 입력을 재구성하면 됩니다.


그림 6. [ 2 단계 ] deinterleaved 데이터 샘플링.


다음으로는 deinterleaved 텍스쳐 당 하나의 드로 호출을 수행하고 각 드로 호출들은 하나의 샘플링 패턴을 가집니다. 이러한 각 드로 호출들은 텍스쳐 배열 A 로부터 단일 슬라이스를 샘플링하고 있으며 텍스쳐 배열 B 로 단일 슬라이스를 출력하고 있습니다.


이 드로 호출들의 픽셀 셰이더에서는 픽셀 당 지터링이 사용되지 않으며 인접 픽셀들은 가까운 텍셀들을 페칭합니다. 이는 텍스쳐 캐시에 친화적입니다.


그리고 모든 텍스쳐 슬라이스들이 저해상도( 절반-해상도 )이므로, 비디오 메모리로부터 셰이더 유닛들로 작업 집합을 전송하기 위해 요구되는 메모리 대역폭이 적으며, 성능을 상당히 개선하는데 도움을 줄 수 있습니다.


그림 7. [ 단계 3 ] 출력을 다시 interleaving.


이제 우리는 각 샘플링 패턴을 위해 4 개의 개별 결과들을 생성했습니다. 그 결과들을 다시 전체-해상도 텍스쳐로 interleave 합니다. 그것은 전체-해상도 픽셀 셰이더 패스이며, 텍스쳐 배열 B 로부터 페치하는 하나의 텍스쳐를 사용합니다.


이 기법은 이 SDK 샘플이 보여주는 것처럼 4x4 interleaving 으로도 확장될 수 있습니다. Deinterleaving 단계의 경우, 4 개의 MRT 를 사용해 1 번의 드로 호출을 수행하는 대신에, 8 개의 MRT 를 사용해 2 번의 드로 호출을 사용합니다. 16 개의 가능한 샘플링 패턴들을 처리하고, 나머지 단계들은 동일하게 유지합니다.


Acknowledgments


Sibenik 모델은 Marko Dabrovic 이 만들었습니다. 이 샘플에서 사용된 AT-AT 모델은 Brad Blackburn 이 만들었으며, http://www.scifi3d.com 에서 다운로드할 수 있습니다.


References


[Keller and Heidrich 2001] Alexander Keller and Wolfgang Heidrich. “Interleaved Sampling.” Proceedings of the Eurographics Workshop on Rendering. 2001.


[Segovia et al. 2006] B. Segovia, J. C. Iehl, R. Mitanchey, B. Péroche. “Non-interleaved Deferred Shading of Interleaved Sample Patterns.” Graphics Hardware 2006.


[Ritschel et al. 2009] Tobias Ritschel, Thorsten Grosch, Hans-Peter Seidel. “Approximating Dynamic Global Illumination in Image Space.” I3D 2009.


[Kasyan et al. 2011] Nickolay Kasyan, Nicolas Schulz, Tiago Sousa. “Secrets of CryENGINE 3 Graphics Technology.” Advances in Real-Time Rendering Course. SIGGRAPH 2011.


[McGuire et al. 2012] Morgan McGuire, Michael Mara, David Luebke. “Scalable Ambient Obscurance.” HPG 2012 [Bavoil and Jansen 2013] Louis Bavoil and Jon Jansen. “Particle Shadows & Cache-Efficient Post-Processing.”. GDC 2013

원문 : https://software.intel.com/en-us/articles/adaptive-screen-space-ambient-occlusion


샘플 소스 : https://github.com/GameTechDev/ASSAO/


주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.


주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.



By Filip Strugar, published on November 9, 2016, updated Feburary 13, 2017


이 기사는 어댑티브 스크린 스페이스 앰비언트 오클루전( adaptive screen space ambient occlusion )( ASSAO )라 불리는 이펙트( effect )의 새로운 구현에 대해서 소개합니다. 그것은 저전력 디바이스에서부터 고해상도를 가진 고성능 데스크탑들까지를, 통일된 외견( look ), 설정들, 업계 표준과 동일한 품질을 가진 하나의 구현하에서, 다룰 수 있도록 특별하게 설계되었습니다. 


SSAO 는 작은 범위의 앰비언트 이펙트들과 콘택트( contact ) 셰도우( shadow ) 이펙트들을 생성하기 위해 실시간 렌더링에서 사용되는 대중적인 이펙트입니다. 많은 현대의 게임 엔진들에서 사용되고 있으며, 보통 프레임 GPU 시간의 5 ~ 10 퍼센트만큼을 사용하게 됩니다. 여러 개의 알려진 구현들이 이미 존재하기는 하지만, 그 어떤 것도 오픈 소스이거나 무료로 이용할 수 있거나 저전력 모바일로부터 데스크탑 디바이스에 이르기까지 요구되는 성능 범위 단계( level of performance scaling )를 제공하지는 않습니다. 이 부분을 ASSAO 가 만족시켜줄 수 있습니다.


이 기사는 샘플코드를 이해하고 더 나아가 그것을 통합하거나 포팅( port )하는 방법에 대해서 초점을 맞추고 있습니다. 또한 구현 명세들과 이용가능한 옵션들과 설정들, 그리고 그것을 사용하는 데 있어 발생하는 트레이드 오프( trade-off )들에 다룹니다. 구현의 세부사항에 대해서 다루는 기사는 앞으로 나올 GPU Zen( GPU Pro* 8 )에 있습니다.


그림 1. Unity 4* 의 씬에 적용된 ASSAO 의 예제.


Full DirectX* 11 implementation is provided under MIT license in an easy-to-integrate package.


Algorithm Overview


ASSO 는 확장성( scalability )와 유연성( flexibility )을 위해 튜닝( tune )된  SSAO 구현입니다. AO 구현은 입체각( solid-angle ) 오클루전 모델에 기반합니다. 이는 novel progressive sampling kernel disk 를 사용하는 [Bavoil et al. 2008] 의 "Horizon-Based Ambient Occlusion" 과 유사합니다. 그것을 둘러싼 성능 프레임워크( performance framework )는 캐시 친화적인 deinterleaved rendering 의 2 x 2 버전입니다( 역주 : 이 방식에 대해서는 [ Deinterleaved Texturing for Cache-Efficient Interleaved Sampling ] 에서 다루고 있습니다 ). 그것은 "Deinterleaved Texturing for Cache-Efficient Interleaved Sampling" [Bavoil 2014] 와 선택적 뎁스( depth ) MIP-mapping 인 "Scalable Ambient Obscurance" [McGuire et al. 2012 ] 에서 언급되었습니다.


성능 관점에서 확장성 품질은 ( 점진적인 샘플링 커널( progressive sampling kernel )에 의해 활성화된 ) 다양한 AO 탭( tap )의 개수와 다양한 프리셋 레벨에서의 개별 기능들을 토글링( toggling )함으로써 결정됩니다.


마지막에 노이즈 제거( de-noise ) 블러가 적용된 인접 픽셀들 사이의 ( 회전되고 스케일링된 샘플링 디스크에 따른 ) AO 값을 공유하기 위해서 확률적( stochastic ) 샘플링을 사용합니다. 노이즈 제거 블러는 앞이나 뒤에 있는 오브젝트들에 AO 효과가 스며들어( bleeding ) 헤일로( halo )가 발생하는 것을 막기 위해 엣지를 고려합니다( edge-aware ). 가장자리는 뎁스에만 기반하거나 뎁스와 노멀( normal )에 기반합니다( 후자는 더 좋은 품질을 생성하지만, 처리하는 데 더 많은 비용을 사용합니다 ). 이런 스마트( smart ) 블러는 최적의 캐시 효율정을 위해 2 x 2 deinterleaved 영역( domain )에서 수행됩니다. Interleaving( 재구성 ) 패스 동안에 마지막 패스에서만 전체 해상도가 사용됩니다.


경험적으로, 그것은 다중 패스 픽셀 셰이더 기반 기법입니다. High 프리셋에서의 주요 단계들은 다음과 같습니다:


  1. 뎁스들을 준비합니다.
    1. 2 x 2 deinterleave 입력 스크린 뎁스를 4 개의 쿼터( quarter )-뎁스 버퍼들로 만들고, 값들을 뷰스페이스( viewspace )로 변환합니다. 입력 스크린 노멀들이 제공되지 않는다면, 그것들을 뎁스로부터 재구성합니다.
    2. 각각의 작은 뎁스 버퍼들을 위해 MIP 들을 생성합니다( Lowest 프리셋에서는 하지 않고, Low 프리셋이나 Medium 프리셋에서는 합니다 ).
  2. AO 항과 엣지를 고려한 블러를 4 개의 2 x 2 deinterleaved 파트( part )들에 대해서 계산합니다.
    1. AO 항과 엣지들을 계산하고 R8G8 텍스쳐에 저장합니다.
    2. 엣지를 고려한 스마트 블러를 적용합니다( 한 패스에서 여섯 패스까지, 사용자 설정에 달려 있습니다 ).
  3. 네 파트를 전체 해상도 버퍼에 합칩고 최종적으로 엣지를 고려한 블러 패스를 적용합니다.


Highest/Adaptive 품질 프리셋은 추가적인 기반( base ) AO 패스를 가지고 있으며, 이는 메인( main ) AO 패스의 픽셀당 변수 샘플 카운트를 안내( guide )하는 중요한 휴리스틱( heuristic )들을 제공하는데 사용됩니다.


표1 은 성능 수치들에 대한 개요를 제공합니다. 이 수치들은 참고용이며 드라이버와 하드웨어 명세에 따라 달라질 수 있습니다. 'radius' 같은 이펙트 설정을 변경하는 것은 성능에 영향을 주지 않습니다. 하지만 엣지를 고려한 블러의 경우에는 예외입니다; 블러 패스의 개수를 증가시키는 것은 비용을 증가시킵니다.


표1. 다양한 프리셋, 해상도, 하드웨어에서 ASSAO 이펙트 비용을 밀리세컨드( millisecond ) 단위로 표시.


스크린 노멀이 제공되고, 2-패스 블러와 Highest adaptive target 가 0.45 로 설정되면, Lowest/Low/Medium/High/Highest 프리셋들 사이의 이펙트 스케일링( 품질 vs 성능 )이 다양한 AO 탭의 개수와 개별 기능들에 대한 on/off 를 통해 결정됩니다. 표 2 는 이런 프리셋들의 세부적인 설정들을 보여 줍니다.


표 2. ASSAO 프리셋의 세부사항.


Sample Overview


샘플은 DirectX 11 을 사용하고 Windows* 7 64-bit 이상, 그리고 Microsoft Visual Studio* 2015 와 호환됩니다.


그림 2. ASSAO 샘플 레이아웃( layout ).


샘플에 포함된 Crytek Sponza* 씬이 기본으로 사용되며 기본 이펙트 프로우파일링 측정치( metric ) 이 우상단쪽 그래프에 나옵니다. 그래프 아래에는, 이펙트 설정, 품질, 혹은 이펙트 디버깅을 변경하기 위해 사용되는 여러 개의 다이얼( dial )들이 있습니다. 주요 설정은 다음과 같습니다:


  1. Effect enabled
    이펙트의 on/off 를 토글합니다.

  2. Effect radius
    뷰스페이스 단위의 앰비언트 오클루전의 반지름입니다.

  3. Effect strength
    선형 이펙트 배율( multiplier ). 이펙트 파워( power )와 함께 이펙트의 강도를 조절하고 이펙트를 페이드( fade ) 인/아웃( in/out )하는 데 유용합니다.

  4. Effect power
    지수적인( exponential ) 이펙트 수정자입니다: occlusion = pow( occlusion, effectPower ). 이펙트 커브의 파워를 변경( tweak )하는 최상의 방법입니다.

  5. Detail effect strength
    고주파수( high-frequency ) 이펙트를 추가하는 데 사용하는 2 픽셀 넓은 추가 커널입니다. 높은 값을 지정하면 에일리어싱( aliasing )과 일시적인 불안정성( temporal instability )이 증가합니다.

  6. Blur amount
    블러 패스의 개수를 더 높게 설정하면 더 낮은 고주파수 버전( less high-frequency variation )을 사용하는 것보다 더 부드러운 결과를 산출합니다. 이는 ( 에일리어싱을 줄여주는 ) 이점을 가지지만, 비용을 증가시킵니다.

  7. Blur sharpness
    거리 기반( 그리고 선택적인 노멀 기반 ) 가장자리가 흐려지는 것을 막는 정도를 결정합니다. 다른 앞뒤의 오브젝트들 사이에서 이펙트가 번지는 것을 막는데 사용됩니다. 이펙트가 번지면 헤일로와 다른 문제들이 발생합니다. 1 값이 의미하는 것은 완전히 날카로워( 엣지에서 블러링하지 않음 )지고 제약이 완화됨을 의미합니다. 1 에 가까운 값들은 에일이어싱을 제어하는 데 유용합니다.

  8. Deferred Path
    디퍼드 경로에서, 이펙트를 위한 입력들은 스크린 뎁스 텍스쳐와 노멀맵 텍스쳐입니다. 하지만, 포워드 패스가 사용되면, 뎁스 텍스쳐만이 입력으로 사용됩니다. 반면에 노멀맵은 뎁스로부터 재구성되는데, 이로 인해 비용이 추가되며 약간 다른 결과를 산출하게 됩니다.

  9. Expand resolution
    스크린 가장자리 근처에서는 이펙트 커널이 스크린 밖에서 처리되는 부분이 있습니다. 다양한 샘플링 모드( 즉 clamp/mirror )들이 다양한 결과를 산출하는데 사용될 수 있지만( m_amplerStateViewspaceDepthTab ), 최적의해결방법은 뎁스 퍼버를 생성하는 동안에 일정 비율만큼 더 크게 렌더링 영역과 해상도를 확장하는 것입니다. 그래서 AO 이펙트가 사용하는 데이터가 가장자리에서도 이용될 수 있게 합니다. ASSAO 는 선택적인 시저 사각형( scissor rectangle )을 사용해서 확장된 ( 보이지 않는 ) 영역들에 대한 AO 계산이 이루어지지 않도록 합니다.

  10. Texturing enabled
    AO 이펙트를 더 잘보이게 만들기 위해 ( 라이팅은 여전히 적용됨 ) 텍스쳐링을 토글합니다.

  11. Quality preset
    표 1 과 2 에서 설명되어 있듯이, 네 개의 품질 프리셋 사이에서 프리셋을 교체합니다.
    Highest/Adaptive 프리셋의 경우, Adaptive 타깃은 점진적인( progressive ) 품질 타깃을 제어하는데, 이는 품질과 성능을 빠르게 교환( trade-off )하기 위해 런타임에 변경될 수 있습니다.

  12. Switch to advanced UI
    이펙트를 더 자세히 디버깅하기 위해서, 샘플이 진보된 UI 로 변경되도록 할 수 있습니다. 이는 추가적인 씬들에 대한 접근을 제공하고 이펙트의 개발자 버전을 제공합니다. 이는 더 자세한 프로우파일링을 허용하며, 노멀, 검출된 가장자리, 선택된 픽셀에 대한 모든 AO 샘플들, adaptive effect heatmap 등을 보여 주는 다양한 디버그 뷰를 제공합니다.

Integration Details


DirectX 11 코드를 빠르게 통합하려면, 샘플 프로젝트에서 세 개의 파일들만 필요합니다:


  • Projects\ASSAO\ASSAO\ASSAO.h
  • Projects\ASSAO\ASSAO\ASSAODX11.h
  • Projects\ASSAO\ASSAO\ASSAO.hlsl


DirectX 11 API 를 제외한 다른 종속성 없이 전체 ASSAO 구현들이 포함되어 있습니다.


기본적인 ASSAO 통합 단계는 다음과 같습니다:


  1. ASSAO.h 와 ASSAODX11.cpp 를 프로젝트에 추가합니다.
  2. ASSAO.hlsl 파일을 로드할 수 있는 곳에다가 추가하십시오. 혹은 바이너리로 .hlsl 파일을 쉽게 임베드하는 방법( 그리고 커스텀 빌드 단계에 대한 )의 세부사항을 원한다면 ASSAOWrapperDX11.cpp "USE_EMBEDDED_SHADER" 를 참조하십시오.
  3. DirectX 11 디바이스 생성 후에, 정적 ASSAO_Effect::CreateInstance(...) 에다가 ID3D11Device 포인터와 셰이더 소스 버퍼를 제공함으로써, ASSAO_Effect 오브젝트 인스턴스를 생성하십시오. DirectX 디바이스가 파괴되기 전에 ASSAO_Effect::DestroyInstance() 를 호출해서 오브젝트를 파괴하는 것을 잊어 버리면 안 됩니다.
  4. 포스트 프로세싱 파이프라인 안에서 적절한 위치를 찾으십시오: SSAO 는 보통 스크린 스페이스 이펙트들이 실행되기 전에 라이트 누적( accumulation ) 버퍼나 포스트-톤맵 컬러 버퍼들에 직접적으로 적용되는데, 항상 곱( multiplication ) 블렌드 모드를 사용합니다. 라이팅 패스에서 나중에 사용하기 위한 개별 버퍼에다가 AO 항을 렌더링하기 위해 종종 더욱 물리적으로 정확한 접근법이 사용됩니다. 어떤 경우든지 간에, 요청되는 입력들은 씬 뎁스( 그리고 가능하다면 스크린 스페이스 노멀 )이므로, ASSAO 는 그것들을 이용할 수 있는 시점에 그려져야 합니다.
  5. 프레임당 입력 구조체를 ASSAO_InputsDX11 에서 채웁니다:
    1. SissorLeft/Right/Top/Bottom 은 Expand resolution 접근법이 사용되는 경우처럼 이펙트 출력이 더 작은 사각형으로 제한될 때만 사용됩니다. 그렇지 않으면 기본값인 0 이 사용되며, 이는 전체 뷰포트로 출력이 나간다는 것을 의미합니다.
    2. ViewportX/Y 는 반드시 0 이어야 하며 ViewportWidth/Height 는 입력 뎁스 텍스쳐 해상도 및 스크린 스페이스 노멀 텍스쳐 해상도와 출력 렌더 타깃의 크기여야 합니다.
    3. ProjectionMatrix 는 뎁스 버퍼를 그릴 때 사용된 프로젝션이어야 합니다. 왼손 좌표계와 오른손 좌표계가 역전된 Z( reversed Z )와 함께 모두 지원됩니다( http://outerra.blogspot.de/2012/11/maximizing-depth-buffer-range-and.html )( 역주 : 역전된 Z 와 관련해서는 [ Depth Precision Visualized ] 에 번역해 둔 다른 자료도 있습니다  ).
    4. NormalsWorldToViewspaceMatrix ( 선택적임 ) 는 입력 스크린 스페이스 노말이 뷰스페이스에 존재하지 않을 때 필요합니다. 이 경우에는 이 행렬이 뷰스페이스로 변환되어야 합니다.
    5. MatricesRowMajorOrder 는 입력 ProjectMatrix NormalsWorldtoViewspaceMatrix 의 메모리 레이아웃을 정의합니다.
    6. NormalsUnpackMul NormalsUnpackAdd 의 기본값은 2 와 -1 입니다. 그리고 노멀들을 일반적으로 저장되는 UNORM[0,1] 텍스쳐로부터 [-1,1] 범위로 언패킹하는데 사용됩니다. 노멀들이 부동소수점 수 텍스쳐로 제공된다면, 이 값들은 각각 1 (mul) 과 0 ( add ) 으로 설정될 필요가 있습니다.
    7. DrawOpaque 는 블렌딩 모드를 지정합니다: 이 값이 true 라면 선택된 렌더 타깃의 내용이 덮어써질 것입니다; 이 값이 false 라면 multiplicative 블렌딩 모드가 사용됩니다.
    8. DeviceContext ( DirectX 11 전용 ) 는 이펙트를 렌더링하는데 사용되는 ID3D11DeviceContext 포인터로 설정되어야 합니다.
    9. DepthSRV ( DirectX 11 전용 ) 는 입력 뎁스 데이터로 설정되어야 합니다.
    10. NormalSRV ( DirectX 11 전용 ) 는 입력 스크린 스페이스 노멀로 설정되거나 이용할 수 없다면 nullptr 로 설정되어야 합니다( 이 경우에 노멀들은 뎁스 데이터로부터 재구성됩니다 ).
    11. OverrideOutputRTV ( DirectX 11 전용 ) 는 nullptr 로 설정되거나 출력 렌더 타깃으로 설정되어야 합니다. 이 값이 nullptr 로 설정되면, 현재 선택되어 있는 RTV 가 사용됩니다.
  6. ASSAO_Settings 에 정의된 설정 구조체를 채웁니다. Sample overview 섹션에서 자세히 설명하고 있습니다.
  7. ASSAO_Effect::Draw 함수를 호출합니다. 현재 모든 DirectX 11 스테이트( state )들은 끈임없는 통합을 보장하기 위해서 그 호출 이후에 백업되고 복구됩니다.
샘플 프로젝트에 있는 다음 파일들은 통합 예제를 제공합니다:

  • Projects\ASSAO\ASSAOWrapper.h
  • Projects\ASSAO\ASSAOWrapper.cpp
  • Projects\ASSAO\ASSAOWrapperDX11.cpp


가장 최신의 소스 코드는 https:/github.com/GameTechDev/ASSAO 에서 다운로드할 수 있습니다.


Citations


[Bavoil et al. 2008] Bavoil, L., Sainz, M., and Dimitrov, R. 2008. "Image-Space Horizon-Based Ambient Occlusion.” In ACM SIGGRAPH 2008 talks, ACM, New York, NY, USA, SIGGRAPH ’08, 22:1–22:1.


[McGuire et al. 2012] Morgan McGuire, Michael Mara, David Luebke. “Scalable Ambient Obscurance.” HPG 2012.


[Bavoil 2014] Louis Bavoil, “Deinterleaved Texturing for Cache-Efficient Interleaved Sampling.” NVIDIA 2014.


Notices


This sample source code is released under the MIT License.

원문 : https://developer.samsung.com/game/usage


주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.


주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.



Introduction


Vulkan 용례 가이드는 독자가 이미 API 와 친숙하지만 여러 종류의 Galaxy 디바이스에서 효율적으로 사용하는 방법에 대해서 알고자 한다고 가정합니다. 또한 일반적으로 모바일 디바이스들에서 찾아볼 수 있는 타일 기반 렌더링( Tile Based Rendering, TBR ) GPU 아키텍쳐의 기초에도 익숙하다고 가정합니다. Vulkan 초심자라면 API 개념을 코드 예제와 함께 소개하는 추천 SDK 들부터 시작하실 수 있습니다. GPU 아키텍쳐에 대해서는 여기에서 학습하실 수도 있습니다.


이 문서를 읽기 전에, Game Asset Optimization Recommendations 에도 익숙해지기를 권장합니다.


Understand your target



고성능 애플리케이션을 개발할 때, 여러분이 대상으로 하는 하드웨어 및 API 의 기능( capabilities )과 성능 특성( characteristics )을 이해하는 것이 필수적입니다.


Vulkan 의 경우, 다음을 포함합니다:


  • 최대 그래픽스 API 버전.
  • 기능들
    • 예를 들어 최대 텍스쳐 해상도.
  • 확장( extension )들.


다른 성능 집중 API 와 마찬가지로, Vulkan 에는 API 사용자가 고려하지 않으면 정의되지 않는 동작을 발생시킬 수 있는 상황들이 존재합니다. 크로노스( Khronos ) Vulkan Validation LayersGraphics API debugging tools 는 API 의 오용을 식별하는 데 도움을 줍니다. 또한 개발 초기 단계에 버그를 식별하기 위해 애플리케이션을 광범위한 디바이스, 칩셋( chipset ), GPU 아키텍쳐에 대해서 테스트하는 것이 매우 중요합니다.


  • GPU 아키텍쳐의 차이를 고려하십시오.
  • 런타임에 기능들과 확장들을 검사하십시오. 대안( fall-back )들을 구현하십시오.
  • 애플리케이션을 여러 디바이스에서 테스트하십시오.


Asset optimization


Game Asset Optimization Recommendations 을 확인하세요.


Shader Precision


SPIR-V 는 정밀도 한정자( precision qualifier )들을 지원합니다( OpDecorate RelaxedPrecision ). 정밀도 힌트( hint )는 ALU 연산들의 성능을 개선하기 위해서, 개발자가 감소된( reduced ) 정밀도를 사용할 위치를 컴파일러에게 알려줄 수 있도록 하며, 결국 GPU 의 전력 소비를 줄이게 됩니다.


변수에 대해 요청된 정밀도를 컴파일러가 받아들이는 것은 유효합니다. 예를 들어 개발자가 RelaxedPrecision 을 지정할 때는 32 비트 부동소수점 정밀도를 사용합니다. 컴파일러는 정밀도 변환을 위해 도입된 명령어가 완전한( full ) 정밀도로 계산을 실행하는 것보다 더 많은 부하를 발생시키는 경우에 이 방법을 사용하는 경향이 있습니다.


  • 컴파일러가 정밀도를 높이는( promote ) 것에 주의하십시오.  A 디바이스( promoted precision )에서 완벽하게 동작하는 셰이더가  B 디바이스( honors precision qualifier )에서 아티팩트( artifact )를 발생시킬 수 있습니다.
  • 감소된 정밀도로 인해 일부 디바이스들에서 렌더링 에러들이 감춰져 있는 상황에 주의하십시오.


Recommendations


  • 성능을 향상시키고 전력 소비를 줄이기 위해서 감소된 정밀도를 사용하십시오.
  • 컴파일러가 정밀도를 높이는 것에 주의하십시오. 셰이더 정밀도 아티팩트들을 초기에 발견하려면 여러 디바이스에서 테스트하십시오.
  • 감소된 정밀도로 인해 일부 디바이스들에서 렌더링 에러들이 감춰져 있는 상황에 주의하십시오.


Pipeline management


파이프라인을 드로( draw ) 시점에 생성하는 것은 성능 끊김( stutter )을 유발합니다. 파이프라인을 애플리케이션을 실행할 때 가능한 한 초기에 생성하는 것을 권장합니다. 파이프라인을 드로 시점 전에 생성하도록 렌더링 엔진을 재구성하는 것이 불가능하다면, 파이프라인을 한 번만 생성하고 그것들을 맵에 넣어서 연속되는 드로 호출들에서 해싱된( hashed ) 스테이트( state )들을 통해 검색할 수 있도록 하는 것을 권장합니다.


파이프라인 캐시는 새로운 파이프라인이 생성될 때 드라이버가 캐싱된 파이프라인들로부터 스테이트들을 재사용할 수 있도록 해 줍니다. 이는 셰이더 컴파일같은 반복되는 비싼 연산들 대신에 이미 구워진 스테이트를 재사용함으로써 성능을 상당히 개선할 수 있습니다. 단일 파이프라인 캐시를 사용해서 드라이버가 이전에 생성되었던 모든 스테이트들을 재사용할 수 있도록 보장하는 것을 권장합니다. 또한 파이프라인 캐시를 파일에 써서 다음 번에 애플리케이션이 실행될 때 재사용될 수 있도록 하기를 권장합니다.


파이프라인 상속( derivative )은 애플리케이션이 "자식" 파이프라인들을 유사한 "부모" 파이프라인으로부터의 증분 스테이트 변경( incremental state changes )으로서 표현할 수 있도록 해 줍니다; 일부 아키텍쳐들에서 이는 유사한 스테이트들 간의 전환 비용을 줄여줄 수 있습니다. 많은 모바일 GPU 들은 주로 파이프라인 캐시를 통해 성능을 개선합니다. 그래서 파이프라인 상속은 종종 휴대용( portable ) 모바일 애플리케이션에 대해서는 이점을 제공하지 않습니다.


Recommendations


  • 애플리케이션 실행 초기에 파이프라인들을 생성하십시오. 파이프라인 생성이 드로 시점에 발생하는 것을 피하십시오.
  • 모든 파이프라인 생성에 대해서 단일 파이프라인 캐시를 사용하십시오.
  • 파이프라인 캐시를 애플리케이션 실행되는 사이에 파일에 쓰십시오.
  • 파이프라인 상속을 피하십시오.


Descriptor set management


디스크립터 셋은 드로를 위한 리소스 바인딩을 정의합니다. 이상적으로 볼 때, 디스크립터 셋들은 빌드 시점에 생성되어야 하고 런타임 실행을 위해 캐싱되어야 합니다. 그것이 불가능하면, 디스크립터 셋들을 가능한 한 애플리케이션 실행 초기에 생성해야 합니다( 애플리케이션 로드나 레벨 로드 ).


Vulkan 의 대부분의 리소스들과 마찬가지로, API 사용자는 디스크립터 셋 갱신( update )을 동기화( synchronizing )할 책임이 있습니다. 그래야 대기중인 디바이스 읽기가 존재할 때 호스트에서 변경이 발생하지 않는 것이 보장됩니다. 리소스 동기화를 단순화하기 위해서 스왑 인덱스( swap index ) 별 디스크립터 풀( pool )을 사용하고 같은 바인딩을 사용하는 드로 사이에서 디스크립터 공유( sharing )를 이용( facilitate )하는 것을 권장합니다. 렌더링 엔진을 재구성하는 것이 불가능하고 드로 시점에 디스크립터 셋을 갱신할 필요가 있다면, 사용중인( in-flight ) 디스크립터를 변경하는 것을 피하기 위해, 디스크립터 셋과 버퍼를 관리하는 전략이 매우 주의깊게 고려되어야 합니다. 아래의 Buffer management 에서도 기술하듯이, 엔진이 최소 VkPhysicsDeviceLimits::maxUniformBufferRange 값에 대처하도록 하는 것이 중요합니다. 왜냐햐면 디스크립터들 사이에서 버퍼를 공유할 때 그 제한을 쉽게 초과할 수 있기 때문입니다.


유니폼( uniform ) 버퍼나 스토리지( storage ) 버퍼의 옵셋( offset )이 자주 변경된다면( 예를 들어 드로 때마다 ), VK_DESCRIPTOR_TYPE_*_BUFFER_DYNAMIC 을 사용해 버퍼를 동적으로 바인딩하고 vkCmdBindDescriptorSets() 가 호출될 때 옵셋을 pDynamicOffsets 로 설정하는 것을 권장합니다. 이는 그 옵셋들이 드로 실행 전에 디스크립터 셋을 수정하지 않고 변경될 수 있도록 해 줍니다.


Recommendations


  • 디스크립터 셋을 가능한 한 초기에 빌드하십시오( 이상적으로는 빌드 시점에 ).
  • 드로에 의해 참조되지 않는 리소스들을 바인딩하지 마십시오.
  • 버퍼 옵셋이 자주 변경될 필요가 있다면( 예를 들어 드로 사이에서 ), 디스크립터를 바인딩할 때 pDynamicOffset 를 사용하십시오.
  • 높은 빈도로 디스크립터 셋을 수정하지 마십시오. 동적 업데이트가 요구된다면, 읽기와 쓰기에 대한 동기화를 보장해야 합니다.


Buffer management


Game Asset Optimizations: Interleaved vertex attributes 에서 기술했듯이, position 애트리뷰트들은 다른 애트리뷰트들과는 다른 버퍼에 저장되어야 합니다. 이는 현대 모바일 GPU 들이 버텍스( vertex ) 셰이딩을 더 효율적으로 실행할 수 있게 해 줍니다. 서로 다른 빈도로 업데이트를 하지 않는 한에는, 다른 모든 애트리뷰트들은 단일 버퍼에 삽입되어야 합니다.


유니폼 버퍼들은 애플리케이션 실행시에 가능한 한 초기에 할당되어야 하며, 프레임 당 할당은 피해야 합니다( 이전의 할당을 재사용하는 것이 훨씬 빠릅니다 ). 실행중인 렌더러들이 현재 프레임의 API 호출에 의해 변경되는 버퍼 영역에 접근하는 것을 끝마치도록 보장하기 위해 펜스( fence )를 사용해야 합니다.


유니폼 버퍼는 서로 다른 빈도로 업데이트되는 데이터를 포함해서는 안 됩니다. 예를 들어, 드로가 정적인 데이터와 프레임 당 설정되는 데이터( 예를 들어 변환 행렬 )에 의존한다면, 두 개의 버퍼가 사용되어야 합니다. 다중 드로에 대해 공유되는 유니폼 데이터는 단일 버퍼에 저장되어야 합니다.


중복 전송( redundant transfer )들을 막기 위해서, 유니폼 데이터 메모리는 VK_MEMORY_PEROPERTY_HOST_VISIBLE_BIT 셋으로 할당되어야 합니다. 이 플래그는 vkMapMemory() 가 호스트로부터 효율적인 수정을 위해 사용될 수 있도록 합니다( 스테이징( staging) 버퍼에 비하면 복사 한 번이 더 적습니다 ). 잦은 Map/Unmap 호출은 피해야 합니다. 가능하다면 버퍼는 VkMemoryProeprtyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT 를 설정함으로써 영구적으로 매핑될 수 있습니다. 주의 : 영구적인 매핑은 API 캡쳐 도구들이 버퍼 수정을 트래킹하는 것을 더 어렵게 만듭니다. 이러한 이유로, 전용 GPU 메모리를 사용하는 플랫폼( platform )을 위해 영구적으로 매핑되지 않는 대안( non-persistently mapped fallback ) 경로를 구현하여 디버깅을 단순화하는 것을 권장합니다.


버퍼는 VkPhysicalDeviceLimits::min*Alignment 제한으로 정렬( align )되어야 합니다. 디스크립터 셋 바인딩과 버퍼 할당의 최대 크기는 다음과 같이 질의될 수 있습니다:


 Limit

 Desriptions

 VkPhysicalDeviceLimits::maxUniformBufferRange

 최대 유니폼 버퍼 메모리 범위.

 VkPhysicalDeviceLimits::maxDescriptorSetUniformBuffers

 디스크립터 셋에 바인딩될 수 있는 유니폼 버퍼의 최대 개수.

 VkPhysicalDeviceLimits::maxDescriptorSetUniformBuffersDynamic

 디스크립터 셋에 바인딩될 수 있는 동적 유니폼 버퍼의 최대 개수.

 VkPhysicalDeviceLimits::maxPerStageDescriptorUniformBuffers

 단일 셰이더 스테이지( stage )에서 접근할 수 있는 유니폼 버퍼의 최대 개수.


Descriptor set management 에서 기술했듯이, 디스크립터 셋 갱신을 최소화하기 위해서 동적 버퍼들과 동적 옵셋들을 사용해야 합니다.


셰이더 입력의 경우에는 항상 스토리지 버퍼보다는 유니폼 버퍼를 선호( prefer )해야 합니다.


Recommendations


  • 애트리뷰트 데이터를 두 개의 버퍼에 저장하십시오 - 하나는 버텍스 position 을 위해, 다른 하나는 다른 모든 애트리뷰트들을 위해.
  • 유니폼 버퍼들의 옵셋을 VkPhysicalDeviceLimits::minUniformBufferOffsetAlignment 로 정렬하십시오.
  • 통합( unified ) 메모리를 사용하는 디바이스 상에서는, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT 를 사용하고, 플러싱( flush )들과 잦은 map/unmap 호출들에 대해 영구적인 버퍼 매핑을 선호하십시오.


Shader inputs


Vulkan 은 셰이더 상수들을 설정하기 위한 다양한 메커니즘을 제공합니다; 유니폼 버퍼, 특수화( specialization ) 상수, 푸시( push ) 상수.


특수화 상수들은 정적인 값들인데, 파이프라인 생성 시점에 설정될 수 있습니다( SPIR-V 바이너리가 컴파일될 때 ).


푸시 상수들은 버퍼 오브젝트가 아니라 Vulkan 커맨드를 통해서 셰이더 상수 입력 데이터를 설정하기 위한 메커니즘을 제공합니다. 푸시 상수 저장 공간은 제한적입니다. 푸시 상수 저장소를 위한 최대 바이트 수는 VkPhysicalDeviceLimits::maxPushConstantsSize 를 사용해 질의될 수 있습니다. 가능하다면 동적 유니폼 퍼버 옵셋과 같은 특수화 옵셋 메커니즘을 푸시 상수보다는 더 선호해야 합니다. 렌더링 엔진이 모든 드로들을 푸시 상수 데이터( 128 바이트 )를 위해 최소 maxPushConstantsSize 바이트 미만으로 사용하는 것을 보장할 수가 없다면, 유니폼 버퍼 기반 대안을 구현해 둘 필요가 있습니다. 이는 가장 작은 크기의 푸시 상수 바이트만을 지원하는 Vulkan 구현에서 렌더링 엔진이 작동하는 것을 보장합니다. 동적 유니폼 버퍼 옵셋을 사용하는 경우, vkCmdBindDescriptorSets() 를 호출해 드로 당 옵셋을 설정할 수 있습니다.


아래 목록들은 셰이더에서 상수값을 설정하는 것과 관련한 권장 사향들을 정리한 것입니다:


  • 정적 브랜칭( static branching ): SPIR-V 생성 시점에 실행 경로들을 지정할 수 있다면, 실행 경로를 위해 전용 SPIR-V 를 생성하십시오( 예를 들어 GLSL 의 #define 을 사용 ). 그렇지 않으면, SPIR-V 파이프라인 생성 시점에 특수화 상수를 사용하십시오.
  • 파이프라인 생성 시점 상수들: 파이프라인 생성 시점에 상수값을 설정할 수 있다면, 특수화 상수들을 사용하십시오.
  • 유니폼 버퍼에 대한 잦은 갱신들: vkCmdBindDescriptorSets() 호출 후에 유니폼 데이터 갱신이 여러 드로들에 적용되면, 동적 유니폼 버퍼를 사용하고 vkCmdBindDescriptorSet() 이 호출될 때 동적 옵셋들을 사용하십시오. 그래야 디스크립터 셋에 대한 변경을 피할 수 있습니다( 자세한 내용을 알고자 한다면 Descriptor set management 섹션을 참조하세요 ). 유니폼 데이터가 자주 갱신된다면, 예를 들어 드로마다 갱신된다면, 동적 유니폼 버퍼 옵셋 대신에 푸시 상수를 사용하는 것을 고려해 보십시오.


Recommendations


  • 생성 시점 정적 브랜칭을 위해 전용 SPIR-V 를 생성하십시오( 예를 들어 셰이더 코드에서의 #define ).
  • SPIR-V 컴파일 시점 정적 브랜칭을 위해 특수화 상수를 사용하십시오.
  • SPIR-V 컴파일 시점 상수 값들을 위해 특수화 상수를 사용하십시오.
  • 유니폼 데이터 갱신이 vkCmdBindDescriptorSets() 호출 후에 여러 드로에 적용된다면, 동적 옵셋 유니폼 퍼버를 사용하십시오.
  • 드로 호출마다 유니폼 데이터가 갱신되다면 푸시 상수를 고려해 보십시오.
  • 여러분의 엔진이 높은 빈도로 128 바이트보다 작은 데이터를 사용하고 있다면 푸시 상수들을 사용하십시오. 이것이 보장되지 않는다면, 동적 유니폼 버퍼 대안 경로를 구현하십시오.


Beware: No Implicit uniform type conversions


OpenGL ES 와는 다르게, Vulkan 은 묵시적으로 유니폼 타입 변환을 수행하지 않습니다. 개발자들은 버퍼 바인딩의 내용이 그것들이 바인딩하려고 하는 셰이더 유니폼 버퍼들과 일치하도록 보장할 책임이 있습니다.


Recommendations


  • 유니폼 버퍼 데이터 타입들이 셰이더 유니폼 변수들과 일치하도록 보장하십시오.


View frustum culling


드라이버와 GPU 가 처리하는 가장 싼 드로는 제출되지 않는 드로입니다. 드라이버와 GPU 의 중복 처리를 피하기 위해, 일반적인 최적화된 렌더링 엔진은 뷰 프러스텀 경계내에 존재하거나 겹치는 것들만을 그래픽스 API 에 제출합니다. 뷰 프러스텀 컬링은 보통 CPU 상에서 싸게 수행되며, 복잡한 3D 씬들을 렌더링할 때는 항상 고려되어야 합니다.


Recommendations


  • 드라이버와 GPU 의 중복 처리를 막기 위해 뷰 프러스텀 컬링을 항상 시도하십시오.


Command buffer binding


Vulkan 은 커맨드 버퍼들이 다중 스레드에서 빌드되는 것을 허용하도록 설계되었으며, 이는 이 비싼 작업들이 다중 CPU 코어들에서 수행될 수 있도록 해 줍니다. 또한, 세컨더리( secondary ) 커맨드 버퍼들이 생성될 수 있으며, 그것은 작업을 더 작은 청크( chunk )로 쪼개는 것을 쉽게 해 줍니다. 세컨더리 커맨드 버퍼는 생성 후에 프라이머리( primary ) 커맨드 버퍼에 커밋( commit )되어야 합니다. 하지만, 일부 구현에서는, GPU 가 렌더 패스 내의 모든 커맨드 버퍼가 단일한 연속된 메모리 블락에 속해 있는 것을 요구합니다 -- 이런 GPU 들을 위한 Vulkan 드라이버들은 커맨드 버퍼가 실행되기 전에 세컨더리 커맨드 버퍼를 프라이머리 커맨드 버퍼로 memcpy() 할 필요가 있습니다. 이러한 부하때문에, 세컨더리 커맨드 버퍼들보다는 프라이머리 커맨드 버퍼들을 선호할 것을 권장합니다. 렌더링을 다중 CPU 코어들 사이에서 다중 스레드로 처리하는 것을 선호한다면, 세컨더리 커맨드 버퍼들을 고려하기 보다는 프라이머리 커맨드 버퍼들을 병렬적으로 빌드하는 것을 권장합니다.


세컨더리 커맨드 버퍼를 사용하기로 결정했다면, 분할 계획( partitioning scheme )을 주의깊게 고려하십시오. 씬이 청크들로 빌드될 때, 여러분의 엔진이 드로 호출 제출 순서를 최적화하고 스테이트( state ) 변경을 최소화하도록 만드는 것이 더 어렵습니다. 세컨더리 커맨드 버퍼 빌드 경로가 구현되었다면, 런타임( run-time )에 그 경로가 요구되는지 혹은 프라이머리 커맨드 버퍼 빌드가 더 빠른지 결정해야 합니다.


Recommendations


  • 세컨더리 커맨드 버퍼들을 사용하기 보다는 프라이머리 커맨드 버퍼들에 서밋( submit )하는 것을 선호하십시오.
  • GPU 제한이 있는 디바이스들에서는 세컨더리 커맨드 버퍼를 사용하는 것을 피하십시오.
  • 세컨더리 커맨드 버퍼들을 고려하기 보다는 병렬적으로 프라이머리 커맨드 버퍼들을 빌드하는 것을 고려하십시오.
  • 세컨더리 커맨드 버퍼를 사용한다면, 분할 계획을 주의깊게 고려하십시오.


Instanced draws


모든 Vulkan CmdDraw* 함수들은 instanceCount 파라미터를 받습니다. 인스턴스 당 데이터는 VkVertexInputBindingDescription::inputRate 를 VK_VERTEX_INPUT_RATE_INSTANCE 로 설정된 버퍼를 바인딩함으로써 제공됩니다.


Recommendations


  • 항상 단일 호출 및 인스턴스 당 데이터를 사용하여 인스턴스화된 오브젝트를 렌더링하십시오.


Clearing framebuffer attachments


Vulkan 에는 프레임버퍼 어태치먼트를 클리어하기 위한 세 가지 메커니즘이 존재합니다 :


  • 렌더 패스 로드 연산( VK_ATTACHMENT_LOAD_OP_CLEAR ).
  • vkCmdClearAttachments() 함수.
  • vkCmdClearColorImage() 함수 및 vkCmdClearDepthStencilImage() 함수.


연산들이 효율적으로 수행되도록 하기 위해서는, 주어진 시나리오를 위해 사용되는 메커니즘을 올바르게 선택하는 것이 중요합니다:


Recommendations


  • 렌더 패스의 시작부에서 어태치먼트들을 클리어할 때는, VK_ATTACHMENT_LOAD_OP_CLEAR 를 사용하시오.
  • 서브 패스 내에서 어태치먼트들을 클리어할 때는 VkCmdClearAttachements() 를 사용하십시오.
  • vkCmdClearColorImage() 와 vkClearDepthStencilImage() 는 렌더 패스 외부에서 클리어하는 데 사용될 수 있습니다. 이 함수들은 타일 기반 GPU 아키텍쳐들에서 가장 비효율적인 메커니즘들입니다.


Efficient render pass upscaling


고화질( high-fidelity ) 3D 게임들에서 일반적인 병목은 프래그먼트( fragement ) 셰이딩 실행 시간입니다. 프레임당 프래그먼트 셰이딩 비용을 줄이기 위해, 게임 씬이 줄어든 해상도로 렌더링되고 나서 디바이스의 원래 해상도로 사용자 인터페이스를 렌더링하기 전에 크게 스케일링될 수 있습니다.


  • 렌더 패스 A( 줄어든 해상도 )
    • 게임 씬 렌더링.
  • 렌더 패스 B( 원래 해상도 )
    • 게임 씬 이미지를 업스케일링.
    • UI 를 렌더링.


업스케일링은 두 가지 방식으로 수행될 수 있습니다:


  1. vkCmdBlitImage() 호출.
    1. 소스 이미지의 영역들을 대상 이미지로 복사하는데, 잠재적으로 포맷 변환, 임의의 스케일링 및 필터링을 수행합니다.
    2. 구현에 따라, 이 연산은 전용 블리팅( blitting ) 하드웨어에 의해 GPU 나 GPU 에서 수행될 수도 있습니다.
  2. 전체 화면 사각형 렌더링.
    1. 렌더 패스 B 를 이미지를 샘플링하는 전체 화면 드로 호출로 시작합니다.


vkCmdBlitImage() 가 최적의 선택인 것으로 보이겠지만, 모바일 GPU 들에서는 전체 화면 사각형을 렌더링하는 것보다 덜 효율적인 경향이 있습니다. 그 이유는 그것이 하나의 VkImage 로부터 다른 VkImage 로의 명시적인 복사를 요구하기 때문입니다. 블리트 연산을 위해서 GPU 를 사용하는 구현들 상에서는, 이것이 렌더 패스 A 와 B 사이에서 추가적으로 구현되어야 할 것입니다 -- 낭비될 수도 있는 메모리 대역폭과 GPU 사이클을 소비하게 됩니다. 반면에, 전체 화면 사각형 접근법은 단지 한 VkImage 에 대한 레이아웃 트랜지션( transition )만을 요구합니다. 트랜지션의 유형에 따라 이것은 "무료" 로 수행될 수 있도록 구현될 수 있습니다.


Recommendations


  • 프래그먼트 셰이더 제약이 있을 때 게임 씬 업스케일링만을 고려하십시오. 업스케일링의 대역폭 비용에 주의하십시오.
  • vkCmdBlitImage() 보다는 전체 화면 사각형 업스케일링을 선호하십시오.


Subpasses


모바일 디바이스들은 제한된 메모리 대역폭을 가집니다. 게다가 메모리 대역폭 데이터 전송( transfer )들은 이를 위해 전력을 소비하게 되므로, 가능한 한 적게 사용하는 것이 최상입니다.


3D 그래픽스 렌더링에서, 프레임버퍼는 하나 이상의 어태치먼트들을 필요로 할 것이고 일부 어태치먼트 데이터들은 보존( preserve )될 필요가 있습니다 -- 다른 모든 어태치먼트 데이터는 임시적( temporary )입니다. 예를 들어 컬러 버퍼는 렌더링된 이미지를 위해 요구될 것이고, 뎁스 버퍼는 프리미티브들이 의도된 순서로 렌더링되는 것을 보장하기 위해서 요구될 것입니다. 이 시나리오에서, 뎁스 데이터는 보존될 필요가 없습니다 .그러므로 GPU 메모리에서 시스템 메모리로 그것을 쓰는 것은 대역폭을 낭비하게 됩니다. 또한 N - 1 프레임에 있는 컬러 및 뎁스 버퍼의 내용은 N 프레임에서 요구되지 않습니다. 이 데이터를 업로드하는 것은 메모리 대역폭을 중복으로 사용하기 때문에, 드라이버에게 그런 연산들이 요구되지 않음을 전달하고 싶을 것입니다.


Attachment load op


각 어태치먼트들의 VkAttachmentLoadOp 속성은 어태치먼트들이 서브패스 시작할 때 초기화되어야 하는 방법을 지정합니다.


  • VK_ATTACHMENT_LOAD_OP_DONT_CARE
    • 이는 가낭 효율적인 선택입니다. 어태치먼트가 초기화될 필요가 없을 때 사용되어야 합니다. 예를 들어 모든 픽셀이 스카이 박스나 어떤 드로에 의해서 칠해지는 경우입니다.
  • VK_ATTACHMENT_LOAD_OP_CLEAR
    • 이 연산은 타일러( tiler ) 매우 효율적입니다. Clearing framebuffer attachements 섹션에서 기술했듯이, 이것은 렌더 패스를 시작할 때 어태치먼트를 클리어하는 가장 효율적인 방법입니다.
  • VK_ATTACHMENT_LOAD_OP_LOAD
    • 이것은 가장 비용이 드는 연산입니다. 타일 기반 렌더러에서, 타일( on-tile ) 메모리는 어태치먼트의 보존된 데이터를 시스템 메모리로부터 읽어들임으로써 초기화됩니다.


Attachment store op


각 어태치먼트들의 VkAttachmentStoreOp 속성은 어태치먼트가 서브패스의 끝에서 저장되는 방법을 지정합니다.


  • VK_ATTACHMENT_STORE_OP_DONT_CARE
    • 이는 가장 효율적인 선택입니다. 어태치먼트가 보존될 필요가 없을 때 사용되어야 합니다. 예를 들어 뎁스와 스텐실 데이터가 이 패스를 렌더링하기 위해서만 사용되는 경우입니다.
  • VK_ATTACHMENT_STORE_OP_STORE
    • 이는 가장 비용이 드는 연산입니다. 타일 기반 렌더러에서, 타일 메모리는 어태치먼트 이미지를 시스템 메모리로 저장함으로써 보존됩니다.


어태치먼트 이미지가 결코 로드되거나 저장되지 않는다면, VkImage 는 VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT 를 사용해 생성되어야 하며, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT 속성을 사용해 메모리에 바운딩되어야 합니다. 이는 드라이버가 그 이미지를 임시적( transient )으로 처리할 수 있도록 해 줍니다. 그런 메모리는 나중에 할당될 수 있습니다.


Advanced subpass usage


디퍼드 라이팅( deferred lighting ) 시스템을 위한 같은 멀티 패스 렌더링같은 발전된 서브패스 주제들은 Introduction to Vulkan Render Passes 기사에서 다루고 있습니다.


Recommendations


  • VK_ATTACHMENT_LOAD_OP_DONT_CARE 를 기본으로 두고, 어태치먼트가 클리어되어야 할 필요가 있다면 VK_ATTACHMENT_LOAD_OP_CLEAR 를, 이전의 어태치먼트 데이터가 보존될 필요가 있다면 VK_ATTACHMENT_LOAD_OP_LOAD 를 사용하십시오.
  • 모든 어태치먼트들이 보존될 필요가 없다면 VK_ATTACHMENT_STORE_OP_DONT_CARE 를 사용하십시오.
  • 어태치먼트 이미지가 결코 로드되거나 저장되지 않는다면, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT 를 사용해 할당하고 VK_MEMORY_LAZILY_ALLOCATED_BIT 속성을 가지고 할당된 메모리를 사용하십시오.


Synchronization


Vulkan 에서 동기화는 복잡하며, 이를 지원하는 게임에들에서 일반적인 버그들의 온상입니다. 다음 챕터들은 동기화 권장 사항들에 대해서 설명합니다.


Terminology


동기화 개체( primitive )들에 대해서 이해하려고 하기 전에, 이 챕터들을 통해서 사용되는 Vulkan 의 용어들에 대해서 이해하는 것이 중요합니다:


  • 동기화 스코우프( Synchronization scope ): 연산들의 집합입니다. 대부분의 동기화 커맨드들에서, 소스( source ) 파이프라인 스테이지 마스크와 대상( destination ) 파이프라인 스테이지 마스크들에 의해 동기화 스코우프들이 정의됩니다.
  • 실행 종속성( Execution dependency ): 대부분의 동기화 커맨드들은 실행 종속성을 정의합니다. 첫 번째 동기화 스코우프에서 정의된 모든 연산들은 두 번째 동기화 스코우프 내에서 정의된 모든 연산들보다 먼저 실행되어야 합니다.
  • ( 역주 : 접근 ) 가능성 연산( Availability operation ): 이는 다음 메모리 접근이 발생하기 전에 완료되어야 하는 메모리 연산들을 지정합니다. 예를 들어, 이미지 메모리 배리어( barrier )는 VkImage 의 컬러 어태치먼트 쓰기가 다음 접근들 전에 완료되어야 함을 지정합니다.
  • ( 역주 : 읽기 ) 가시성 연산( Visibility operation ): 이는 주어진 가능성 연산이 발생하기 전에 한 번만 발생해야 하는 메모리 연산을 지정합니다. 예를 들어, 이미지 메모리 배리어는 지정된 가능성 연산이 완료되기 전에 셰이더가 어태치먼트를 한 번 읽어들이게 됨을 지정할 수 있습니다.
  • 리소스 트랜지션( Resource transition ): 어떤 VkImage 리소스들은 가능성 연산과 가시성 연산 사이에서 한 레이아웃에서 다른 레이아웃으로 전이( transition )될 필요가 있습니다. 이미지 메모리 배리어의 경우에, 리소스 트랜지션은 oldLayout 과 newLayout 으로 정의됩니다.
  • 메모리 종속성( Memory dependency ): 이는 가시성 연산들의 집합 이전에 완료되어야만 하는 가능성 연산들의 집합을 정의합니다. 메모리 종속성은 리소스 트랜지션을 정의하기도 합니다.


Semaphores


세마포어는 다중 큐 사이에서 리소스 접근을 제어하는 데 사용될 수 있습니다. 가장 일반적인 세마포어의 용례는 그래픽스 큐와 프리젠테이션 큐를 동기화하는 것입니다.


예제: 그래픽스 큐 <--> 프리젠테이션 큐 동기화


코드 블락 1 : Per-Frame



더 많은 정보를 원한다면 이 페이지를 참고하세요.


Fences


펜스는 큐에서 호스트와 통신하기 위해서 사용될 수 있습니다. 가장 일반적인 용례는 그래픽스 렌더링이 완료되었을 때를 시그널( signal )링하는 것입니다. 그러므로 그 리소스들이 다음 프레임을 위해 재사용될 수 있습니다. 최적의 성능을 위해서는 프리젠트할 이미지들과 리소스들의 개수들 사이를 1:1 로 매핑되도록 하는 것을 추천합니다.


프레임 루프에서 vkWaitForFences() 호출을 피할 것을 추천합니다. 왜냐하면 이것은 실행을 중지( stall )시키고 성능을 감소시키는 결과를 낳기 때문입니다( 우리가 프로우파일링한 게임에서 1-3 fps 정도가 떨어지는 것을 발견했습니다 ). 대신에 vkGetFenceStatus() 를 호출해서 프리젠트할 수 있는 이미지가 이용가능한지를 확인하는 것을 권장합니다.


Example: graphics queue --> host synchronization


코드 블록 2 : Initialization



코드 블록 3 : Per-frame rendering loop content


Recommendations


  • 그래픽스 큐를 호스트와 동기화하려면 항상 펜스를 사용하십시오.
  • 원형( circular ) 버퍼에 있는 동적 리소스들에 대한 참조를 유지하고 각 리소스들이 재사용될 수 있는지 확인하기 위해서 펜스를 사용하십시오.
  • vkWaitForFences() 를 프레임 루프에서 호출하는 것을 지양하고, 대신에 vkGetFenceStatus() 를 사용하십시오.


Barriers


Vulkan 배리어는 API 사용자가 같은 큐 혹은 같은 서브패스 내의 커맨드들 사이에 종속성을 삽입할 수 있도록 해 줍니다. 실행 종속성은 파이프라인 스테이지 동기화 스코우프에 의해 정의됩니다. 실행 종속성과 함게 vkCmdPipelineBarrier() 호출은 세 가지 유형의 메모리 접근 배리어 -- 전역( global ), 버퍼( buffer ), 이미지( image ) -- 들을 받아들일 수 있습니다. 메모리 배리어들은 API 사용자에게 첫 번째 동기화 스코우프가 완료되는 동안( 혹은 전에 ) 두 번째 동기화 스코우프 내의 읽기 연산전에 쓰기 연산을 보장해 줍니다. 이름이 제안하고 있듯이, 전역 메모리 배리어들은 특정 리소스를 지정하는 것이 아니라 모든 메모리 접근들을 동기화하는 데 사용됩니다. 더 자세한 동기화를 위해서, 버퍼 메모리 배리어와 이미지 메모리 배리어를 사용할 수 있습니다:


 Host

 Transfer

 Compute

 Graphics

 TOP_OF_PIPE_BIT

 HOST_BIT

 

 

 

 

 TRANSFER_BIT

 

 COMPUTE_SHADER_BIT

 DRAW_INDIRECT_BIT

 DRAW_INDIRECT_BIT

 

 VERTEX_INPUT_BIT

 VERTEX_SHADER_BIT

 TESSELLATION_CONTROL_SHADER_BIT

 TESSELLATION_EVAULATION_SHADER_BIT

 GEOMETRY_SHADER_BIT

 EARLY_FRAGEMENT_TESTS_BIT

 FRAGMENT_SHADER_BIT

 LATE_FRAGMENT_TESTS_BIT

 COLOR_ATTACHMENT_OUTPUT_BIT

 BOTTOM_OF_PIPE_BIT


위의 표의 네 열은 Vulkan 파이프라인들입니다. TOP_OF_PIPE_BIT 와 BOTTOM_OF_PIPE_BIT 는 모든 파이프라인에 대해 공통입니다. 상대적으로, 그것들은 실행을 시작하고 실행을 종료하는 첫 번째 커맨드를 위한 일반 파이프라인 스테이지들을 표시합니다.


파이프라인 버블( bubble )을 막기 위해서는, API 사용자가 배리어들의 실행 종속성을 매우 주의깊게 고려하는 것이 중요합니다. 배리어 호출을 타일 기반 GPU 아키텍쳐의 서브패스 내에서 하고 있을 때는 특히 중요합니다. 예를 들어 첫 번째 동기화 스코우프에서 BOTTOM_OF_PIPE_BIT 이고 두 번째 동기화 스코우프에서 TOP_OF_PIPE_BIT 인 서브패스 내에서 배리어가 설정되었다면, 배리어 앞에 있는 모든 GPU 커맨드들이 플러싱( flush )되고 배리어 뒤에 있는 커맨드들이 실행을 시작하기 전에 플러싱이 종료되는 것을 위해 대기해야만 할 것입니다.


이러한 버블을 피하려면, 배리어의 첫 번째 동기화 스코우프가 파이프라인의 가능한 한 앞쪽으로 설정되고 두 번째 동기화 스코우프가 가능한 한 가장 마지막의 파이프라인 스테이지로 지정되어야 합니다. scrStageMask 를 TOP_OF_PIPE_BIT 로 설정하는 것은 결코 배리어를 블락킹하지 않으며, 이는 첫 번째 동기화 스코우프가 비어 있는 것처럼 동작합니다. 이와 유사하게 dstStageMask 를 BOTTOM_OF_PIPE_BIT 로 설정하는 것은 두 번째 동기화 스코우프가 비어 있는 것을 의미하게 됩니다. 이러한 동작이 기대되는 경우들이 존재합니다 -- 다른 동기화( 예를 들어 세마포어 )가 이미 요청된 종속성들을 강제하고 있는 경우 -- 하지만 이런 옵션들은 주의깊게 사용되어야 합니다.


자세한 버퍼 동기화가 요구되지 않는 한은 전역 메모리 배리어들이 버퍼 메모리 배리어보다 선호됩니다. -- 예를 들어 버퍼의 특정 영역을 쓰는 동기화입니다. 같은 동기화 스코우프에 의존하는 이미지 메모리 배리어와 버퍼 메모리 배리어는 단일 vkCmdPipelineBarrier() 호출 내부에 배칭( batch )해야 합니다.


Recommendations


  • 소스 종속성을 파이프라인 내에서 가능한 한 앞쪽으로 설정하십시오. 그리고 대상 종속성을 가능한 한 늦게 설정하십시오.
  • 타일 기반 아키텍쳐 상에서 서브패스 내에서 설정되는 배리어들에 의해 만들어질 수 있는 파이프라인 버블에 조심하십시오.
  • 버퍼 메모리 배리어보다는 전역 메모리 배리어를 선호하십시오.
  • 이미지 메모리 배리어와 버퍼 메모리 배리어가 같은 스코우프를 사용하고 있다면 그것들은 단일 vkCmdPipelineBarrier() 호출 내에 배칭하십시오.
  • 용례에 기반한 권장 사항에 대해서 알고자 한다면 크로노스 그룹의 동기화 예제를 찾아 보십시오.


Events


이벤트들은 자세한 동기화 메커니즘을 제공합니다:


  • 호스트에서 그래픽스 큐로의 동기화.
  • 렌더 패스 내부에서의 커맨드 종속성들.


vkCmdWaitEvents() 는 vkCmdPipelineBarrier() 와 유사한 인자들을 취합니다. 추가적인 인자들은 eventCount 와 pEvents 입니다. pEvents 와 srcStageMask 에 의해 정의되는 동기화 스코우프는 vkCmdWaitEvents() 와 dstStageMask 뒤쪽의 커맨드들이 실행되기 전에 종료되어야 합니다.


호스트에서 그래픽스 큐로의 이벤트 동기화는 커맨드 버퍼에 쓰여진 리소스들에 대한 종속성이 있는 커맨드들 뒤쪽에서 리소스 쓰기가 발생할 필요가 있을 때 유용합니다. 예를 들어, 사용자 입력과 GPU 실행 사이의 지연을 줄이기 위해서, VR 합성기( compositor )는 타임 워프 합성이 수행되기 전에 유니폼 버퍼에 씬이 렌더링되므로 머리 방향 델타( delta )를 표현하는 행렬을 쓸 수 있습니다. vkCmdWaitEvents() 는 이 이벤트가 시그널링될 때까지 실행을 블로킹합니다. 하지만 너무 많은 시간이 걸리는 GPU 제출( submission )은 시스템에 의해 무시( kill )될 수 있다는 점을 기억하십시오. 그래서 이러한 접근법을 사용하려면 극도의 주의가 필요합니다.


Recommendations


  • 이벤트보다는 배리어를 사용하세요.


Wait idle


아이들 대기는 동기화에서 매우 무거운 형식입니다. vkQueueWaitIdle() 은 모든 큐 연산들이 종료될 때까지 대기하며 기능적으로는 펜스를 기다리는 것과 동일합니다. vkDeviceWaitIdle() 은 모든 디바이스 연산이 종료될 때까지 대기합니다. 아이들 대기 함수들은 중복이 없음이 보장하며, and should only be used for rendering engine tear down.


Recommendations


  • Only use WaitIdle functions for rendering engine tear down.


SwapChains


스왑체인을 생성할 때, VK_PRESENT_MODE_FIFO_KRH 프리젠테이션 모드를 사용하고 minImageCount 를 3 으로 설정하는 것을 권장합니다.


VK_PRESENT_MODE_MAILBOX_KHR 프리젠테이션 모드는 잠재적으로 프레임율을 안정화시켜 줍니다; 하지만 이는 렌더링된 전체 프레임을 날림( throwing )으로써 수행됩니다. 이는 GPU 가 필요 이상으로 무거워지도록 만듭니다( 그리고 결국 더 많은 전력을 소비합니다 ). FIFO 를 사용하여 프로우파일링하고 최적화할 것을 강력히 권장합니다. 그리고 MAILBOX 는 최소 지연이 절대적으로 요구될 때만 사용하십시오.


스왑체인 이미지의 개수를 결정하는 데 대한 주요 고려사항은 메모리 사용과 부드러움 사이의 균형입니다. 안드로이드는 2 개의 이미지만을 가진 스왑체인을 생성하는 것을 지원합니다. 이는 요구되는 메모리를 줄여주지만, 프레임이 v-sync 를 위한 시간 내에 렌더링되지 못한다면 렌더링 파이프라인에서 버블을 만듭니다. 3 개 보다 많은 스왑체인 이미지를 요청할 수 있기는 하지만, 이는 이러한 이점과 추가적인 메모리 소비량 사이에서 주의깊게 고민해 봐야 합니다.


그래픽스 큐와 프리젠테이션 큐를 동기화하기 위해서 세마포어를 사용해야 합니다. 펜스는 그래픽스 큐와 호스트를 동기화하기 위해 사용되어야 합니다. 더 많은 정보를 원한다면 다음 예제들을 참고하십시오:


  • 예제 : 위의 코드블락 1, 2.


Recommendations


  • VK_PRESENT_MODE_FIFO_KHR 프리젠테이션 모드를 사용하십시오.
  • 스왑체인의 minImageCount 를 3 으로 설정하십시오.
  • 그래픽스 큐와 프리젠테이션 큐를 동기화하기 위해 세마포어를 사용하십시오.
  • 그래픽스 큐와 호스트를 동기화하기 위해 펜스를 사용하십시오.


Minimizing overdraw


프래그먼트들은 프리미티브가 그래픽스 큐에 제출된 순서대로 래스터화( rasterize )됩니다. 여러 개의 프리미티브들이 겹쳐 있으면, 결과 프래그먼트들이 최종 이미지의 다른 것들에 의해 가려지는 상황이라고 할지라도 모든 프래그먼트들이 렌더링됩니다. 이후의 프래그먼트들에 의해 겹쳐지게 되는 값을 가진 프래그먼트들을 렌더링하는 것은 오우버드로( overdraw )라 알려져 있습니다.


일부 아키텍쳐들만 나중에 무시될 프래그먼트에 대한 셰이딩 부하를 줄이는 최적화를 포함하고 있습니다. 그럼에도 불구하고, 최상의 휴대용 최적화를 획득하기 위해서는, 초기 뎁스/스텐실 테스트를 사용하고 불투명 드로 호출들을 깊이 순서대로( 일반적으로 앞에서 뒤로, 뎁스 테스트 모드에 따라 다름 ) 제출하는 것을 권장합니다. 이는 GPU 가 셰이딩을 위한 가시적인 우선순위를 가지고 있는 프리미티브들이 어떤 것인지 판단할 수 있도록 해 줍니다. 투명 프리미티브들은 불투명 프리미티브들 후에 블렌딩 동작을 유지할 수 있는 순서로 렌더링되어야 합니다.


Recommendations


  • 불투명 드로를 앞에서 뒤로 정렬하고 나서 투명 프리미티브들을 렌더링하십시오. 순서 결정은 오우버드로 감소를 위해 완벽할 필요는 없습니다. CPU 정렬 부하와 오우버드로 감소 사이의 좋은 균형을 제공하는 알고리즘을 찾으십시오.


Avoid redundant API calls


렌더링없이 반복적으로 스테이트를 설정하는 것과 같은 Vulkan 중복 호출들은 병목을 유발하지 않습니다만, 여전히 비용이 듭니다.


Recommendations


  • API 를 중복호출하는 것을 피하십시오.


Robust Buffer Access


Vulkan 드라이버들은 API 가 그것을 호출하는 애플리케이션 내에서 올바르게 사용되고 있다고 가정합니다. 이러한 가정은 드라이버가 런타임 유효성 검사를 피할 수 있도록 해 줍니다. 애플리케이션들은 robustBufferAccess 기능을 활성화함으로써 더 강력한 신뢰성 보장을 요구할 수 있습니다. 신뢰성있는 버퍼 접근의 주요 목적은 버퍼 경계를 넘어서는 것과 관련한 검사들을 제공하는 것입니다. Vulkan 명세는 robustBufferAcess 가 물리 디바이스 상에서 이용가능한 기능임을 보장하지만, 그것을 활성화하는 것은 일부 아키텍쳐에서 심각한 성능 페널티를 발생시킬 수 있습니다.


Recommendations


  • robustBufferAccess 를 개발 단계에서 사용하여 버그를 잡으십시오. 릴리스 빌드에서는 비활성화하십시오.


Validation Layers


API 오용을 식별하기 위해서 개발 단계 동안 주기적으로 크로노스 그룹의 Vulkan 유효성 레이어들을 사용해야 합니다. 최상의 결과를 위해서, 최신 버전의 유효성 레이어들을 사용하는 것을 권장합니다. 유효성 레이어들의 소스 코드는 깃허브에서 호스팅되고 있으며 그것을 빌드하는 것은 직관적입니다. 각 레이어의 릴리스들에는 sdk-* 라는 태그가 붙어 있습니다.


이용가능한 레이어들의 하위 집합에 의존해서는 안 됩니다. 모든 잠재적인 문제들을 발견하기 위해서는 크로노스에서 제공하는 모든 레이어들을 주기적으로 사용할 것을 권장합니다.


렌더링 엔진은 서로 다른 디바이스 상에서 서로 다른 Vulkan 기능들( 예를 들어 텍스쳐 포맷 )을 사용할 것입니다. 그래서 여러 Galaxy 디바이스를 사용해 유효성 레이어들을 실행하는 것을 권장합니다. 게임을 릴리스하기 전에 모든 유효성 레이어 메시지들을 해결해야 합니다.


에러코드는 메시지에 특정되지 않습니다.그것들은 문제의 유형을 분류하며 하나 이상의 메시지들에 이해 재사용될 것입니다. 레이어 출력을 파싱하는 것을 권장하지는 않습니다. 왜냐하면 에러 코드와 메시지 텍스트는 유효성 레이어 릴리스마다 다양하기 때문입니다.


레이어에 의해 보고된 메시지의 의미가 명확하지 않다면, 원인을 더 잘 이해하기 위해서 유효성 소스 코드를 검색해 볼 것을 권장합니다.


잘못된 긍정( positive )이나 잘못된 API 용례가 발견되지 않는 시나리오들을 인지했다면, 깃허브의 레이어에 대한 문제에다가 보고해 주십시오.


크로노스 유효성 레이어들에 대해 더 알고자 한다면, LunarG 의 Vulkan Validation Layers Deep Dive 슬라이드를 읽어보실 것을 권장합니다.


Recommendations


  • 크로노스 유효성 레이어들을 개발 단계에서 주기적으로 사용하십시오.
  • 항상 최신 버전의 레이어 릴리스를 사용하십시오.
  • 다중의 디바이스 상에서 레이어들을 사용해 디바이스-지정 메시지들을 수집하십시오.
  • 주의 : 에러 코드들은 문제를 분류합니다 -- 그것들은 주어진 메시지에 대한 유일한 식별자가 아닙니다.
  • 메시지가 불명확하다면, 레이어 소스 코드를 참고하십시오.


원문 : https://gpuopen.com/performance-root-signature-descriptor-sets/


주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.


주의 : 허락받고 번역한 것이 아니라 언제든 내려갈 수 있습니다.



Direct3D 12 와 Vulkan 이전에는 리소스들이 "슬롯( slot )" 을 통해서 셰이더에 바인딩되었습니다. 여러분들 중 일부는 하드웨어가 첫 번째 유닛에 텍스쳐를 바인딩하고 두 번째 유닛에 라이트 맵을 바인딩할 것을 요구했던 소수의 고정함수( fixed-function ) 유닛( unit )들만을 가지고 있었다는 것을 기억할 것입니다. OpenGL 와 Direct3D 11 까지의 바인딩 시스템은 이러한 잔재( heritage )를 보여줍니다. 두 API 에서는 심지어 하드웨어가 이 모델에서 발전했음에도 불구하고 리소스를 바인딩할 수 있는 슬롯 집합이 존재했습니다.


새로운 바인딩 모델에 대해서 살펴 보기 전에, 현대의 GCN 기반 GPU 가 리소스를 식별하는 방법에 대해서 살펴 보겠습니다. 우리가 텍스쳐( texture )에 접근하려고 한다고 해 봅시다 -- 이것이 어떻게 텍스쳐 샘플링 명령( instruction )으로 전달되는 것일까요? GCN ISA 문서를 살펴 보면, 다음과 같은 구절을 찾을 수 있을 것입니다:


모든 벡터 메모리 연산( operation )들은 이미지 리소스 상수( constant ) (T#) 을 사용하는데, 그것은 SGPR 들 안의 128 비트 혹은 256 비트 값입니다. 이 상수는 명령이 실행될 때 텍스쳐 캐시로 전송됩니다. 이 상수는 메모리 상의 서피스에 대한 주소, 데이터 포맷, 특성( characteristics )을 정의합니다.


그러므로 이제 슬롯으로 사용되는 것은 텍스쳐 샘플링 명령으로 넘겨지는 레지스터 집합( couple of registers )들입니다. 다음 구절은 더 흥미롭습니다:


일반적으로 이 상수들은 메모리로부터 페치( fetch, 전송 )됩니다.


이는 텍스쳐를 기술하기 위해서 필요로 하는 모든 것들이 메모리의 아무 곳에나 배치될 수 있는 작은 디스크립터( descriptor, 기술자 )( 128 비트 혹은 256 비트 )라는 것을 의미합니다. 그것이 레지스터에 로드되어 있는 한 올바르게 진행되고 있는 것입니다. 이 문서의 나머지 부분을 읽게 되면, 모든 다른 리소스 유형들에 대해서도 같은 패턴이 사용되고 있다는 것을 알게 될 것입니다. 사실, 리소스 접근을 하게 될 때 "슬롯" 이라는 것은 존재하지 않습니다 -- 대신에 모든 것은 텍스쳐 디스크립터( 혹은 T# ), 샘플러( sampler ) 디스크립터( S# ), 혹은 상수 ( V# ) 을 통해서 진행됩니다. Direct3D 12 와 Vulkan 을 사용하면, 이러한 디스크립터들이 최종적으로 그 자체로서 노출됩니다 -- 일부 GPU 메모리.


GCN 하드웨어에는 특별한 레지스터 집합 -- "사용자 레지스터" 라 불림 -- 들이 존재하는데, 이것들은 디스크립터( 그리고 상수들 -- 아래에서 더 살펴 볼 것임 )들을 저장하는 데 사용됩니다. 이런 레지스터들의 개수는 디스크립터 저장소마다 다른데요, 셰이더 스테이지( stage ), PSO , 드라이버에 의존합니다. 하지만 일반적으로 대충 12 개( dozen ) 정도 됩니다. 각 레지스터는 디스크립터, 상수, 포인터를 사용해 프리로드( pre-load )될 수 있습니다. 레지스터 공간이 넘치면, 드라이버가 메모리의 spill space 를 사용해 더 큰 테이블( table )을 에뮬레이트( emulate )합니다; 이 때 CPU 비용( spill table 관리 -- spill 영역이 변경될 때마다 발생 )과 GPU 비용( 추가적인 포인터 간접 참조( indirection ) )이 모두 발생합니다. 결과적으로, 일제히 변경하는 디스크립터들이나 상수들의 블록들을 가지고 있다면, 그것들을 개별적으로 저장하고 포인터를 사용하여 그것들이 넘치지 않도록 하는 것이 더 낫습니다.


Vulkan resource binding


Vulkan 에서 바인딩 모델은 다음과 같이 설명될 수 있습니다. 디스크립터들은 디스크립터 셋에 배치되고, 하나 이상의 디스크립터 셋을 바인딩할 수 있습니다. 디스크립터 셋 내부에서는 모든 디스크립터 유형들을 자유롭게 섞을 수 있습니다. 또한 레지스터에 프리로드할 수 있는 상수들인 "push constants" 들도 존재합니다.



왼쪽의 블록은 API 에서 암시적( implicit )입니다 -- 여러분은 현재 바인딩되어 있는 것을 볼 수 없습니다 -- 그리고 오른쪽의 블록들은 개별 디스크립터 셋들입니다. 위에서 배웠듯이, 디스크립터 셋들은 플레인 메모리( plain memory, 역주 : 메모리의 구조가 명확함 )입니다. 그래서 모든 패킹( packing ), 캐시 적중( cache hit ) 등과 같은 모든 조언들이 여기에도 적용됩니다. 같이 사용하려고 하는 디스크립터를 가깝게 배치하십시오. 가능하면 인접한( consecutive ) 엔트리( entry )들을 로드하십시오. 메모리 주소들에 접근할 때 큰 점프( jump )를 피하십시오. 텍스쳐와 함께 사용하고자 하는 샘플러들에 대한 정보를 더 알고 있다면, 그것들을 샘플러와 텍스쳐를 가깝게 배치하는 "combined image sampler" 로 결합함으로써 디스크립터 셋을 최적화할 수 있습니다.


동적 버퍼( dynamic buffer ) 디스크립터들에 대해서 궁금할 것입니다: 그것들은 버퍼와 옵셋( offset )에 대한 접근을 제공합니다. 그것들은 기본적으로 플레인 상수를 가진 혼합 버퍼 디스크립터입니다. 이 상수들은 레지스터들을 사용하게 되므로, 많은 동적 버퍼들에서 문제가 될 수 있습니다. 이를 우회하기 위한 두 가지 방법이 존재합니다. 인덱싱( indexing )중인 데이터가 동일한 너비( uniform stride )를 가지고 있다면, 그냥 그 버퍼들을 바인딩하고 단일 상수( 혹은 다른 버퍼 내에 저장된 다중 상수들 )를 사용해 인덱싱하면 됩니다. 만약 너비가 고정되지 않았지만 옵셋을 알고 있다면, 디스크립터 배열을 생성하고 대상 위치로 점프하도록 인덱싱할 수 있습니다. 다시 말해, 여러분이 처음 사용하는 인덱스가 단일 상수인지 다른 버퍼로부터의 인덱스인지 확인해야 합니다.


Direct3D resource binding


Direct3D 는 루트 시그너쳐( root signature )를 통해서 암시적 바인딩을 노출하는데, 이는 실시간에 관리됩니다. 이는 위에서 설명했던 사용자 레지스터들 상에서 매우 직관적으로 매핑합니다. 하지만 API 에는 몇 가지 제약들이 존재합니다: 테이블 내의 다른 디스크립터들을 사용해 샘플러 디스크립터들을 저장할 수 없습니다. 또한 루트 시그너쳐에는 디스크립터 테이블, 루트 시그너쳐, 버퍼 포인터에 대한 포인터만이 포함될 수 있습니다. 컴파일타임에 샘플러 디스크립터를 지정할 수 있는 한 가지 대안이 있기는 하지만, 텍스처 디스크립터나 샘플러 디스크립터를 루트 시그너쳐에 배치할 수는 없습니다.


혼합된 텍스쳐 디스크립터와 샘플러 디스크립터는 존재하지 않습니다; 대신에 분리된 디스크립터 힙( heap )들에 저장되어야만 합니다.



그것을 제외하고는 바인딩 모델은 Vulkan 과 동일합니다. 그리고 디스크립터를 통해 리소스들에 접근할 것을 요구합니다. 둘의 주요한 차이는 루트 시그너쳐가 특정한 종류의 디스크립터들이 인라인( in-line )으로 저장되는 것을 허용한다는 것입니다. 반면에 Vulkan 은 모든 경우에 디스크립터 셋을 통해서 진행하는 것을 기대합니다.


Performance guidelines


최적화의 시작점은 루트 시그너쳐를 가능한 한 작게 유지하는 것입니다. 루트 시그너쳐가 커진다는 것은 spill 이 될 확률이 올라가고 파라미터들을 유효화( validating )할 때 드라이버에 전달될 더 많은 엔트리들이 필요함을 의미합니다. 이는 디스크립터 테이블을 적게 바인딩하고, 큰 상수들을 설정하는 것을 피하고, 피할 수 있다면 Direct3D 에서 버퍼 뷰를 사용하지 않아야 함을 의미합니다. 단일 상수 버퍼 뷰가 용납될 수 있기는 하지만, 항상 바인딩되어 있는 큰 상수 버퍼 내의 옵셋을 제공하는 상수를 사용하는 것이 좋습니다. 아래의 루트 시그너쳐를 살펴 봅시다:



그것은 포인터로 시작해서, 몇 개의 루트 상수들, 몇 개의 포인터들, 그리고 두 개의 디스크립터들로 끝납니다. 그 두 개의 디스크립터들이 넘쳐흐를 가능성이 존재하므로, 드라이버가 관리하는 오우버플로우( overflow ) 버퍼들을 만나게 될 것입니다. 루트 시그너쳐를 가능한 한 작게 유지해서 이러한 문제를 피하십시오!


어쨌든 항상 가장 자주 변경되는 파라미터들을 목록의 앞에 유지하도록 노력해야 합니다. 만약 spill 이 발생하면 주파수가 급격히 떨어지며, 파라미터들을 유효화시 드라이버 부하도 줄어들 것입니다. 이는 드라이버가 오우버플로우 처리 상수( overflow handling constant )를 유지할 수 있도록 합니다. 그리고 가장 자주 변경되는 엔트리가 성능을 위해 레지스터에 남아 있도록 합니다.


오늘은 이만! 질문이 있다면 자유롭게 댓글을 남기거나 Twitter 에서 ping 해 주세요:  @NThibieroz & @NIV_Anteru.


Tweets


  • 07: Keep your root descriptor set small and place most frequently changed entries first.
  • 17: Order root signature parameters by change frequency (most frequent to least frequent).
  • 27: Keep your descriptor sets constant, reference resources that belong together in the same set.
  • 36: Use as few root signature slots as possible. Frequently updated slots should be grouped at the beginning of the signature
  • 44: Try to make root signatures as small as possible (create multiple root signatures if needs be).
  • 49: If using multiple root signatures, order/batch draw calls by root signature.
  • 54: Only constants or constant buffers changing every draw should be in the root signature.


Matthäus Chajdas is a developer technology engineer at AMD. Links to third party sites, and references to third party trademarks, are provided for convenience and illustrative purposes only. Unless explicitly stated, AMD is not responsible for the contents of such links, and no third party endorsement of AMD or any of its products is implied.

주의 : 초심자 튜토리얼은 아닙니다. 그러므로, 실제 API 호출 용례를 알고자 한다면, 샘플이나 튜토리얼을 찾아서 확인해 보세요.

주의 : 완전히 이해하고 작성한 글이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 확인하세요.



D3D 에 익숙한 사람이 벌칸을 할 때 발견하게 되는 어려운 개념 중에 하나는 디스크립터 셋 레이아웃( Descriptor Set Layout )입니다. 일단 디스크립터라는 것의 개념 자체를 모르는 상황이기 때문에 당연합니다.


사실 어려운 개념은 아니지만 생소하기 때문에 어렵게 느껴집니다. 디스크립터에 대해서 이해하기 위해서는 벌칸 명세에 있는 파이프라인 블록 다이어그램을 보는 것이 도움이 됩니다.


그림 1. Block diagram of the Vulkan pipeline. 출처 : 1 ].


그림 1 에 나타나 있듯이 디스크립터는 셰이더에 바인딩할 수 있는 리소스에 대한 정보를 의미하며 그것들의 모임이 디스크립터 셋이 됩니다.


이제 명세로부터 정확한 정의를 찾아 보도록 하죠.


디스크립터는 버퍼, 버퍼 뷰, 이미지 뷰, 조합된 이미지 샘플러와 같은 셰이더 리소스들을 표현하는 불투명( opaque ) 데이터 구조입니다. 디스크립터들은 디스크립터 셋을 구성하는데요, 그것들은 일련의 드로 커맨드들 내에서 사용할 커맨드들을 기록하는 동안 바인딩됩니다. 각 디스크립터 셋에서 칸텐트의 배열( arrangement )은 디스크립터 셋 레이아웃에 의해 결정됩니다. 그것은 어떤 디스크립터들이 저장되어 있는지를 결정합니다. 일련의 디스크립터 셋 레이아웃들은 파이프라인 레이아웃( pipeline layout )에 지정되어 있는 파이프라인에 의해 사용될 수 있습니다. 각 파이프라인 오브젝트들은 maxBoundDescriptorSets 만큼 까지의 디스크립터 셋들을 사용할 수 있습니다.


셰이더들은 디스크립터 셋과 디스크립터 셋 내에서 리소스와 디스크립터를 연결하는 바인딩 번호가 부여된( decorated ) 변수들을 통해 리소스들에 접근하게 됩니다. 디스크립터 셋들을 바운딩하기 위한 셰이더 인터페이스 매핑( shader interface mapping )에 대해서는 Shader Resource Interface 섹션에서 다루고 있습니다.


출처 : Chapter 13. Resource Descriptors, [ 1 ].


우리는 디스크립터 셋 레이아웃을 렌더패스/서브패스 개념과 유사하게 생각해 볼 수 있습니다. 렌더패스와 서브패스는 프레임버퍼의 설계도처럼 동작합니다. 렌더패스와 서브패스가 프레임버퍼의 바인딩 구조를 결정하고 실제 렌더링시에는 프레임버퍼가 바인딩됩니다. 그리고 이 프레임버퍼에는 실제 이미지 뷰들이 바인딩되어 있죠. 


마찬가지로 디스크립터 셋 레이아웃은 디스크립터 셋의 설계도라고 생각하시면 됩니다. 그리고 실제 렌더링시에는 디스크립터의 묶음인 디스크립터 셋이 바인딩되는 것입니다.


그림 2. 디스크립터 셋 레이아웃. 출처 : [ 2 ] 의 7 분 47 초 부분.


디스크립터 종류는 VkDescriptorType 에 기술되어 있습니다.



그런데 이 디스크립터라는 것은 리소스 오브젝트를 의미하고 있기 때문에 실제로는 디스크립터라는 오브젝트가 따로 존재하지는 않습니다. 디스크립터 셋에 의해서 디스크립터라는 것이 규정되는 것이죠. 마치 프레임버퍼라는 오브젝트에 이미지 뷰를 바인딩하는 것과 유사합니다.


디스크립터 셋은 디스크립터 풀( Descriptor Pool )인 VkDescriptorPool 오브젝트에 할당됩니다. 디스크립터 셋이라는 것은 셰이더의 종류가 늘어나면 많이 존재할 수 있기 때문에 풀이 필요합니다. 


풀은 외부 동기화( externally synchronized )되기 때문에 여러 스레드에서 동시에 접근해서는 안 됩니다. 그러므로 가급적이면 스레드마다 하나씩 존재하거나 직접 동기화 처리를 해야겠죠. 하지만 동기화 비용을 없애기 위해서는 스레드 별로 존재하는 것이 좋을 것입니다.


이 시점에 다음과 같은 그림을 그려 볼 수 있습니다.



그림 3. 디스크립터 풀과 디스크립터 셋.


이것을 파이프라인 스테이지와 연동시켜서 보면 다음과 같은 형태를 띠게 됩니다.


그림 4. 파이프라인 스테이지와 디스크립터 셋 레이아웃. 출처 : [ 3 ].


그런데 여기에서 하나 알아 둘 것이 있습니다. 명세에서는 파이프라인 내의 스테이지들이 같은 바인딩 번호를 통해 리소스에 접근할 수 있다고 하고 있습니다.


그림 4 를 보시면 알겠지만, 하나의 파이프라인에 여러 개의 디스크립터 셋들이 바인딩될 수 있습니다. 그러므로 그 디스크립터 셋들을 정의하면서 바인딩 번호를 동일하게 설정하면 같은 데이터에 접근하는 것이 가능합니다. 셰이더에서 레이아웃은 다음과 같은 식으로 지정됩니다.



만약 HLSL 과 DXC 를 사용한다면 register 를 통해서 디스크립터 셋의 레이아웃을 지정할 수 있습니다.



'X' 는 바인딩 번호이고, 'Y' 는 디스크립터 셋을 의미합니다. 


그런데 register 에 의존하지 않고 [[vk::binding(X[, Y])]] 와 [[vk::counter_binding(X)]] 애트리뷰트를 사용하는 것도 가능합니다. 자세한 사항은 [ HLSL to SPIR-V Feature Mapping Manual ] 에서 확인하시기 바랍니다.


튜토리얼 개념으로 쓴 글이 아니다 보니 여기에서 구체적인 API 와 코드에 대해서 집중적으로 다루지는 않았습니다. 개념을 이해하는 것이 중요하다는 생각에 정리해 봤으니 자세한 코드는 튜토리얼들에서 살펴 보시기 바랍니다. 잘못된 내용이 있으면 지적해 주시기 바랍니다.


참고자료


[ 1 ] Vulkan Specification 1.1.123. Khronos Group.


[ 2 ] Vulkan Tutorial 5: Triangle Sample Walkthrough, Qualcomm Developer Network.


[ 3 ] Siggraph 2016 - Vulkan and NVidia : the essentials.

원문 : https://www.micromine.com/what-is-implicit-modelling/


주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.


주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.


정보 : 본문에서는 수학적 개념을 다루고 있으므로 "implicit function" 을 "음함수", "explicit function "을 "양함수", "parametric function" 을 "매개변수함수"라 번역합니다.



Paul Hooykaas

November 19, 2014


Introduction


모든 사람들이 음함수 모델링( implicit modeling ) 행사에 뛰어 들고 싶어하는 것으로 보입니다. Orefind 블로그 페이지에서는 이 유행을 반영하는데, 2012 년 후반부터 적어도 6 개의 관련 기사들이 있습니다 -- Jun Cowan 과 Ron Reid 가 주목할 만한 기여자들입니다.


불행하게도, 업계에서는 "음함수 모델링" 이라는 개념에 대한 일과된 이해가 없어 보입니다. 제가 불행하다라고 이야기한 이유는 원래의 정의는 "진짜" 라는 한정자를 필요로 하지 않음에도 리뷰어( reviewer )들이 이제 "진짜 암시적 모델링" 이라고 부르를 정도까지 타협이 일어나고 있기 때문입니다.


그래서 여기에서는 음함수 모델링이 실제로는 무엇인지 명확하게 해 보려고 합니다. 이 개념은 광물 자원 분야( mineral resources sector )에서는 상대적으로 새로 등장한 것입니다만, 컴퓨터 그래픽스 업계는 1960 년 후반부터 기반 기법들을 사용해 오고 있습니다. Jules Bloomenthal 의 ( 1995 년 1 월의 ) 논문인 Skeletal Design of Natural Forms 으로부터 발췌한 내용을 고려해 봅시다.


기하 모델링( geometric modeling )은 종종 파라미터( parametric ) 기법과 음함수( implicit ) 기법으로 분류됩니다. 둘 다 컴퓨터 그래픽스로부터 개발되었습니다(  [Coons 1967] 에서 초기 파라미터 기법이 소개되었고, [Mathematical Applications Group 1968] 에서 초기 음함수 기법이 소개되었습니다. 음함수 서피스들은 더욱 손쉽게 볼륨 관계( volumetric relationship )들을 수집( capture )합니다...


수학에서 "음함수" 라는 단어는 더 오랜 역사를 가지고 있으며, 그것은 특별한 유형의 함수를 기술하기 위해서 사용됩니다.


Implicit or Explicit


"음함수 모델링" 은 2003 년 11 월 벤디고( Bendigo )에서 열린 5 회 국제 광산 지질학 컨퍼런스( international mining geology conference )에서 제출된 논문에서 Jun Cowan 에 의해 탐사( exploration ) 업계와 광산 업계에 소개되었습니다. 이 기사의 많은 부분들은 그 논문의 내용을 반복한 것입니다.


아마도 음함수 모델링과 관련된 개념들을 강조하기 위해서, 그 논문은 ( 수작업 디지털화( manual digitizing )에 매우 의존하는 ) 전통적인 모델링 기법들을 '양함수' 라고 기술한 것 같습니다. 필자는 이러한 언급이 무심코 오늘날 우리가 인지하게 된 혼란의 일부 원인이 된 것이라 생각합니다. 매일 대화를 하면서 '음함수' 와 '양함수' 라는 단어들은 반대 의미로 쓰입니다( 후자는 "완전히 명확하게 정의된" 을 의미하고 전자는 "직접적으로 표현되지 않은" 을 의미합니다 ). 두 단어들을 모델링 프로세스( process )와 연관시킴으로써, 모든 메서드들이 이 두 가지 분류 중 하나로 들어 가야 한다고 여겨지게 되었습니다. 양함수 기법을 이해하는 것이 쉬웠기 때문에, 수작업 디지털화를 배제한 적절한 출력을 생성하는 모든 프로시저( procedure )들을 "음함수" 라고 여겨지도록 분류하는 것이 편했습니다. 예를 들어, 3D 블록 모델로부터 하나 이상의 등급 셸( grade cell )을 생성하는 것은 음함수 모델링의 형태라고 주장하는 것이 일반적( popular )입니다. 이것은 '음함수' 이라는 기술자( descriptor )가 일반적인 용법이 아니라 수학적인 의미에서 생겨났다는 것을 알게 되면 그리 이상한 것은 아닙니다.


Curves and Surfaces


이 주제에 대한 많은 참고 자료( reference material )들은 '커브( curve )' 와 '서피스( surface )' 에 대해 언급합니다. 커브와 서피스의 수학적 정의에 대해서 언급하는 것은 중요합니다. 왜냐하면 그것들은 이런 개념들에 대한 표준적인 이해와는 약간 다르기 때문입니다:


  • 커브는 라인( line )과 유사하지만 직선일 것을 요구받지 않습니다. 열린( open ) 커브는 시작 점과 끝 점이 다릅니다. 닫힌( closed ) 커브는 시작점과 끝이 만납니다. 그러므로 닫힌 커브에는 끝 점이 없습니다.
  • 놀랍게도 서피스의 ( 기하학적( geometric ) ) 정의는 '매니폴드( manifold )' 와 '유클리드( Euclidean )' 과 같은 기술적인 개념들로 빠르게 어수선해질 수 있습니다. 단순한 설명은 "솔리드( solid, 역주 : 안이 채워진 도형? ) 경계의 일부 혹은 전체" 입니다. 그런 서피스들의 중요한 점은 커브처럼 열려있거나 닫혀있다는 것입니다. 평면( plane )은 열린 서피스의 단순한 예입니다. 그리고 구( sphere )는 닫힌 서피스의 단순한 예입니다. 이것을 바라보는 다른 방식은 열린 서피스가 면적을 가지고 있고 닫힌 서피스가 부피를 가지고 있다는 것입니다.


Function Types


자, 수학 문맥에서 "음함수" 는 어떤 의미일까요? 수학에서는 ( 커브의 ) x, y 사이의 관계나 ( 서피스의 ) x, y, z 사이의 관계를 함수라고 부르며, 세 가지 종류의 구별되는 함수 유형이 존재합니다: 양함수, 음함수, 매개변수함수.


양함수:


일반 형태: y = f(x)


등차식 왼쪽의 변수는 등차식 오른쪽의 다른 변수의 항들로 표현됩니다.


Circle: y = √( r2 - x2 )


양함수들은 단일 값들을 가집니다.


이는 원을 정의하기 위해서는 두 개의 개별적인 양함수들이 요구된다는 것을 의미합니다.


  1. 위쪽 반원, y = +√( r2 - x2 )
  2. 아래쪽 반원, y = -√( r2 - x2 )


음함수:


일반 형태: f( x, y ) = 0


0 이 되는 항들의 집합을 설정함으로써 정의됩니다.


Circle: x2 + y2 - r2 = 0


매개변수함수:


일반 형태: x = Fx( t ), y = Fy( t )


출력 요소들이 파라미터나 파라미터들에 기반합니다.


Circle: x = r * cos( t ), y = r * sin( t )



노트 : 


  1. 위는 변수 x 와 y 를 사용하는 커브( 혹은 라인 )에 기반합니다. z 변수를 사용함으로써 서피스를 포함하는 논의로 확장하는 것이 가능합니다.
  2. 함수와 공식( equation ) 사이에는 약간의 차이가 있습니다만, 여기에서는 무시합니다.


양함수들은 x 값으로부터 직접적으로 y 값을 생성합니다. 그래서 원 상에 존재하는 점들의 수열을 생성하는 것이 상대적으로 쉽습니다. 하지만 ( 닫힌 라인이나 서피스가 지원되지 않는 ) 단일 값 제약( single value restriction )에 의해 양함수 형태는 우리 목적에 맞지 않게 됩니다.


음함수들은 "implicit" 라는 단어의 일반적인 용법과 연결됩니다. 원 예제에서 음함수는 x2 + y2 - r2 = 0 이라는 것을 기억하세요. 이에 기반해서 특정 반지름( 2 라고 하죠 )이 주어졌을 때 원을 어떻게 그려야 할 것인지 생각해 보세요. 원 위에 있는 연속된 점들을 체계적으로( systematically ) 생성할 수 있는 직접적인 방법이 존재하지 않습니다. 그리고 이것이 이 표현이 음함수라고 불리는 이유입니다. 원이 거기 존재한다 -- 확실히 잘 정의되어 있습니다 -- 는 것을 알고 있습니다만, 그것을 가시적으로 표현하는 것은 문제입니다.


하지만 음함수 형태는 자신만의 특별한 특성을 가지고 있습니다. ( 반지름이 알려진 값인 ) 특별한 원의 경우에, 우리는 모든 x, y 를 공식에 대입할 수 있습니다. 만약 그 결과가 양수라면 그 점은 원 밖에 존재합니다. 만약 결과가 음수라면 그 점은 원 안에 존재합니다. 만약 결과가 0 이라면 그 점은 원 위에 존재합니다. 다시 말해 모든 점에 대한 '분류( classification )'를 쉽게 할 수 있습니다.


한편으로 파라미터 표현은 원을 그리는 매우 편리한 방법을 제공합니다. t = 0 에서 시작해 x 와 y 를 계산합니다. 이제 적은 양만큼 t 를 증가시키면서 다음 x 와 y 를 계산합니다. 이를 t = 2π 가 될 때까지 계속 진행합니다...


An Implicit Model


변수를 포함하고 있지만 측정 가능한 속성( attribute )을 가진 3D 볼륨( volume )에 대해 생각해 보죠. '속성' 은 광석( ore ) 몸체의 밀도( density ), 온도( temperature ), 등급( grade )일 수 있습니다. 실제 측정값들은 분산된 데이터 포인트들로부터 취해집니다. 그 데이터 포인트들은 볼륨 상의 속성에 대한 연속적인 수학적 표현을 제공하기 위한 음함수들을 유도하기 위해서 사용될 수 있습니다. 이 음함수 모델은 무한한 개수의 isosurface -- 상수값의 점들을 표현하는 서피스 -- 들을 포함합니다. 등급 셸은 isosurface 에 대한 친숙한 예입니다.


하지만 음함수 모델 자체는 가시화될 수 없습니다. 그렇게 하기 위해서는 모델로부터 하나 이상의 isosurface 들을 추출하고 이를 3D 공간에 그려야 합니다. 만약 모델이 파라미터 형식으로 변환될 수 있다면 isosurface 추출은 쉬워질 것입니다. 하지만 사는 게 그렇게 쉽지는 않습니다. 동일한 파라미터 해법을 찾아내는 것은 너무 멀리 있는 희망입니다.


원 예제에서 살펴 봤듯이, 음함수 모델은 체계적으로 isosurface 상의 점들을 생성하기 위한 직접적인 방법을 가지고 있지 않습니다. 그것은 점이 그 서피스 상에 존재하는 지, 위쪽이나 아래쪽에 존재하는지를 결정하는 테스트를 제공합니다. 하지만 서피스 점들 자체를 생성하는 명시적인 규칙을 제공하지는 않습니다. 


The Definition (finally)


음함수 모델은 볼륨 상의 속성들에 대한 연속적인 수학적 표현입니다. 그것은 무한한 세부적인( fine ) 해상도를 가지고 있습니다. 이 모델로부터 눈에 보이는 서피스들을 생성하는 것은 개별적인 다음 단계입니다. 그리고 그것은 음함수 모델을 생성하는 것과는 별개입니다.


등급 셸을 블록 모델로부터 생성하는 시나리오를 다시 살펴 보죠. 위의 정의를 이용하면 두 가지 이유로 실패할 것입니다. 첫 번째로 그 모델은 삼차원 공간 상의 연속적인 등급이 아닌 개별적인 등급값을 가진 이산적인( discrete ) 점 집합입니다. 두 번째로 출력 서피스의 해상도는 원래 모델의 블록 크기에 의존합니다.


Isosurface Extraction


음함수 모델링은 일반적으로 세 부분으로 구별됩니다:


  1. 적절한 형식으로 데이터를 조직하기.
  2. 연속적인 볼륨 모델을 생성하기( 음함수 모델 ).
  3. 모델에 포함된 하나 이상의 서피스들을 출력하기.


출력은 거의 항상 삼각화된( triangulated ) 메시나 와이어프레임( wireframe )입니다. 삼각형은 직선으로 구성되므로, 출력 와이어프레임은 모델링된 서피스의 근사치( approximation )입니다. 메시를 더 세부적으로 만들수록 ( 더 작은 삼각형으로 만들수록 ) 더 나은 근사치가 나오며 외형이 더 부드러워집니다. 메시의 크기는 외형과 계산 시간 사이의 타협점입니다.


서피스 점들을 생성하는 직접적인 방법이 존재하지 않기 때문에, 간접적인 수단에 의존해야 합니다. 가장 일반적인 기법들 중의 하나는 "마칭 큐브( marching cube )"라 불립니다.



그 원칙은 2D 원 예제에서 "마칭 스퀘어( marching square )" 를 사용해서 ( 가장 단순하게 ) 설명될 수 있습니다. 좌상단에 위치한 정사각형으로부터 시작해, 정사각형의 모서리( corner )에서 음함수를 평가합니다. 만약 모든 모서리들이 양수값을 반환한다면, 그 정사각형은 완전히 원 바깥쪽에 존재합니다. 마찬가지로, 모든 모서리가 음수값을 반환한다면, 그 정사각형은 완전히 원 안쪽에 존재합니다. 만약 일부 모서리는 원 바깥에 존재하고 일부 모서리는 원 안쪽에 존재한다면, 그 정사각형은 원과 겹쳐 있는 것이고, 겹치는 라인을 계산할 수 있습니다. 만약 정사각형과 동일한 크기의 그리드를 가진 영역을 다루고 있다면, 원을 근사계산하는 라인 세그먼트( segment ) 집합을 결과로 작업을 끝내게 될 것입니다. 정사각형이 더 작아질 수록, 더 많은 개수의 라인 세그먼트들이 생겨나고 그 결과는 더 부드러워집니다.


3D 문맥에서, 큐브의 크기는 최종 서피스의 해상도를 제어합니다. 아래에 있는 스크린샷( screenshot )에는 두 개의 드래곤이 있는데요, 같은 음함수 모델을 사용하고 있지만, 서피스들이 서로 다른 메시 크기를 사용해서 생성되었습니다.



Radial basis functions


음함수 모델링은 언제나( invariably ) Radial Basis Functions( RBF ) 와 연관되어 있습니다. 이것들은 분산된 데이터 보간 문제를 해결하는 가장 정확하고 안정적인 기법 중의 하나로 인식되고 있습니다.


기저 함수들은 수학적 레고( Lego )로 기술됩니다. 그것들은 기본적인 기능 빌딩 블록( functional building block )들의 집합인데, 그 블록들은 우리가 필요로 하는 기능들을 제공하기 위해 서로의 위쪽에 쌓일( stack )수 있습니다.


Radial 기저 함수는 거리에 기반해 값들을 생성하는 함수들의 그룹입니다. 그래서 특정 점에 가까워질 수록 더 많은 영향이 발생합니다. 가장 일반적인 유형들은 다음과 같습니다:


  • Gaussian.
  • Multiquadric.
  • Inverse quadratic.
  • Inverse multiquadric.
  • Polyharmonic spline.


내 조사( survey )에 영향을 미친 것은 Grady Wright 가 2003 년에 작성한 논문입니다. 그는 RBF 개발이 초기에는 ( 1968 ) 자동화되고( automated ) 선입견없는( unbiased ) 등고선( contour ) 지도를 만들기 위해서 개발되었다고 언급했습니다. 


Summary


필자는 최근에 지질학자( geologist )들과 광산 기술자( engineer )들에게 음함수 모델에 대해서 어떻게 생각하는지에 대해 묻고 있습니다. 그 답변들은 다양했고 보통 명확하지 못했습니다. 자원 업계는 이 모델링 기술에 지난 10 년 동안 노출되어 왔습니다. 하지만 그 방법론은 더 오래전부터 존재했던 기하학적 프로세스들에서 채택되어 왔습니다. 우리는 음함수 모델링의 의미를 수집하기 위해 수학적 흔적을 따라야 합니다. 그렇게 하면, 명확한 정의가 보입니다. 그것은 볼륨 상의 수치적 속성들에 대한 연속적인 표현입니다. 그 모델은 x, y, z 항들에 대한 수학적 함수입니다. 보통 볼륨이 신발장 모양( shoe box shape )을 가지는게 편리합니다. 정의된 범위 내의 좌표계에서 xyz 위치를 삽입하면, 해당 위치의 속성값을 계산할 수 있습니다. 이것이 "연속적인 표현"이 의미하는 바입니다.


그 모델 자체를 가시화하는 직접적인 방법은 존재하지 않습니다. 이를 위해서는 추가적이고 독립적인 단계가 필요하며, 모델링되고 있는 속성들에 대한 하나 이상의 3D 등고이나 isosurface 들이 추출되는 것을 내포합니다.


우리 업계가 이 정의를 수용하는지 혹은 현재의 모호함을 계속 유지하게 되는지 여부는 여전히 남아 있습니다. 현재의 AusIMM "Mineral Resource and Ore Reserve Estimation -- Guide to Good Practice" 에는 "음함수 모델링" 에 대한 20 개가 넘는 참조들이 존재합니다. 이것은 이 방법론이 진지하게 수용되고 있음을 보여주며, 업계가 그 의미를 일관되게 이해하는 것이 더 중요해 집니다.


References


http://www.orefind.com/docs/orefind-research-papers-in-pdf/pigm_hr.pdf


http://www.unchainedgeometry.com/jbloom/dissertation.html


http://www.psych.mcgill.ca/misc/fda/ex-basis-a1.html


http://mathinsight.org/surface_defined_implicitly


http://www.wdv.com/Math/Calculus/Lecture01.pdf


http://amath.colorado.edu/faculty/fornberg/Docs/GradyWrightThesis.pdf


http://0fps.net/2012/07/10/smooth-voxel-terrain-part-1/

Blinn 과 Newell 은 1976 년에 reflection mapping( environment mapping 이라 불리기도 함 ) 이라는 기법을 소개했는데요, 이는 거울같은 서피스로부터의 반사를 시뮬레이션합니다. 그 공식은 다음과 같습니다[ 1 ].


식 1. Reflection Vector 계산


여기에서 N 은 서피스의 노멀( normal ) 벡터이며 V 는 서피스로부터 뷰어( viewer )를 향하는 벡터입니다. 그리고 R 은 반사 벡터입니다. N 과 V 는 모두 정규화( normalize )되어 있다고 가정합니다.


이 식을 처음 보면 상당히 이해하기 힘든데요, 그림을 그려서 관계를 살펴 보면 쉽게 이해할 수 있습니다. R 은 N 과 V 가 이루는 평면상에 존재하게 되므로 사실 이는 2D 수학입니다.


그림 1. Reflection Vector 계산 과정.


먼저 N 과 V 가 모두 정규화되어 있기 때문에 dot( N, V ) 는 VN 에 사영했을 때의 길이가 됩니다. 이게 마름모를 형성하고 있으므로 그것을 2 배 한 만큼 N 방향으로 밀어 낸 후에 -V 방향을 더하면 R 이 나옵니다.


그런데 셰이더를 작성할 때 이걸 일일히 계산할 필요는 없습니다. HLSL 내장함수로 reflect( I, N ) 라는 것이 존재합니다.


Blinn 과 Newell 의 공식과 차이가 있는데요, 뷰어와 노멀의 관계에서 반사 벡터를 구하는 것이 아니라, 입사( incident ) 벡터와 노멀의 관계에서 뷰어를 구한다는 것입니다.


식 2. HLSL reflect( i, n ) 의 계산식.


그림 1 처럼 직접 그려 보시면 이해가 갈 것이라 생각합니다. 자세한 내용은 [ reflect ] 에서 확인하세요.


참고자료


[ 1 ] 38p. TEXTURING & MODELING. A Procedural Approach. THIRD EDITION.

원문 : https://community.arm.com/developer/tools-software/graphics/b/blog/posts/mali-performance-7-accelerating-2d-rendering-using-opengl-es

주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.


성능 블로그를 쓴지 한참 지났습니다. 지난 밤동안 작성하고 싶은 블로그에 대한 점심 커피 토론 중 하나가 wasim abbas 가 만든 기술 데모가 되었습니다. 글을 다시 쓰게 만들어 준 그에게 감사드립니다. 이번에는 2D 렌더링과 OpenGL ES 가 도울 수 있는 부분에 대해서 살펴 보도록 하겠습니다.

매우 많은 모바일 칸텐트들은 여전히 2D 게이밍이거나 2D 유저 인터페이스 애플리케이션입니다. 애플리케이션들은 스크린상에 생성되는 스프라이트( sprite ) 레이어들이나 UI 요소( element )들을 렌더링합니다. 이런 거의 대부분의 애플리케이션들은 OpenGL ES 를 사용해 렌더링을 수행하는데요, 일부 애플리케이션들은 3D 기능들을 사용하며, 알파 투명도를 다루기 위해 블렌딩을 사용하는 전통적인 back-to-front 알고리즘의 단순함( simplicity )을 선호합니다.

이 접근법이 동작하기는 하지만, 하드웨어의 3D 기능들을 이용하지는 않습니다. 그래서 많은 경우에 GPU 는 필요 이상으로 더 빡세게( harder ) 작동합니다. 이것의 영향은 저성능부터 포함된 GPU 에 의존하는 배터리 수명까지 다양합니다. 그리고 이 영향들은 모바일 디바이스에서 고해상도를 향할수록 더 증폭되는 경향이 있습니다. 이 블로그는 스프라이트 렌더링 엔진들에 대한 간단한 변경사항들에 대해서 살펴 볼 것입니다. 그 엔진은 3D 렌더링 API 가 제공하는 도구들과 비교했을 때 렌더링을 훨씬 더 빠르게 만들어 주며, 에너지 효율이 더 높습니다.

Performance inefficency of 2D content

2D 게임들에서 OpenGL ES 프래그먼트 셰이더들은 보통 단순합니다( trivial ) -- 텍스쳐 좌표를 보간( interpolate )하고, 텍스쳐 샘플을 로드( load )하고, 프레임 버퍼에 블렌딩( blend )합니다 -- 그러므로 최적화할 부분이 많지 않습니다. 이러한 칸텐트 유형을 위한 성능 최적화는 거의 대부분 중복 작업을 완전히 없애는 방식을 찾는 것과 관련이 있습니다. 그러므로 일부 프래그먼트에서는 셰이더가 심지어는 아예 실행되지 않습니다.

소개 섹션에서의 그림은 배경 레이어에 위에서의 사각 스프라이트의 전형적인 블리트( blit, 역주 : 블록 데이터를 빠르게 메모리로 옮기거나 복사하는 기법 )를 보여 줍니다; 실드 스프라이트의 바깥 부분이 투명( transparent )이며, 경계 부분은 부분적으로 투명합니다. 그래서 배경으로 갈수록 에일리어싱( aliasing ) 아티팩트( artifact )없이 완전히 투명하게 사라집니다. 그리고 몸통 부분은 불투명( opaque )합니다. 이 스프라이트 블리트는 알파 블렌딩을 활성화한 상태로 프레임버퍼에서 back-to-front 렌더링 순서로 최상위에 렌더링됩니다.

여기에서 비효율성이 발생하는 두 가지 주요 원인이 존재합니다:

  • 먼저, 이 스프라이트를 둘러 싼 주변 영역들이 완전히 투명하므로, 그것은 출력 결과에 전혀 영향을 미치지 않고 있습니다. 하지만 처리하는데 시간은 걸립니다.
  • 둘째로, 스프라이트의 몸통 부분은 완전히 불투명하므로, 그 밑에 있는 배경 많은 픽셀들을 완전히 무시하고 있습니다. 그래픽스 드라이버는 배경이 무시되는 빠르게 알 수 없기 때문에, 이 배경 프래그먼트들이 GPU 에 의해 렌더링되어야만 합니다. 이는 최종 씬에 유용하게 영향을 주지 않는 뭔가를 렌더링하기 위해서 처리 사이클과 나노줄( nanojules )의 에너지를 낭비합니다.

이는 상대적으로 단일 오우버드로( overdraw ) 만이 존재하는 합성 예제이지만, 1080p 화면에서 렌더링된 프래그먼트가 절반 이상 중복되는 실제 애플리케이션을 볼 수 있습니다. 만약 애플리케이션들이 OpenGL ES 를 다른 방식으로 사용해 이 중복을 제거할 수 있다면, GPU 는 애플리케이션을 더 빠르게 렌더링할 수 있거나, clock rate 와 operating voltage 를 줄이기 위해서 생성된 성능 헤드룸( performance headroom )을 사용할 수 있을 것입니다. 즉 상당한 양의 에너지를 절약할 수 있습니다. 이런 결과들은 어느 하나라도 매력적입니다. 그래서 "애플리케이션 개발자가 이를 성취하기 위한 방법은 무엇인가?" 라는 질문을 할 수 있습니다.

Test scene

이 블로그를 위해서, 위의 실드 아이콘들을 cover-flow 스타일의 정렬로 구성된 간단한 테스트 씬을 렌더링할 것입니다. 하지만 그 기법은 불투명 영역을 가진 모든 스프라이트 셋에 대해 동작할 것입니다. 테스트 씬은 아래와 같이 렌더링합니다:

여기에서 각 실드 아이콘들은 실제로는 알파 투명도를 사용하여 보이지 않는 부분들을 가리는 사각 프라이트입니다.

Tools of the trade

전통적인 전용 2D 렌더링 하드웨어에는, 사용할 옵션들이 별로 없었습니다; 애플리케이션은 스프라이트 레이어를 back-to-front 로 렌더링해 블렌딩 함수가 올바로 작동하도록 보장해야만 했습니다. 우리의 경우에는 애플리케이션이 3D API 를 사용하여 2D 씬을 렌더링하며, 3D API 가 애플리케이션에 중복 작업 제거를 위해 제공하는 도구가 무엇이냐는 질문을 할 수 있습니다.

완전한 3D 씬을 렌더링하는 데 있어 중복 작업을 제거하기 위해 사용되는 기본 도구는 깊이( depth ) 테스트입니다. 삼각형 안의 모든 버텍스들은 그것의 위치에 "Z" 요소를 가지고 있으며, 이는 버텍스 셰이더로부터 노출됩니다. 이 Z 값은 버텍스와 카메라의 거리를 인코딩하며, 래스터화( rasterization ) 프로세스는 버텍스 값을 보간해서, 프래그먼트 셰이딩을 할 필요가 있는, 각 프래그먼트에 깊이를 할당합니다. 이 프래그먼트 깊이 값은 현재 깊이 버퍼에 저장된 현존하는 값들에 대해 검사될 수 있습니다. 만약 그 값이 프레임버퍼에 있는 현재 데이터보다 카메라와 가깝지 않다면, GPU 는 그 프래그먼트를 버리게( discard ) 되는데, 그려면 그것은 셰이더 코어에 제출되지 않습니다. 왜냐하면 그 프래그먼트가 불필요하다는 것을 안전하게 알고 있기 때문입니다.

Using depth testing in "2D" rendering

스프라이트 렌더링 엔진은 이미 각 스프라이트의 레이어링( layering )을 트래킹했습니다. 그래서 그것들은 올바른 순으로 쌓여( stack )있습니다. 그래서 우리는 이 레이어 번호를 GPU 에 보내진 각 스프라이트의 버텍스에 할당된 Z 좌표 값으로 매핑할 수 있습니다. 그리고 실제로 그것이 3D 깊이를 가진 것처럼 씬을 렌더링할 수 있습니다. 만약 깊이 어태치먼트( attachment )를 가진 프레임버퍼를 사용하고, 깊이-쓰기( depth-write )를 활성화하고, 스프라이트들과 배경 이미지를 front-to-back 순서로 렌더링한다면( 예를 들어 back-to-front 인 일반적인 블리팅 패스의 반대 순서로 ), 깊이 테스트가 스프라이트와 백그라운드의 다른 스프라이트에 의해 가려지는 부분을 제거할 것입니다다.

이를 우리의 간단한 테스트 씬을 위해 실행하면, 다음의 결과르 얻을 수 있습니다:

오! 뭔가 잘못되었습니다.

여기에서 문제는 사각 스프라이트 지오메트리가 정확하게 불투명 픽셀의 모양과 일치하지 않는다는 것입니다. 카메라와 더 가까운 스프라이트의 투명 부분이 알파테스트로 인해 어떠한 컬러 값도 생성하지 않고 있음에도 여전히 깊이 값을 설정하고 있습니다. 아래쪽 레이더의 스프라이트가 렌더링될 때, 깊이 테스트를 하면 이전의 스프라이트의 투명 부분의 밑에서 가시적이어야만 하는 부분들이 부적절하게 제거( kill )된다는 것을 의미합니다. 그래서 OpenGL ES 클리어 컬러만이 보이고 있습니다.

Sprite geometry

이 문제를 해결하기 위해서, 스프라이트를 위해 유용한 지오메트리를 설정하는 방법에 대한 연구를 좀 할 필요가 있었습니다. 스프라이트 내에서 완전히 불투명한 픽셀을 위해 front-to-back 으로 렌더링할 때 깊이 값을 안전하게 설정할 수 있습니다. 그래서 스프라이트 아틀라스 생성은 각 스프라이트를 위해 두 가지 셋( set )을 제공할 필요가 있습니다. 한 셋은 아래의 가운데 이미지에서의 녹색 영역으로 지정되어 있으며, 이는 불투명 지오메트리만을 다룹니다. 그리고 두 번째 셋은 아래의 오른쪽 이미지에서 녹색 영역으로 지정되어 있으며, 완전히 투명( 완전히 버려질 수 있는 경우 )하지 않은 모든 것들을 포함하고 있습니다.

버텍스들은 상대적으로 비쌉니다. 그래서 이런 지오메트리 셋을 생성할 때는 가능한 한 적은 개수의 부가적인 지오메트리들을 사용하십시오. 불투명 영역은 완전히 불투명한 픽셀들만을 포함해야 합니다. 하지만 투명 영역은 안전하게 불투명 픽셀들과 완전하게 투명한 픽셀들을 부수 효과( side-effect )없이 포함할 수 있습니다. 그러므로 "best fit" 를 획득하려고 시도할 필요없이 "good fit" 를 위해 대충 근사계산하십시오. 일부 스프라이트의 경우에는, 불투명 영역을 생성할 가치가 없다는 것을 기억하세요( 불투명 텍셀을 가지고 있지 않거나 매우 작은 영역만이 불투명할 때 ). 그래서 일부 스프라이트들은 투명 렌더링으로 렌더링될 단일 영역만으로 구성될 수도 있습니다. 경험적으로 볼 때, 불투명 영역이 256 픽셀보다 적을 때, 그것들은 부가적인 지오메트리 복잡성을 사용해 경계처리될 가치가 없습니다. 하지만 항상 시도하고 확인할 가치는 있습니다.

이런 지오메트리를 생성하는 것은 상대적으로 성가십니다. 하지만 스프라이트 텍스쳐 아틀라스들은 보통 정적이며 이것은 애플리케이션 칸텐트 저작 과정에의 일부로 오프라인으로 수행될 수 있습니다. 그리고 런타임에 플랫폼 상에서 라이브로 수행될 필요가 없습니다.

Draw algorithm

각 스프라이트에 대해 두 개의 지오메트리 셋을 사용하여, 이제 테스트 씬의 최적화된 버전을 렌더링할 수 있습니다. 먼저 불투명 스프라이트 영역들과 배경을 font-to-back 순서로 렌더링합니다. 이 때 깊이 테스팅과 깊이-쓰기를 활성화합니다. 그 결과는 다음과 같습니다: 

다른 스프라이트 밑에서 가려진 스프라이트나 백그라운드의 영역은 절약될 수 있는 렌더링 작업입니다. 왜냐하면 그것은 셰이딩이 발생하기 전에 빠른 깊이 테스트를 통해 제거되었기 때문입니다.

불투명 지오메트리를 렌덜이했다면, 이제 각 스프라이트의 투명영역을 back-to-front 순서로 렌더링할 수 있습니다. 깊이 테스트를 켜 둔 채로 남겨 놨기 때문에, 아래쪽 레이어들의 스프라이트들은 논리적으로 위쪽에 있는 레이어들의 불투명 영역을 덮어쓰지 않습니다. 하지만 조금이라도 전력을 절약하려면 깊이 버퍼를 비활성화하십시오.

만약 불투명 스테이지의 컬러 출력을 지워버리고 깊이 값을 유지하고 투명 패스를 그리면, 이 패스에 의해서 추가된 부가적인 렌더링 결과를 가시화할 수 있습니다. 이는 아래 그림처럼 보입니다:

링들 외부의 불완전한 영역들은 작업이 절약된 영역을 가리킵니다. 왜냐하면 없어진 부분들은 첫 번째 드로잉 패스에서 렌덜이된 카메라에 더 가까운 불투명 스프라이트 영역의 깊이 값을 사용하는 깊이 테스트에 의해 제거되었기 때문입니다.

두 패스를 모두 같은 이미지에 함께 배치하고 렌더링하면, 원래의 back-to-front 렌더링과 동일한 가시적 결과를 다시 얻을 수 있습니다:

하지만 35 % 정도 더 적은 프래그먼트 스레드들이 시작되었고, 이는 이 씬을 렌더링하기 위해서 필요한 MHz 의 35 % 를 줄인 것으로 해석됩니다. 성공적입니다!

필요로 하는 마지막 연산 논리( operational logic )는 우리가 씬에 추가한 깊이 버퍼가 메모리에 다시 쓰여지지 않음을 보장하는 것입니다. 만약 애플리케이션이 EGL 윈도우 서피스에 직접적으로 렌더링하고 있다면, 여기에서 할 일은 없습니다. 왜냐하면 윈도우 서피스를 위한 깊이가 묵시적으로 버려지기 때문입니다. 하지만 여러분의 엔진이 오프스크린 FBO 에 렌더링하고 있다면, 오프스크린 타깃으로부터 FBO 바인딩을 변경하기 전에 ( OpenGL ES 3.0 이상에서는 ) glInvalidFramebuffer() 이나 ( OpenGL ES 2.0 에서는 ) glDiscardFramebufferEXT() 에 대한 호출을 추가하는 것을 보장하십시오. 세부사항을 원한다면 이 블로그를 확인하세요.

Summary

이 블로그에서 우리는 깊이 테스팅과 깊이-지향( depth-aware ) 스프라이트 기법의 사용이 3D 그래픽스 하드웨어를 사용하는 렌더링을 가속하는데 사용되는 방법에 대해서 살펴 보았습니다.

스프라이트의 불투명 영역과 투명 영역 사이의 분리를 제공하기 위해 부가적인 지오메트리를 추가하는 것은 복잡도를 증가시킵니다. 그래서 스프라이트들을 위한 버텍스의 개수를 최소화하려고 해야 하며, 그렇지 않으면 부가적인 버텍스와 작은 삼각형 크기를 처리하는데 드는 비용이 이점을 없애버릴 것입니다. 부가적인 지오메트리가 너무 복잡해질 것이 요구되거나 화면을 덮고 있는 영역이 너무 작다면, 그냥 불투명 영역을 무시하고 전체 스프라이트를 투명인 것처럼 렌더링하십시오.

이 기법이 3D 게임에서 2D UI 요소들을 렌더링하기 위해서도 사용될 수 있다는 것을 언급할 가치가 있습니다. 3D 씬을 렌더링하기 전에 near clip plane 과 매우 가까운 깊이로 UI 의 불투명 부분을 깊이와 함께 렌더링하십시오. 그리고 나서 3D 씬을 일반적인 경우처럼 렌더링하십시오( 불투명 UI 요소들의 뒤에 있는 부분들이 건너뛰어지게 될 것입니다 ). 마지막으로 UI 의 투명 부분들이 3D 출력의 최상위에서 렌더링되고 블렌딩될 수 있습니다. 3D 지오메트리들이 UI 요소에 간섭하지 않는다는 것을 보장하기 위해서 glDepthRange() 를 사용해 3D 패스에 의해 노출되는 깊이 값들의 범위를 매우 약간 제한할 수 있습니다. 이는 UI 요소들이 3D 렌더링보다 항상 near clip plane 에 더 가깝도록 보장해 줍니다.

Tune in next time,

Pete.

+ Recent posts