
Papers : CRACK-FREE POINT-NORMAL TRIANGLES USING ADJACENT EDGE NORMALS - Nvidia
인접 경계 노말(Adjacent Edge Normals)을 사용한 Crack-Free Point-Normal Triangles(PN-AEN)은 아티스트의 추가 개입 없이 크랙 없는 메시를 생성하는 기존 하드웨어의 리얼타임 테셀레이션 방식입니다.
이 솔루션은 기존 버텍스 버퍼 데이터와 함께 실시간 렌더링 애플리케이션(예: 게임)과 같은 기존 삼각형 기반 메시에서 사용하는 인덱스 버퍼에서 쉽게 계산할 수 있는 인덱스 버퍼 데이터를 사용합니다.
이 접근 방식은 Curved PN Triangles Paper(Vlachos, et al.)에서 처음 제안한 PN triangles을 확장한 것입니다.

각 면에 발산 노멀이 있는 소스 큐브(왼쪽), 결과물인 PN-트라이앵글 테셀레이션(가운데), 크랙이 없는 테셀레이션(오른쪽).
하드웨어 테셀레이션을 처음 접하는 독자가 많을 수 있으므로 간략한 개요를 제공합니다.
하드웨어 테셀레이션에 익숙한 독자는 이 섹션을 건너뛰셔도 됩니다.
프로그래밍 가능한 테셀레이션은 Direct3D 11 및 OpenGL 4.0의 새로운 기능으로, 하드웨어가 GPU에서 연속적인 지오메트리를 생성할 수 있게 해줍니다. 이를 통해 이전에는 메모리 또는 복잡성 요구 사항으로 인해 실현 불가능했던 기술을 구현할 수 있습니다.
테셀레이션은 그림 2와 같이 세 가지 새로운 단계를 추가하여 개념적 파이프라인을 수정합니다:

테셀레이션에는 세 가지 일이 필요합니다:
테셀레이션 패치를 위한 새로운 primitive topology 유형이 Direct3D 11에 포함되어 patche당 1~32개의 control points를 사용하여 patche를 지정할 수 있습니다. “Control Point"라는 용어는 테셀레이션에 입력되는 버텍스를 지칭하는 데 사용되며, 테셀레이션을 논의할 때 일반적으로 버텍스는 테셀레이션의 결과로 생각됩니다.
새로운 토폴로지 유형은 다음과 같은 형식을 따릅니다.
// 여기서 X는 patche당 Control Point 수를 나타냅니다.
D3D11_PRIMITIVE_TOPOLOGY_X_CONTROL_POINT_PATCHLIST
참고: 테셀레이션이 비활성화된 상태에서 새 인덱스 버퍼 유형을 사용하는 경우 각 패치의 처음 세 인덱스는 삼각형 목록으로 직접 해석됩니다. 예를 들어 입력 데이터 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }에 인덱스 버퍼 유형 PATCH_9를 사용하는 경우: 헐과 도메인 셰이더가 NULL로 설정되어 있으면 두 개의 트라이앵글이 그려집니다: {0, 1, 2}와 {9, 10, 11}.
Hull Shader는 다음과 같은 작업을 수행합니다:
Output primitive type
Triangle winding specification
Geometry generation method ( "fractional_even", "fractional_odd", "int", "pow2" )\
그러나 이러한 파라미터는 모두 셰이더 실행 시점이 아닌 셰이더 생성 시점에 attributes를 통해 설정됩니다.
Hull Shader는 두 단계로 작동합니다.
첫 번째 단계인 Hull Control Point shading은 각 출력 제어점에 대해 별도의 스레드가 실행되는 단일 프로그램 다중 데이터(SPMD)로 실행됩니다. 이 단계에서 병렬로 실행되는 스레드 수는 “output control points” shader attribute를 수정하여 지정할 수 있습니다.
단일 패치에 속한 모든 Hull Control Point threads가 완료되면 하드웨어는Hull Patch Constant Function를 단일 스레드로 실행합니다. Patch Constant Function는 출력 패치<ControlPointOutput, OUTPUT_CONTROL_POINTS> 유형의 입력 파라미터를 지정하여 Hull Control Point Functions의 결과를 읽을 수 있습니다.
이 단계에서는 control points를 수정할 수 없습니다.
Hull Patch Constant Function는 테셀레이션 생성기(TG)가 새로운 지오메트리를 생성하는 데 사용할 LOD 파라미터를 설정하는 역할을 담당합니다.
참고: SV_TessFactor의 순서는 삼각형 래스터화에서 흔히 볼 수 없는 언어로 설명됩니다. SV_TessFactor를 올바르게 지정하지 않으면 에지 간 LOD 불일치로 인해 패치 간 균열이 발생할 수 있습니다.

