3차원 객체의 이미지를 렌더링할 때 모델은 적절한 기하학적 모양뿐만 아니라 시각적 표현도 가져야 합니다. 만들기에 따라, 실제적인 모습부터 창조적으로 생성된 모양까지 구현할 수 있습니다.
5.1. Shading Models
렌더링된 객체의 표현을 결정하는 첫 번째 단계는 셰이딩 모델을 선택하여 표면 방향, 뷰 방향 및 조명과 같은 요인에 따라 객체의 색상의 변화를 결정하는 일입니다. 이러한 셰이더 모델은 외관 변화에 사용되는 몇몇 정보를 가지는데, 이러한 속성의 값을 설정하는 것이 다음 단계입니다.
대부분의 셰이더 모델에서는 뷰와 조명 방향에 영향을 받습니다. 셰이더 모델에 대한 입력 값들이 정의되었다면 모델 자체의 수학적 정의를 파악해야 합니다. 자주 쓰이는 방법 중 하나로는 clamping으로 값을 0과 1사이의 값으로만 한정하여 사용하는 방법이 있으며, 다른 방법은 lerp 혹은 mix로 이루어지는 보간으로 적절한 사이 값을 골라내는 방법입니다. 이러한 방법들을 적절히 섞어서 구현할 수 있습니다.
5.2. Light Sources
빛이 셰이더 모델에 끼치는 가장 큰 영향은 그림자의 방향입니다. 다양한 형태의 빛이 있을 수 있어 다채로울 수 있으며, 간접광까지 고려한다면 더욱더 그렇습니다.
셰이더 모델을 구축하는 것은 이보다 더 다양한 방법을 기술합니다. 어떤 모델 및 표현을 사용할 지를 선택할 지에 따라 수많은 다양한 결과를 낼 수 있습니다. 또한 빛의 여부에 따른 표현의 선택도 중요합니다. 보통 빛의 여부는 빛의 강도를 통해 표현 가능합니다.
$$ c_{shaded} = f_{unlit}{(n, v)} + {\sum}{i=1}^{n}c{light}f_{lit}{(l_i, n, v)} $$
unlit은 빛에 직접적인 영향을 받지 않을 때를 의미하며 단순하게 검은색으로도 표현 가능하지만 간접적인 영향인 주변광의 영향을 받게 설정도 가능합니다. 앞서 얘기한 것처럼, 빛의 방향은 노말 벡터에서 90도 이상 넘어가면 표면에 영향을 주지 않습니다.
빛이 표면에 주는 영향은 빛의 세기 및 밀도에 따라 달라지며, 이는 빛의 방향과 노말 벡터의 내적으로 구해지기 때문에 입사각의 코사인에 비례합니다. 이는 좀 더 물리적으로 기술하고 싶을 때 사용됩니다.
5.2.1 Directional Lights
직접광은 가장 간단한 모델로, 빛의 방향이 일정하고 세기가 일정합니다. 광원이 특정한 위치에 존재하지 않는 것으로 생각해도 좋습니다.
5.2.2 Punctual Lights
punctual light은 한 지점에서 빛이 퍼져나가는 점 광원에 해당합니다. 이는 실제 세계와는 다르게 부피와 모양을 가지지 않습니다. 따라서 빛의 벡터는 빛을 보내는 위치에 따라 바뀌게 됩니다. punctual light는 크게 두가지로 분류 됩니다.
point/omni light의 경우는 빛을 모든 방향으로 균등하게 방출합니다. 이때 빛의 강도는 거리의 제곱에 반비례하는 형태를 띄는데 다음과 같은 식으로 적힐 수 있습니다
$$ {c}{light}(r) = {c}{light_0} \frac{r_{0}^{2}}{r^{2} + \epsilon} = {c}{light_0} f{dist}(r) $$
입실론은 0인 경우 무한으로 뛰는 것을 막기 위함이며, 다른 엔진의 경우 하한치를 두어 무한대로 가는 것을 방지합니다. 하지만 항상 이 방식을 사용하지는 않습니다. 어느 수준을 넘어가면 빛이 존재하지 않는 것처럼 만들고 싶은데, 이런 경우에는 위의 식을 바탕으로 하되, 특정 수치를 넘어가면 0이 되게 하는 함수를 표현하고 싶은 형태에 따라 알맞게 정의하여 사용합니다. 이때, 경계선에서 날카롭게 빛이 끊기는 것을 방지하기 위하여 값이 0이 되는 지점에서 적절한 기울기를 가지도록 해야합니다. 이를 통해 거리에 따른 비례 함수(distance falloff function)를 정의합니다.
Spotlights는 point light와는 다르게 거리만이 아닌 방향에 의해서도 빛을 다르게 설정합니다. 즉 위의 함수에서 방향에 따른 함수가 하나 더 붙는 형식이 됩니다. 또한 이는 빛을 원뿔 모형으로 투사합니다. 따라서 spotlight의 방향 벡터에 대해 대칭적으로 빛이 표현될 수 있습니다. spotlight는 흔히 두개의 각도를 가지는데, umbra angle ${\theta}{u}$는 이 값보다 큰 각도로 쏠 때는 강도가 0이되며, penumbra angle ${\theta}{p}$는 이 값보다 작을 때 최대 크기의 강도를 가지게 됩니다.
다른 puntual light도 존재하는데, 이는 강도를 정하는 함수에 달려있습니다.
5.2.3 Other Light Types
직접광과 punctual 빛은 빛의 방향을 어떻게 정의하냐에 따라 크게 좌우되었습니다. 다른 빛의 경우도 유사하게 빛의 방향을 어떻게 정의하는가에 따라 다르게 정의될 수 있습니다. 즉, 빛의 방향과 빛의 강도에 대해서만 정의를 한다면 어떠한 셰이더 모델에도 적용할 수 있습니다. 면광원(area light)은 특정한 크기와 형태를 가지고 있어 표면을 다양한 방향에서 비추는 경우를 말하는데, 이는 위의 경우보다 현실적이며 좀 더 많이 사용되고 있습니다. 이러한 면 광원의 사용 기술은 크게 두가지로 그림자의 가장자리 부분을 부드럽게 만들거나, 표면 셰이딩을 시뮬레이팅 하는데 사용됩니다.
5.3. Implementing Shading Models
이러한 셰이딩과 빛 공식을 코드에 어떻게 적용하는 지에 대해 알아봅시다.
5.3.1 Frequency of Evaluation
셰이딩을 구현할 때, 평가의 빈도에 따라서 계산이 나뉘어질 필요가 있습니다. 먼저 주어진 계산의 결과가 전체 draw 호출에 대해서 일정한지 여부를 결정해야합니다.
빈도는 범주가 광범위한데 그 중에서도 딱 한번만 하는 경우도 존재합니다. 가장 간단 예로는 셰이딩 수식의 하위 표현식부터, 하드웨어 장치와 같이 쉽게 바뀌지 않는 것이 존재합니다. 또 다른 존재로는, 해가 조금씩 이동하는 것처럼 셰이딩 계산의 결과가 실행 도중에 바뀌지만 매 프레임마다 바꾸기에는 느리고 계산 비용이 큰 경우, 여러 시간에 걸쳐 분할하여 수행하는 것이 가치가 있을 수 있습니다. 다른 경우로는 뷰 및 투시 행렬을 연결하는 것과 같이 프레임당 한 번 수행되거나, 위치에 따라 달라지는 모델 조명 매개 변수 업데이트와 같이 모델당 한 번 수행되거나, 모델 내의 각 재료에 대한 매개 변수 업데이트와 같은 모델당 한 번 수행되는 연산이 존재합니다. 평가 빈도별로 균일한 셰이더 입력을 그룹화하면 애플리케이션 효율성에 유용하며, 지속적인 업데이트를 최소화하여 GPU 성능을 향상시킬 수 있습니다.
드로우 호출 내에서 셰이딩 계산 결과가 변경되면 하나의 셰이더 입력만으로 전달되는 것이 아니라 여러 셰이더 단계를 걸치면서 계산되어야만 합니다. 이론적으로, 셰이딩 계산은 프로그래밍 가능한 단계 중 하나에서 수행될 수 있으며, 각각은 다른 평가 빈도를 가지게 됩니다.
- Vertex Shader - pre-tessellation vertex 당 평가
- Hull shader - 표면 조각 당 평가
- Domain shader - post-tesselation vertex 당 평가
- Geometry shader - 프리미티브 당 평가
- Pixel Shader - 픽셀당 평가
대부분의 셰이딩 계산은 픽셀 셰이더에서 수행되며 픽셀 당으로 수행되지만, 더 다양한 단계에서 수행되기 시작했으며, 기하학적 변형을 위해 자주 사용됩니다. 이는 정점 당 수행하는 것과 픽셀 당 수행하는 것의 차이를 보입니다. 정점 당 수행하는 것은 픽셀 당 수행하는 것에 비해 가시적인 효과에서 부정확한 모습을 보입니다. 이는 빛의 하이라이트에서 더 심한데, 하이라이트는 표면에서 비선형적인 값의 분포를 보이기에, 보간을 통해 작업을 수행하는 정점 당 셰이딩 수행은 이상한 시각적 오류를 만들어낼 수 밖에 없습니다.
만약 하이라이트만 pixel shader에서 수행하고 나머지를 vertex shader에서 수행하게 된다면, 이는 시각적 시각적 문제를 보이진 않으면서도 약간의 계산을 절약할 수 있지만, 실제로 이러한 구현은 최적화되지 않습니다. 셰이더 모델의 선형적으로 변화하는 부분은 계산이 가장 적게 들고, 이러한 방식으로 분할한다면 중복 계산 및 추가 변수 입력과 같은 오버헤드를 필요로 할 수도 있기에 오히려 실이 많을 수도 있습니다.
대부분의 경우 vertex shader는 기하학적 변형과 같은 비셰이딩 계산에 많이 쓰이는데, vertex shader로 인해 적절한 좌표계로 변환된 위치, 법선와 같은 기하학적 표면 특성들이 삼각형에서 적절하게 보간되어 pixel shader와 같은 다양한 셰이더의 입력으로 들어가게 됩니다.
vertex shader는 단위 길이의 표면 법선을 생성하더라도 보간에서 이 크기가 변경될 수 있습니다. 따라서 구현은 보간 전후로 벡터를 정규화합니다. 즉, vertex shader와 pixel shader에서 둘다 수행합니다.
뷰 벡터나 빛 벡터와 같이 특정 위치를 가리키는 벡터는 일반적으로 보간하지 않으며, 보간된 표면 위치로 pixel shader에서 이러한 벡터를 계산하는 데 사용됩니다. 보간이 필요하다면 사전에 정규화하지 않는 것이 중요하며, 만일 보간한다면 잘못된 결과를 불러일으킬 수 있습니다.
uniform 변수를 통해 pixel shader로 전달되는 카메라 및 조명 위치는 일반적으로 애플리케이션에 의해 동일한 좌표계로 변환됩니다. 이는 pixel shader에서 모든 셰이딩 모델 벡터를 동일한 좌표 공간으로 가져오는 작업을 최소화한다. 이때 적절한 좌표계의 선정이 중요한데, 일반적으로 성능, 유연성 및 단순성과 같은 시스템적 고려사항에 따라 렌더링 시스템 전체를 선택합니다. 예를 들어, 렌더링된 많은 수의 조명이 존재한다면, 조명 위치의 변환을 피하기 위해 글로벌 공간을 선택할 수 있고 뷰 벡터와 관련된 픽셀 셰이더 작업을 더 잘 최적화하고 정밀도를 향상시키기 위해 카메라 공간이 선호될 수 있습니다.
위와 같은 일반적인 개요를 벗어나는 경우도 존재하는데, 프리미티브 당 셰이딩 평가를 하려고 하는 경우를 flat shading이라고 합니다. 이는 geometry shader 혹은 vertex shader에서 수행될 수 있습니다.
5.3.2 Implementation Example
예시이므로 그냥 찬찬히 잘 읽어보면 됩니다.
5.3.3 Material Systems
렌더링 프레임워크는 단일 셰이더로 구성된 경우는 드물며, 보통 다양한 재료, 셰이딩 모델을 다루는 전용 시스템을 필요합니다. 셰이더는 GPU의 프로그래밍 가능한 셰이더 단계이며, 이는 저수준의 그래픽 API 리소스이기 때문에 아티스트들이 직접 다루기는 어렵습니다. 반대로 재료는 표면의 시각적 효과에 대한 예술적 측면의 요약본입니다. 이러한 재료는 셰이더를 통해 구현되지만 환경에 따라 동일한 재료더라도 상이한 셰이더를 사용할 수도 있으며, 동일한 셰이더라도 여러 재료에 의해 공유될 수 있습니다. 가장 흔한 경우는 매개변수에 따라 특정 클래스의 재료를 설정하고 이에 추가적인 효과를 더 얹는 식으로 구현됩니다.
이러한 매개변수는 실행 시간에도, 컴파일 타입에도 수행도리 수 있는데, 컴파일 타입에 수행되는 경우에는 주어진 재료 특성의 활성화를 bool로 표현합니다. 이는 checkbox 형태로 재료를 사용자가 설정할 수 있게 하거나, 재료 시스템을 절차적으로 설정하여 먼 곳의 물체의 시각적 효과를 무시할 수 있습니다.
재료의 매개변수는 셰이더 모델의 매개 변수와 일대일 대응될 수도 있지만 아닐 수도 있습니다. 재료는 주어진 셰이더 모델 매개변수로서 고정될 수도 있으며, 여러 정점의 값으로 계산된 값을 지닐 수도 있습니다. 예를 들어 시간 기반으로 네온사인을 깜빡이게 할 수도 있습니다.
재료 시스템의 가장 중요한 작업 중 하나는 다양한 셰이더 기능을 별도의 요소로 나누고 이러한 기능이 이들의 결합을 제어하는 것입니다. 많은 구성이 존재하며 다음과 같습니다.
- 강체 변환, 정점 혼합 및 클리핑과 같은 기하학적 처리를 통한 표면 셰이딩 구성 - 이러한 기능은 독립적으로 변화합니다. 표면 셰이딩은 재료에 따라 달라지고 geometry 처리는 mesh에 따라 달라지기에, 그것들을 개별적으로 작성하고 필요에 따라 재료 시스템이 구성하도록 하는 것이 편리합니다.
- 픽셀 버림 및 블렌딩과 같은 합성 작업으로 표면 셰이딩 구성 - 이는 특히 픽셀 셰이더에서 블렌딩이 수행되는 모바일 GPU와 관련이 있으며 표면 셰이딩에 사용되는 재료와는 독립적으로 이러한 작업을 선택하는 것이 적절합니다.
- 셰이딩 모델의 매개변수를 셰이딩 모델 자체의 계산으로 수행하는 동작 구성 - 이를 통해 셰이딩 모델 구현을 한 번 작성하면 셰이딩 모델 매개 변수를 계산하는 다양한 방법과 함께 재사용할 수 있게 됩니다.
- 개별적으로 선택 가능한 재료, 로직 및 셰이딩으로 구성 - 이를 통해 각 기능의 구현을 개별적으로 작성할 수 있습니다.
- 광원과 매개변수의 계산을 통한 셰이딩 구성 - 각 광원에 대한 셰이딩 지점에서의 값을 계산하여, 추가적인 계층을 추가하여 다양한 기술들을 지원합니다.
GPU 셰이더의 경우 코드의 컴파일 후 연결을 허용하지 않아 코드 모듈성이 허용되지 않습니다. 따라서 각 셰이더 단계의 프로그램은 하나의 프로그램의 단위로 컴파일 됩니다. 이러한 분리는 제한된 모듈성으로 이어집니다. 이러한 한계를 고려한다면, 재료 시스템이 구현을 할 수 있는 방법은 소스 코드 레벨 단계에서만으로 제한되며 이는 연결 및 교체, 전처리 명령을 통해서 수행될 수 있습니다. 초기에는 셰이더의 변형이 적고 수동으로 작성되어 문제가 별로 없었지만 수가 늘어나면서 비효율적이 되어 모듈성과 결합성을 요구로 하게 되었습니다.
셰이더 변형을 처리하려면 동적 분기를 런타임에 수행하거나 조건 전처리를 통해 컴파일 타임에 처리할 것인지 선택을 해야합니다. 예전에는 동적으로 처리하기에 하드웨어가 바쳐주지 않았으나 현재는 동적으로 처리를 할 수 있으며, 분기가 draw 호출의 모든 픽셀에 동일하게 동작할 때는 더욱 접합합니다. 하지만 많은 양의 변화가 추가될수록 성능상의 문제를 일으킬 가능성이 높아집니다. 컴파일 시간에 수행하는 정적 처리도 아직 유효하게 사용될 수 있습니다. 현대의 재료 시스템은 이 둘을 적절하게 사용하고 있습니다.
컴파일 시간에 미리 셰이더 변형을 처리하는 경우는 전체적인 변형의 수가 계속 증가하기에 부담이 될 수도 있습니다. 따라서 이를 줄이기 위해서 다양한 전략을 선택하고, 이러한 전략들은 상호배타적이거나 상호작용할 수도 있습니다. 전략들은 다음과 같습니다.
- 코드 재사용 #include를 사용하여 공유 파일에 기능을 구현하고 이를 전처리를 통해 다른 셰이더에서 사용할 수 있도록 처리합니다.
- 감산 일반적으로 ubershader 또는 supershader로 불리는 셰이더로, 정적 분기 처리(컴파일 타임 전처리 조건에 의한 처리) 및 동적 분기를 적절히 조합하여 상호 배타적인 대안 사이에서 사용하지 않는 부분을 제거하고 전환하여 많은 기능 집합을 취합합니다.
- 추가 다양한 기능의 입력 및 출력 연결이 존재하는 노드로 정의되며 함께 모여 구성됩니다. 이는 코드 재사용 전략과 비슷하지만 좀 더 구조화되어 있습니다. 노드의 구성은 시각적으로 편집될 수 있습니다.
- 템플릿 인터페이스가 정의되어 있으며, 인터페이스를 준수하는 한 서로 다른 구현을 연결할 수 있습니다. 이는 추가 전략보다 약간 더 형식적이며 일반적으로 더 큰 기능 덩어리에 사용됩니다.
구성 외에도 셰이더 코드의 멀티 플렛폼 지원과 같은 현대 재료 시스템을 위한 몇 가지 중요한 설계 고려 사항이 존재합니다. 여기에는 플랫폼, 셰이딩 언어 및 API 간의 성능 및 기능 차이를 설명하기 위한 다양한 기능이 포함됩니다. 또한 재료 시스템은 좋은 성능을 가져야하는데 셰이딩 변형의 전문화된 컴파일 외에도, 재료 시스템이 수행할 수 있는 몇 가지 다른 일반적인 최적화가 존재합니다.
5.4. Aliasing and Antialiasing
모든 종류의 기본 렌더러에서 격자 셀의 중심이 가려지는 순간 픽셀 색상이 즉시 흰색에서 검은색으로 변하는 문제가 존재하는데 표준 GPU 렌더링도 예외는 아닙니다.
삼각형은 그 해당 픽셀에 존재 여부로 그려지게 되며 선분의 경우도 비슷하게 작동합니다. 이렇게 근사화로 그려진 것들의 가장자리는 삐죽삐죽한 모양을 가지는데, 이러한 문제를 앨리어싱(aliasing)이라 하며 이를 없애기 위한 기술을 안티앨리어싱 기술이라 합니다.
5.4.1 Sampling and Filtering Theory
이미지 렌더링 프로세스는 본질적으로 샘플링 작업입니다. 이미지의 생성은 이미지의 각 픽셀에 대한 색상 값을 얻기 위해 3차원 장면을 샘플링하는 과정에 해당합니다. 이 섹션에서는 샘플링, 재구성 및 필터링에 대해 소개합니다.
연속된 신호를 이산화시키면 정보의 양이 줄어들기에 사용하기 편하게 됩니다. 샘플링을 수행하면, 앨리어싱이 발생할 수 있는데 이는 너무 낮은 빈도로 샘플링될 때 일어나게 됩니다. 제대로 샘플링될려면 주기를 샘플링될 빈도의 2배 이상이 되어야하며 이를 표본 추출 정리라고 합니다.
5.4.2 Screen-Based Antialiasing
삼각형의 가장자리는 표본 추출 및 필터링이 잘 되지 않으면 그림자 경계, 반사 하이라이트 및 색상이 빠르게 변하는 현상과 같은 문제를 일으킬 수 있습니다. 이들은 화면 기반이라는 공통 분모를 가지고 있습니다. 즉, 파이프라인의 출력 샘플에서만 작동하며, 이는 환경에 영향을 받기에 동일한 최고의 해법은 존재하지 않습니다.
화면에서 삼각형의 모서리가 끊겨서 보이는 문제는 화면 그리드 셀당 더 많은 샘플을 사용하고 이를 어떤 방식으로 혼합함으로써 더 나은 픽셀 색상을 계산하여 해결 가능합니다. 화면 기반 안티에일리어싱 방식의 일반적인 전략은 화면에 샘플링 패턴을 사용한 다음 픽셀 색상을 생성하기 위해 샘플을 가중치하고 합하는 방법입니다. 가중치의 합은 1이어야하며, 실시간 렌더링에서는 대부분 균등한 가중치를 부여합니다.
픽셀당 하나 이상의 전체 샘플을 계산하는 안티에이리어싱 알고리즘을 슈퍼샘플링(또는 오버샘플링) 방법이라고 합니다. 슈퍼샘플링 안티에이리어싱(SSAA)이라고도 하는 개념적으로 단순한 전체 장면 에이리어싱(FSAA)은 장면을 더 높은 해상도로 렌더링한 다음 이웃 샘플을 필터링하여 이미지를 생성합니다. 이 방법은 모든 하위 샘플이 z-buffer depth를 포함하여 계산을 해야하므로 비용이 많이 듭니다.
물체 가장자리, 반사 하이라이트 및 선명한 그림자와 같은 현상이 급격한 색상 변화를 일으킬 경우 추가 샘플이 필요합니다. 그림자는 종종 더 부드럽게 만들 수 있고 앨리어싱을 방지하기 위해 더 부드럽게 강조할 수 있습니다. 객체 가장자리의 앨리어싱은 여전히 주요 샘플링 문제로 남아 있습니다.
다중 샘플링 안티에이리어싱(MSAA)은 픽셀당 표면의 셰이더를 한 번 계산하고 이 결과를 샘플 간에 공유하여 높은 계산 비용을 절감합니다. 색상과 z 깊이지만 픽셀 셰이더는 픽셀에 적용된 각 객체 조각에 대해 한 번만 평가됩니다. 모든 MSAA 위치 샘플이 프래그먼트에 의해 가려지는 경우, 음영 샘플은 픽셀의 중심에서 평가됩니다. 대신 조각이 더 적은 위치 샘플을 덮는 경우, 셰이더 샘플의 위치를 이동하여 커버된 위치를 더 잘 나타낼 수 있습니다. 이 위치 조정은 중심 샘플링 또는 중심 보간이라고 하며, 활성화된 경우 GPU에 의해 자동으로 수행됩니다. 중심 표본 추출은 삼각형이 아닌 문제를 방지하지만 파생 계산이 잘못된 값을 반환하게 할 수 있습니다.
MSAA는 조각이 한 번만 음영 처리되기 때문에 순수 슈퍼샘플링 방식보다 빠르기에, 샘플링과 커버리지의 추가적인 결합을 해제함으로써 더 많은 메모리를 절약할 수 있으며, 이는 앤티앨리어싱을 더 빠르게 만들 수 있습니다.
5.5. Transparency, Alpha, and Compositing
5.6. Display Encoding
'그래픽 공부 > Realtime Rendering' 카테고리의 다른 글
3. The Graphics Processing Unit (0) | 2022.07.12 |
---|---|
2. The Graphics Rendering Pipeline (0) | 2022.07.11 |
21. Virtual and Augmented Reality (0) | 2022.05.11 |
8. Light and Color (0) | 2022.05.11 |