Graphics Programming
GPU Gems 2: 8장. 거리 함수를 이용한 픽셀별 변위 매핑 본문
원문: GPU Gems 2: Chapter 8. Per-Pixel Displacement Mapping with Distance Functions
주소: https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter08.html
용어 번역
distance function: 거리 함수
displacement mapping: 변위 매핑
ray tracing: 광선 추적
surface: 표면
implicit function: 음함수
mapping: 매핑(사상)
fragment shader: 조각 셰이더
William Donnelly
University of Waterloo
이번 장에서는 물체에 소규모 변위 매핑을 적용하기 위한, 거리 매핑이라는 픽셀 셰이더 기법을 살펴본다. 여기서는 변위 매핑을 광선 추적 문제로 취급한다. 기본 표면(base surface) 위의 텍스쳐 좌표를 출발점 삼아 시야선(viewing lay)이 변위 표면과 교차하는 텍스쳐 좌표를 계산한다. 그러기 위해 삼차원 거리 맵을 미리 계산해두는데, 이 맵은 공간의 점과 변위 표면 사이의 거리를 측정한 값을 알려준다. 이 거리 맵은 광선과 표면의 교차를 신속히 검사하는 데 필요한 모든 정보를 제공한다. 이 알고리즘은 실시간 수행능력을 유지하면서도 인식되는 장면을 기하학적으로 상당히 복잡하게 만든다.
- 기본 표면(base surface) : 표면의 높이를 0으로 본 것 (원본)
- 변위 표면(displaced surface) : 융기된 표면
8.1 소개
쿡(1984)은 표면의 자잘한 울툴불퉁함을 살리는 방법으로서 변위 매핑을 소개했다. 범프 매핑은 표면의 셰이딩(shading)에 영향을 주지만 변위 매핑은 표면 요소의 위치를 조정한다. 그럼으로써 표면 부분들이 서로를 가리게 하거나 곡선 표면을 그릴 수 있게 되었는데, 이는 범프 매핑으로는 불가능한 것이다. 그림 8-1의 돌 표면 렌더링에서는 각 부분이 서로를 가려서 보는 이에게 깊이감이 있다는 환상을 심어준다.
그림 8-1 변위된 돌 표면
변위 매핑의 일반적인 구현은 기본 표면을 바둑판처럼 나눠서 정점들이 기본 표면의 법선을 따라 튀어나오도록 하는 작업을, 생성된 다각형이 단일 픽셀의 크기에 가까워질 때까지 계속하는 것이다. Michael Bunnell이 이 책의 7장 "Adaptive Tessellation of Subdivision Surfaces with Displacement Mapping"에서 이 방법을 설명한다.
이 방법이 파이프라인의 정점 셰이딩 단계에서 변위 매핑을 구현하는 가장 논리적인 방법으로 보이겠지만, 정점 셰이더는 새 정점을 만들어낼 수 없기 때문에 이 방법은 불가능하다. 따라서 픽셀 셰이더에 의존하는 기법으로 눈을 돌려야 한다. 또한 픽셀 셰이더를 쓰는 것이 더 나은 이유가 몇 가지 있다.
- 오늘날의 GPU는 정점 처리 능력보다 픽셀 처리 능력이 높다. 예를 들어 지포스 6800 울트라는 픽셀 셰이딩 파이프라인이 16개, 정점 셰이딩 파이프라인이 6개다. 게다가 대개는 단일 픽셀 파이프라인이 단일 정점 파이프라인보다 클럭 사이클 당 가능한 작업 수가 더 많다.
- 픽셀 셰이더가 텍스쳐 접근에 더 알맞다. 많은 GPU는 정점 셰이더에서 텍스쳐에 접근하는 것을 허용하지 않는다. 가능한 경우에도 접근에 제약이 있고 수행능력에서 대가를 치뤄야 한다.
- 거리에 비례하는 픽셀 처리량. 정점 셰이더는 모델의 모든 정점에 대해 실행되지만 픽셀 셰이더는 화면에 표시되는 픽셀에 대해서만 실행된다. 이것은 작업이 가장 필요한, 화면에 가까운 물체들에 집중됨을 뜻한다.
픽셀 셰이더를 사용할 때의 단점은 셰이더 안에서 픽셀의 화면 좌표를 수정할 수 없다는 것이다. 테셀레이션(쪽매맞춤)에 기반한 방법과 달리 픽셀 셰이더를 쓰는 방법은 임의의 광범위한 변위에 적용할 수 없다. 하지만 실전에서 쓸 정도의 변위에는 심각한 제약이 아니다.
8.2 이전의 연구
시차 매핑(Parallax mapping)은 범프 매핑을 확장하여 시차 효과를 내는 간단한 기법이다(Kaneko et al. 2001). 시차 매핑은 표면의 한 점에서 높이를 이용하여 텍스쳐 좌표를 관찰자로부터 후퇴시키거나 전진시킨다. 부드럽게 변하는 높이 필드에서만 알맞은 거친 근사이긴 하지만, 세 단계의 셰이더 명령만 추가해서 구현할 수 있음을 감안하면 놀라우리만큼 좋은 결과를 낸다. 불행히도 이 기법에 내재된 가정 때문에 큰 변위, 고주파 특징이나 부분간 가림은 처리할 수 없다.
Policarpo(2004)가 제안한 relief 매핑은 높이 맵에 대해 해 발견 기법을 사용한다. 이 기법은 시야선을 텍스처 공간 안으로 변환하는 것으로 시작한다. 그 다음 표면과의 교점을 구하기 위해 선형 검색을 수행하고 정확한 교점을 찾기 위해 이진 검색을 한다. 불행히도 선형 검색에는 일정 양의 수행 단계가 필요하다. 즉 작은 세부 사항을 해결하려면 단계를 늘려야 해서 사용자가 수행능력과 정확성 중 하나를 희생해야 한다. 우리 알고리즘은 그런 양자택일이 없다. 표면에 가까운 세부 사항을 제공하기에 필요한 단계 크기를 알아서 조절하고 표면으로부터 떨어진 넓은 빈 공간은 넘어간다.
뷰에 의존하는 변위 매핑(Wang et al. 2003)과 그것의 확장(Wang et al. 2004)은 변위 매핑을 일반화하여 광선 검출 문제로 다룬다. 이것들은 삼차원 볼륨 안에서 가능한 모든 광선 교차 질의를 세 위치 좌표와 두 각 좌표로 인덱싱되는 오차원 맵에 저장한다. 이 맵은 특이값분해(singular value decomposition)로 압축되고 실시간으로 픽셀 셰이더 안에서 압축 해제된다. 자료가 오차원이기 때문에 상당한 양의 전처리와 저장 공간이 필요하다.
여기서 제안하는 변위 렌더링 알고리즘은 음함수 곡면(implicit surface)의 광선 검출을 빠르게 하기 위해 개발된 구 검출(Hart 1996)에 기반한다. 개념은 같지만 알고리즘을 음함수 곡면의 광선 검출이 아니라 GPU에서 변위 맵을 렌더링하는 데 적용한다.
- implicit surface : 삼차원 좌표를 상수에 대응하는 함수
8.3 거리 매핑 알고리즘
평면에 변위 매핑을 적용하려 한다고 가정하자. 표면은 축 정렬 상자에 의해 제한된 것으로 생각할 수 있다. 일반적인 변위 알고리즘은 상자의 바닥면을 그리고 정점들을 위로 올린다. 우리의 알고리즘은 대신 상자의 천장면을 그린다. 그리고 셰이더 안에서 변위 표면의 어느 점이 실제로 관찰자에게 보이는지 찾는다.
어떤 의미에서 우리는 기존의 변위 매핑의 정반대 문제를 계산한다. 변위 매핑은 묻는다. "이 기하 구조의 한 조각에 이미지의 어느 점이 매핑되는가?" 우리 알고리즘은 묻는다. "이미지의 이 픽셀에서 우리는 기하 구조의 어느 조각을 볼까?" 첫 번째 접근법이 래스터화 알고리즘에 쓰이는 반면 두 번째 방법은 광선 검출 알고리즘에 쓰인다. 그래서 우리는 변위 매핑을 광선 검출 문제로서 접근하는 것이다.
일반적인 광선 검출법은 높이 맵을 정규 간격마다 추출하여, 시야선과 표면의 교차를 검사한다. 불행히도 우리는 다음과 같은 문제에 직면한다. 표본들이 단일 텍셀보다 멀리 떨어져 있으면 표본 사이의 교차를 놓치지 않는다고 보장할 수 없다. 그림 8-2에 이 문제가 나타나있다. 이런 과도한 건너뜀은 렌더링된 기하 구조에 틈을 만들어 심각한 계단 현상을 초래할 수 있다. 둘 중 하나를 선택해야 한다. 언더샘플링을 받아들이거나, 시야선을 따라 각 텍셀마다 표본을 선택하거나. 그림 8-3에서 우리의 알고리즘이 계단 현상이나 기하 구조상 틈을 만들지 않고 괜찮은 수준으로 물체를 그릴 수 있음을 볼 수 있다.
그림 8-2 변위 알고리즘의 난감한 경우
그림 8-3 변위된 텍스트 렌더링
앞의 예시에서 두 결론을 이끌어낼 수 있다.
1. 단순히 높이 맵을 고정 간격으로 추출할 수는 없다. 언더샘플링되거나 다루기 힘든 알고리즘이 된다.
2. 단순한 높이 필드 이상의 정보가 필요하다. 표면을 건너뛰지 않으면서도 임의의 영역에서 표본을 얼마나 건너뛰어 택할 수 있는지 알아야 한다.
위 문제들을 해결하기 위해 표면의 거리 맵을 정의할 것이다. 텍스처 공간의 임의 점 p와 표면 S에 대해 함수 dist(p, S) = min{d(p, q) : q in S} 로 정의한다. 달리 말하자면 dist(p, S)는 p에서 표면 S 내 가장 가까운 점까지의 최단 거리다. 그러면 S의 거리 맵은 각 점 p에 대해 dist(p, S)의 값을 저장하는 3D 텍스쳐다. 그림 8-4는 일차원 높이 맵과 그에 대응하는 거리 맵의 예시를 보여준다.
그림 8-4 이차원의 추출 거리 맵
이 거리 맵은 추출 위치를 선택하는 데 필요한 바로 그 정보를 제공한다. 원점이 p0이고 방향 벡터가 D인 광선을 가정하자. (D는 단위 길이) 새로운 점 p1 = p0 + dist(p0, S) X D 로 정의한다. 이 점은 p0이 표면 밖이면 p1 역시 표면의 밖이라는 중요한 정보를 갖는다. 그 다음 p2 = p1 + dist(p1, S) x D 로 정의하여 같은 작업을 적용하는 식이다. 점은 갈 수록 표면에 가까워진다. 그러므로 충분히 추출하면 우리의 점은 표면과 광선의 가장 가까운 교점을 향해 간다. 그림 8-5에 이 알고리즘의 유효성이 나타나있다.
그림 8-5 구 검출
거리 함수는 높이 필드에만 적용할 수 있는 게 아니다. 사실 거리 맵은 임의의 복셀화된 자료를 표현할 수 있다. 소규모 세부 사항을 복잡한 위상 기하를 가지고 그릴 수 있을 지도 모른다는 뜻이다. 예를 들어 쇠미늘갑옷도 이런 식으로 그릴 수 있다.
8.3.1. 임의 메시
지금까지는 거리 매핑을 평면에 적용하는 것만 논의했다. 거리 매핑을 일반적인 메시에 적용해보자. 표면이 국소적으로 평평하다고 가정하고 표면의 local tangent frame을 이용하여 평면의 경우와 똑같이 계산한다. 빛 벡터를 접공간 법선 매핑을 위해 변환했듯이, local 표면 tangent를 사용하여 view 벡터를 접공간으로 변환한다. 시야선을 접공간으로 변환한 후에 알고리즘은 평면의 경우와 똑같이 진행한다.
이제 거리 맵을 광선 검출을 위해 사용하는 방법을 알았으니 임의의 높이 필드에 대한 거리 맵을 효율적으로 계산하는 방법을 알아낼 차례다.
8.4 거리 맵 계산하기
거리 변환의 계산은 많이 연구된 문제다. Danielsson(1980)은 O(n) 시간 안에 수행되는 알고리즘을 제안했다. n은 맵의 픽셀 개수다. 각 픽셀이 표면의 가장 가까운 점을 향하는 3D 변위 벡터를 저장하는 3D 맵을 만든다. 그 다음 3D 영역에서 sweep을 몇 번 반복하여 각 픽셀의 변위 벡터를 이웃한 변위 벡터에 기반해 갱신한다. 변위를 계산하고나면 각 픽셀의 거리는 변위의 크기로 계산된다. 이 알고리즘의 구현은 책의 CD에 들어있다.
거리 변환 알고리즘이 끝나면 우리는 거리 맵의 각 픽셀에서 표면의 가장 가까운 점까지의 거리를 픽셀 단위로 계산한 것이다. 이 거리들이 범위 [0, 1] 안에 들어오도록 하기 위해 각 거리를 픽셀 단위에서 3D 텍스처의 깊이로 나눈다.
8.5 셰이더
8.5.1. 정점 셰이더
예제 8-1의, 거리 매핑을 위한 정점 셰이더는 접공간 변위 매핑의 정점 셰이더와 아주 비슷하지만 두 가지 중요한 차이가 있다. 첫 번째는 빛 벡터를 접공간으로 변환하는 것에 더해, 보는 방향도 접공간으로 변환하는 것이다. 이 접공간 시점 벡터는 정점 셰이더에서, 텍스쳐 공간 내에서 검출될 광선의 방향으로서 쓰인다. 두 번째 차이점은 지각되는 깊이에 반비례하는 추가 인자를 포함시키는 것이다. 이렇게 하여 변위 비율을 능동적으로 조율할 수 있다.
예제 8-1. 정점 셰이더
uniform float4x4 modelViewProj,
uniform float3 eyeCoord,
uniform float3 lightCoord,
uniform float invBumpDepth)
{
v2fConnector v2f;
// Project position into screen space
// and pass through texture coordinate
v2f.projCoord = mul(modelViewProj, float4 (a2v.objCoord, 1));
v2f.texCoord = float3 (a2v.texCoord, 1);
// Transform the eye vector into tangent space.
// Adjust the slope in tangent space based on bump depth
float3 eyeVec = eyeCoord - a2v.objCoord;
float3 tanEyeVec;
tanEyeVec.x = dot(a2v.objTangent, eyeVec);
tanEyeVec.y = dot(a2v.objBinormal, eyeVec);
tanEyeVec.z = -invBumpDepth * dot(a2v.objNormal, eyeVec);
v2f.tanEyeVec = tanEyeVec;
// Transform the light vector into tangent space.
// We will use this later for tangent-space normal mapping
float3 lightVec = lightCoord - a2v.objCoord;
float3 tanLightVec;
tanLightVec.x = dot(a2v.objTangent, lightVec);
tanLightVec.y = dot(a2v.objBinormal, lightVec);
tanLightVec.z = dot(a2v.objNormal, lightVec);
v2f.tanLightVec = tanLightVec;
return v2f;
}
8.5.2. 단편 셰이더
광선을 날릴 준비는 끝났다. 우리는 시작점(정점 셰이더에서 넘어온 기반 텍스처 좌표)과 방향(접공간 시점 벡터)을 알고 있다.
단편 셰이더에서는 먼저 방향 벡터를 정규화한다. 거리 맵에 저장된 거리는 픽셀에 비례하는 양으로 저장되어 있음을 주의하라. 방향 벡터는 텍스쳐 좌표의 단위로 측정된다. 일반적으로 거리 맵은 깊이에 비해 훨씬 높고 넓어서 거리의 두 측정값은 상당히 다르다. 우리의 벡터가 거리 맵에서 쓰는 거리의 측정값에 맞추어 정규화되었음을 보장하기 위해, 방향 벡터를 정규화하고 (depth/width, depth/height, 1)이라는 추가적인 "정규화 인자"를 곱한다.
이제 광선을 점진적으로 진행시킬 수 있다. 먼저 거리 맵을 질의하여, 표면과 교차하지 않고 안전하게 광선을 진행시킬 수 있는 거리의 측정량을 얻는다. 현재 위치에서 그만큼 진행하면 여전히 표면의 밖에 있는 점을 얻게 된다. 이런 식으로 계속하여 변위된 표면에 수렴하는 일련의 점들을 만들어낸다. 마지막으로, 교점의 텍스처 좌표를 얻은 후에는 접공간 상에서 노멀 매핑된 빛을 계산한다.
예제 8-2. 단편 셰이더
f2fConnector distanceFragment(v2fConnector v2f,
uniform sampler2D colorTex,
uniform sampler2D normalTex,
uniform sampler3D distanceTex,
uniform float3 normalizationFactor)
{
f2fConnector f2f;
// Normalize the offset vector in texture space.
// The normalization factor ensures we are normalized with respect
// to a distance which is defined in terms of pixels.
float3 offset = normalize(v2f.tanEyeVec);
offset *= normalizationFactor;
float3 texCoord = v2f.texCoord;
// March a ray
for (int i = 0; i < NUM_ITERATIONS; i++) {
float distance = f1tex3D(distanceTex, texCoord);
texCoord += distance * offset;
}
// Compute derivatives of unperturbed texcoords.
// This is because the offset texcoords will have discontinuities
// which lead to incorrect filtering.
float2 dx = ddx(v2f.texCoord.xy);
float2 dy = ddy(v2f.texCoord.xy);
// Do bump-mapped lighting in tangent space.
// 'normalTex' stores tangent-space normals remapped
// into the range [0, 1].
float3 tanNormal = 2 * f3tex2D(normalTex, texCoord.xy, dx, dy) - 1;
float3 tanLightVec = normalize(v2f.tanLightVec);
float diffuse = dot(tanNormal, tanLightVec);
// Multiply diffuse lighting by texture color
f2f.COL.rgb = diffuse * f3tex2D(colorTex, texCoord.xy, dx, dy);
f2f.COL.a = 1;
return f2f;
}
8.5.3. 필터링에 관한 노트
텍스처를 추출할 때는 텍스처 색인(texture lookup)의 도함수를 어떻게 나타낼 지에 신경써야 한다. 보통은 변위된 텍스쳐 좌표들이 (예를 들어 텍스처의 가려진 부분들로 인해) 불연속적이기 때문이다. 밉매핑이나 비등방성 필터링(anisotropic filtering)이 활성화되어 있다면 GPU는 텍스처 좌표의 도함수 정보가 필요하다. GPU가 도함수를 유한차로 근사하기 때문에 이 도함수들의 값은 정확하지 않으며 불연속이다. 따라서 밉맵 수준이 잘못 선택되며, 불연속 구간에서 이음매가 드러난다.
변위된 텍스처 좌표의 도함수를 대신 기본 텍스처 좌표의 도함수로 대체할 수도 있다. 변위된 텍스처 좌표는 항상 연속이고 기본 텍스처 좌표와 거의 같은 비율로 변하기 때문에 이 방법이 가능하다.
우리는 GPU의 밉맵 수준 결정 메커니즘을 사용하지 않기 때문에 텍스처 계단현상(aliasing)이 일어날 수 있다. 이것이 실제로는 큰 문제가 아닌데, 기본 텍스처 좌표의 도함수가 변위 텍스처 좌표를 훌륭하게 근사하기 때문이다.
밉맵 수준에 관한 동일한 논의가 거리 맵의 색인에도 적용됨에 주목하자. 텍스처 좌표는 뾰족한 부분에서 불연속일 수 있기 때문에 텍스처 유닛이 너무 거친 밉맵 수준에 접근할 것이다. 그러면 잘못된 거리 값을 얻는다. 우리의 해결책은 밉맵 없이 거리 맵을 선형 보간하는 것이다. 거리 맵 값은 절대로 직접 시각화되지 않기 때문에, 여기서 밉매핑을 하지 않는다고 계단현상이 일어나진 않는다.
8.6 결과
우리는 거리 매핑 알고리즘을 OpenGL과 Cg, 그리고 Sh(http://www.libsh.org)로 구현했다. 이 글의 그림들은 Sh 구현에서 가져온 것이며 동반된 CD로도 얻을 수 있다. 대량의 의존 텍스처 읽기와 도함수 명령 때문에 이 구현은 GeForce FX와 GeForce 6 시리즈 GPU에서만 작동한다.
글의 모든 예제에서 256x256x16 크기의 3D 텍스처를 사용했지만 이런 선택이 너무 데이터에 의존적일 수도 있다. 복잡한 데이터 집합에 대해 512x512x32 크기의 맵도 실험해봤다. 예제에서는 NUM_ITERATIONS의 값을 16번 반복으로 설정했다. 대부분의 경우 이는 과도하며 부드러운 데이터 집합에는 8번만으로도 충분하다.
조각 셰이더에서는 16번 반복을 가정하여 GeForce FX 기준으로 48개 명령으로 컴파일된다. 루프의 각 반복마다 두 명령을 수행한다. 텍스처 색인 다음에 곱-합을 수행한다. GeForce FX와 GeForce 6 시리즈 GPU들은 이 명령을 단일 클락 사이클에 수행할 수 있으며, 이는 각 반복이 오직 한 클락 사이클을 소비한다는 뜻이다. 각 텍스처 색인이 오직 이전 색인 명령에 의존한다. 간접 텍스처 작업의 수를 제한하는 GPU에서는 이 셰이더를 다중 패스로 쪼개야 함에 주의하라.
우리의 거리 매핑 구현을 GeForce FX 5950 Ultra와 GeForce 6800 GT에서 약간 다른 데이터 집합을 가지고 실험했다. 그림 8-6에 그런 데이터 집합이 하나 나타나있다. GeForce FX 5950 Ultra에서 우리는 1024x768 해상도의 모든 픽셀을 약 30 fps로 처리할 수 있었다. 반복 수를 8로 줄이면 같은 해상도에서 약 75 fps까지 올라간다. GeForce 6800 GT에서는 1280x1024 해상도에서 16번 반복을 하면서도 70 fps를 유지했다.
그림 8-6 변위 맵핑으로 렌더링한 하수구 창살
8.7 결론
우리는 음함수의 광선 검출에 기반한 변위 맵핑을 위한 고속 반복법인 거리 맵핑을 소개했다. 거리 함수에 포함된 정보를 이용하여, 광선이 표면에서 멀 때는 많이 전진하되, 렌더링된 기하에 틈이 생기지는 않음을 보장한다는 것을 보였다. 최종 구현은 매우 효율적이다. 몇 번의 반복만으로 수렴하고, 각 반복은 GeForce FX와 GeForce 6 시리즈의 GPU에서 단일 사이클만을 소모한다.
다음에는 셰이더 모델 3 GPU의 동적 분기 기능을 이용하여 빠르게 수렴하는 "조기 탈출(early out)"을 도입해 우리의 기법을 개선할 것이다. 그럼으로써 더 느리게 수렴하는 픽셀에 계산을 집중하여 우리 기법의 품질을 향상할 수 있다.
또한 이 기법을 곡면에도 적용할 계획이다. 이 알고리즘을 적당한 기울기를 가진 어느 모델에든 사용할 수 있지만 곡률이 큰 영역에서는 찌그러지게 된다. 특히 일반화된 변위 매핑(Wang et al. 2004)은 곡면에 대해 일종의 사면 사영(tetrahedral projection)을 사용하는데 이 방법은 우리의 알고리즘에도 적용할 수 있다. 사면 사영은 정점 셰이더에서 구현할 수 있다. (Wylie et al. 2002)
8.8 참고문헌
- Cook, Robert L. 1984. "Shade Trees." In Computer Graphics (Proceedings of SIGGRAPH 84) 18(3), pp. 223–231.
- Danielsson, Per-Erik. 1980. "Euclidean Distance Mapping." Computer Graphics and Image Processing 14, pp. 227–248.
- Hart, John C. 1996. "Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces." The Visual Computer 12(10), pp. 527–545.
- Kaneko, Tomomichi, Toshiyuki Takahei, Masahiko Inami, Naoki Kawakami, Yasuyuki Yanagida, Taro Maeda, and Susumu Tachi. 2001. "Detailed Shape Representation with Parallax Mapping." In Proceedings of the ICAT 2001 (The 11th International Conference on Artificial Reality and Telexistence), Tokyo, December 2001, pp. 205–208.
- Policarpo, Fabio. 2004. "Relief Mapping in a Pixel Shader Using Binary Search."Http://Www.Paralelo.Com.Br/Arquivos/ReliefMapping.Pdf
- Wang, Lifeng, Xi Wang, Xin Tong, Stephen Lin, Shimin Hu, Baining Guo, and Heung-Yeung Shum. 2003. "View-Dependent Displacement Mapping." ACM Transactions on Graphics (Proceedings of SIGGRAPH 2003) 22(3), pp. 334–339.
- Wang, Xi, Xin Tong, Stephen Lin, Shimin Hu, Baining Guo, and Heung-Yeung Shum. 2004. "Generalized Displacement Maps." InEurographics Symposium on Rendering 2004, pp. 227–234.
- Wylie, Brian, Kenneth Moreland, Lee Ann Fisk, and Patricia Crossno. 2002. "Tetrahedral Projection Using Vertex Shaders." InProceedings of IEEE Volume Visualization and Graphics Symposium 2002, October 2002, pp. 7–12.
Special thanks to Stefanus Du Toit for his help implementing the distance transform and writing the Sh implementation.