Tessellation Factors는 삼각형 영역에서 테셀레이션을 위해 N번째 버텍스의 반대쪽 에지로 지정됩니다.
The Patch Constant Shader는 도메인 셰이딩 중에 사용할 상수 세트를 추가로 출력할 수 있습니다.
TG는 Hull Patch Constant Function가 완료되면 실행되는 고정 기능 유닛입니다.
위에 지정된 정보(Output Primitive Type, Forward Winding Specification, Geometry Generation Method)를 사용하여 패치 스페이스에서 지오메트리를 생성합니다. 패치 스페이스는 각 프리미티브 유형에 대해 특별히 정의된 공간입니다. 출력되는 값은 인접한 에지에 대해 입력 LOD 값이 동일하다면 패치 에지 전체에 걸쳐 비트 단위 정확도가 보장됩니다. 따라서 크랙 없는 메시를 생성하려면 적절한 에지 LOD 사양을 지정하는 것이 매우 중요합니다.
예를 들어 삼각형을 출력할 때 TG는 Barycentric Coordinates를 사용하여 정점 세트를 출력합니다. 쿼드를 출력할 때 TG는 UV 좌표 세트를 출력합니다.
TG가 compile-time Hull Shader parameters와 런타임 지정 LOD 파라미터에 따라 지오메트리를 생성한 후 도메인 셰이더는 새로 생성된 버텍스에서 실행되기 시작합니다.
도메인 셰이더는 일반적으로 InputPatch<ControlPointOutput, OUTPUT_CONTROL_POINTS>, 선택적 PatchConstant(the Patch Constant portion of the Hull Shader 부분에서 출력), 현재 셰이딩 중인 패치 공간의 위치에 해당하는 the semantic SV_DomainLocation,을 가진 파라미터의 세 가지 항목을 입력으로 받습니다.
도메인 셰이더는 세 가지 정보를 모두 결합하여 버텍스를 Patch Space에서 Clip (or Projection) space로 변환합니다.
이제 테셀레이션의 기본 사항을 살펴보았으니 테셀레이션 워크로드를 효율적으로 분할하는 방법에 대해 논의해 볼 가치가 있습니다. 표준 파이프라인과 마찬가지로 작업은 가능한 한 가장 낮은 빈도로 수행해야 합니다. 즉, 초기 단계에서 약간 더 복잡한 작업을 수행하여 이후 단계에서 더 간단한 작업을 수행할 수 있도록 하는 것이 좋습니다.
다음은 다양한 테셀레이션 기법에서 잘 작동하는 구체적인 분석입니다:
이 분류에는 몇 가지 장점이 있습니다:
Curved PN Triangles, Vlachos, et al, 논문에서 purely local information 및 non-C1 source data로는 출력 메시의 크랙을 피할 수 없음을 증명합니다.
참고: C(숫자) 표기는 the continuity of derivatives of a surface.을 나타냅니다.
표면에 크랙이 없는 경우(메시 전체에 걸쳐 위치가 연속적이고, T 접합이 없는 경우 등) 표면을 C0이라고 합니다. 표면의 모든 위치에 노멀이 하나만 있는 경우(서페이스 노멀에 불연속성이 없는 경우) 표면을 C1이라고 합니다.
PN-AEN은 (Bezier control points of the surface을 결정하는 동안) Hull Shading 단계에서 이웃 정보를 제공하여 메시 가장자리의 양쪽을 따라 계산된 제어점이 정렬될 수 있도록 함으로써 이 문제를 해결합니다.
이를 위해서는 다음과 같은 사항이 필요합니다:
이러한 방식으로 PN-AEN은 C0인 소스 머티리얼에 대해 크랙이 없는 메쉬를 생성할 수 있으며, 이는 C1의 PN 트라이앵글 요건과 비교됩니다.
PN-AEN은 트라이앵글 메시(position and normal 포함)가 이미 존재하는 실시간 그래픽 애플리케이션(예: 게임)에서 아티스트의 추가 작업 시간을 확보할 수 없을 때 사용하기에 적합합니다.
PN-AEN은 성능 저하 없이 실루엣과 섀도잉 품질을 개선하는 데 사용할 수 있습니다.
다음 그림은 이 기법을 사용한 경우와 사용하지 않은 경우의 두 가지 모델을 보여줍니다.
왼쪽 이미지는 테셀레이션되지 않은 원본 메시이고, 오른쪽 이미지는 PN-AEN을 사용하여 테셀레이션된 이미지입니다.


PN-AEN은 패치당 9개의 제어점 기본 유형을 사용합니다. 관례상 처음 3개의 인덱스는 트라이앵글 목록에서 지정하는 것처럼 트라이앵글을 지정합니다.
이를 통해 실제로 동일한 버퍼를 사용하여 null Hull and Domain Shaders를 지정하여 테셀레이션되지 않은 지오메트리를 그릴 수 있습니다.
나머지 인덱스는 삼각형의 각 가장자리에 대해 인접한 꼭지점에 해당합니다. 이는 그림 6에 더 명확하게 나와 있습니다.

사전 처리 단계는 PN-AEN 트라이앵글 인덱스 버퍼를 계산하는 데 사용됩니다. 이 알고리즘의 레퍼런스 구현은 NVIDIA 개발자 지원팀에 문의하여 얻을 수 있습니다.
이 알고리즘은 삼각형의 수가 선형이며, 상수 계수는 사용된 저장 방법에 따라 달라집니다.
source Index Buffer(IB)가 TRIANGLELIST과 Vertex Buffer(VB)로 주어지면 다음과 같이 PN-AEN에 필요한 인접 에지 정보를 구축합니다:
이 알고리즘이 완료되면 PN-AEN과 함께 사용하기에 적합한 버퍼를 사용할 수 있습니다.
참고: PN-AEN 인덱스 버퍼에 있는 데이터의 1/3은 TRIANGLELIST 인덱스 버퍼에 저장된 정보와 동일합니다. 따라서 “일반” 인덱스 버퍼 데이터는 한 파일에 저장하고 PN-AEN에 고유한 데이터만 다른 파일에 저장한 다음 테셀레이션이 활성화된 경우 에셋 로드 시 함께 인터리빙하는 것이 유리할 수 있습니다.
PN-AEN은 PN 트라이앵글에 사용되는 도메인 셰이더와 동일한 도메인 셰이더를 사용하며, 차이점은 전적으로 Hull Shading(Bezier Control Points)에 있습니다.
SIMD 효율적인 코드를 제공하기 위해 각 hull shader control point 프로그램은 한 번에 하나의 에지에서 작동합니다. 각 스레드에서 출력되는 값은 그림 7에 설명되어 있습니다. 이 그림에서 타원은 단일 스레드에서 작업한 값을 나타냅니다. 가운데 사각형은 Hull Shader의 Patch Constant 부분에 의해 계산된 값입니다.

다음 페이지부터 최적화된 참조 구현이 제공됩니다. 참고로 부록에는 PN 트라이앵글의 유사하게 최적화된 구현이 PN 또는 PN-AEN 트라이앵글과 함께 사용되는 전체 버텍스 및 도메인 셰이더와 함께 제공됩니다.
// The Patch Control Point portion of the Hull Shader.
[domain("tri")]
[partitioning(PARTITION_METHOD)]
[outputtopology("triangle_cw")]
[patchconstantfunc("HS_Constant")]
[outputcontrolpoints(3)]
HS_ControlPointOutput HS_PNAENTriangles(
InputPatch<HS_RenderSceneInput, 9> I,
uint uCPID : SV_OutputControlPointID
)
{
HS_ControlPointOutput O = (HS_ControlPointOutput) 0;
// The PN-AEN Index buffer provides access to the neighbor across
// the edge of the triangle. Compute where
// they are here.
const uint NextCPID = uCPID < 2 ? uCPID + 1 : 0; // (uCPID + 1) % 3
const uint AdditionalData = 3 + 2 * uCPID;
const uint NextAdditionalData = AdditionalData + 1;
float3 myCP, otherCP;
// Copies first.
O.f3ViewPosition[0] = I[uCPID].f3ViewPosition;
O.f3WorldNormal = I[uCPID].f3WorldNormal;
O.f2TexCoord = I[uCPID].f2TexCoord;
// Calculate control points next. To compute a crack-free control
// point, we average the control point we'd like with the
// control point our neighbor would like. The result is that we
// both agree on where that control point should go--and that
// results in crack-free tessellation!
// This is the only difference between PN and PN-AEN tessellation,
// made possible by modern programmable tessellation hardware.
myCP = ComputeCP(I[uCPID].f3ViewPosition,
I[NextCPID].f3ViewPosition,
I[uCPID].f3ViewNormal);
otherCP = ComputeCP(I[AdditionalData].f3ViewPosition,
I[NextAdditionalData].f3ViewPosition,
I[AdditionalData].f3ViewNormal);
O.f3ViewPosition[1] = (myCP + otherCP) / 2;
myCP = ComputeCP(I[NextCPID].f3ViewPosition,
I[uCPID].f3ViewPosition,
I[NextCPID].f3ViewNormal);
otherCP = ComputeCP(I[NextAdditionalData].f3ViewPosition,
I[AdditionalData].f3ViewPosition,
I[NextAdditionalData].f3ViewNormal);
O.f3ViewPosition[2] = (myCP + otherCP) / 2;
// Note: We're relying on the optimizer to avoid projecting to
// projection space twice. Probably better to be explicit,
// but this is a bit clearer.
if (g_clipping.x)
{
O.fClipped = ComputeClipping(g_f4x4Projection,
O.f3ViewPosition[0],
O.f3ViewPosition[1],
O.f3ViewPosition[2]);
}
else
{
O.fClipped = 0.0f;
}
// Perform Adaptive Tessellation step here.
if (g_adaptive.x)
{
O.fOppositeEdgeLOD = ComputeEdgeLOD(g_f4x4Projection,
O.f3ViewPosition[0],
O.f3ViewPosition[1],
O.f3ViewPosition[2],
I[NextCPID].f3ViewPosition);
}
else
{
O.fOppositeEdgeLOD = g_f4TessFactors.x;
}
return O;
}
// The Hull Shader Constant function, which is run after all threads
// of the Hull Shader Control Point function (above) complete.
HS_ConstantOutput HS_Constant(
const OutputPatch<HS_ControlPointOutput, 9> I)
{
HS_ConstantOutput O = (HS_ConstantOutput) 0;
// These were computed during the Control Point phase of either PN
// or PN-AEN Triangles.
// We're just aliasing them to better match our functionality with
// the reference implementation.
float3 f3B300 = I[0].f3ViewPosition[0],
f3B210 = I[0].f3ViewPosition[1],
f3B120 = I[0].f3ViewPosition[2],
f3B030 = I[1].f3ViewPosition[0],
f3B021 = I[1].f3ViewPosition[1],
f3B012 = I[1].f3ViewPosition[2],
f3B003 = I[2].f3ViewPosition[0],
f3B102 = I[2].f3ViewPosition[1],
f3B201 = I[2].f3ViewPosition[2];
// The tessellation factors map up in a somewhat surprising way.
// Specifically, if you think of a triangle with indices 0, 1 and
// 2, the LOD values map to the edge that is opposite the index.
// That is to say that TessFactor[0] is the LOD for edge (1,2).
// Here's the complete table:
// TessFactor[0] => Edge(1, 2)
// TessFactor[1] => Edge(2, 0)
// TessFactor[2] => Edge(0, 1)
O.fTessFactor[0] = I[1].fOppositeEdgeLOD;
O.fTessFactor[1] = I[2].fOppositeEdgeLOD;
O.fTessFactor[2] = I[0].fOppositeEdgeLOD;
// There's no right or wrong answer here. We've chosen to say that
// the interior should be at least as tessellated as the most
// tessellated exterior edge.
O.fInsideTessFactor[0] = max(max(O.fTessFactor[0],
O.fTessFactor[1]),
O.fTessFactor[2]);
// Center control point
float3 f3E = (f3B210 + f3B120 + f3B021 + f3B012 + f3B102 + f3B201) / 6.0f;
float3 f3V = (f3B003 + f3B030 + f3B300) / 3.0f;
O.f3ViewB111 = f3E + ((f3E - f3V) / 2.0f);
// Determine whether the center control point is visible or not.
float fB111Clipped = IsClipped(ApplyProjection(g_f4x4Projection, O.f3ViewB111));
if (I[0].fClipped && I[1].fClipped && I[2].fClipped && fB111Clipped)
{
// If all control points are clipped, the surface is
// almost certainly not visible.
O.fTessFactor[0] = O.fTessFactor[1] = O.fTessFactor[2] = 0;
}
return O;
}
PN-AEN은 모든 C0 입력 메시에서 크랙이 없는 메시를 생성할 수 있지만, 한 가지 주의할 점이 있습니다. 메시에는 안쪽을 가리키는 노멀(표면이 오목함을 나타냄)이 있어 가장자리 근처에서 메시 상호 침투가 발생할 수 있습니다. 사소한 문제이긴 하지만 주의해야 할 문제입니다. 이는 그림 8에 더 명확하게 설명되어 있습니다.

PN-AEN은 PN 트라이앵글을 새롭게 개선한 것으로, 더 다양한 입력 메시에서 크랙 없는 테셀레이션을 생성하고 분리된 노멀이 발생할 때 하드 에지를 보존합니다.
PN 트라이앵글과 마찬가지로 PN-AEN은 아티스트의 추가 개입 없이도 메모리 사용량보다 훨씬 더 높은 곡률을 가진 표면을 생성할 수 있습니다.
또한 최신 GPU의 하드웨어 테셀레이션에 대한 개요와 효율적이고 일반적인 작업 분석을 제공합니다.