이번 시간에는 지난 시간들까지 언급한 내용을 기반으로 해서,
간단한 테셀레이션 작업을 구현해 보려 합니다.

당연한 얘기이겠지만,
하드웨어 기반의 테셀레이션은 하드웨어의 지원이 없으면 매우 느립니다.
즉 DirectX11 이상을 지원하는 그래픽 카드가 아니면,
효과를 눈으로 확인하는 것조차 무척 고통스럽습니다.

그래서 이번 시간에 만들 테셀레이션은 간단히 삼각형 하나를 이용합니다.
우리는 이 삼각형 하나를 가지고 테셀레이션 작업을 수행할 것이며,
DirectX11 을 지원하지 않는 그래픽카드라면
강제적으로 REF 모드로 테셀레이션 작업을 수행하도록 합니다.

먼저 결과 샘플을 보면 아래와 같습니다.



이제 우리가 만들려는 그림이 그려졌으니, 직접 코딩 작업을 시작하겠습니다.
이 글에서는 DirectX11 의 기본 셋팅과 관련한 사항은 생략합니다..^^
자세한 API 적인 설명은 생략을 하니 DirectX 2010 6월 버전의 SDK 의 튜토리얼을 참고하시거나,
'알코코더의 DirectX11' 을 참고하시기 바랍니다.^^

우리가 이번 샘플에서 사용할 버텍스 데이터의 형식은 위치 정보만 있으면 됩니다.
이번 샘플에서는 최대한 간단하게 작성하는 것을 목적으로 했기 때문에,
많은 정보를 필요로 하지는 않습니다..^^
그래서 아래와 같이 간단한 버텍스 형식을 정의했습니다..^^



생소한 데이터 타입이 보입니다. 바로 XMFLOAT3 입니다.
DirectX11 부터는 D3DX 계열의 수학 데이터 타입들은 더 이상 업데이트 되지 않습니다.
지금부터는 XNA Math 라는 수학 라이브러리를 사용합니다.
그렇다고 더 이상 D3DX 계열의 수학 데이터 타입들을 사용할 수 없는 것은 아니니, 안심하시기 바랍니다.
이들에 대해서는 향후 언급할 기회가 있으니,
지금은 D3DX 계열의 수학 클래스 대신에 XNA Math 라는
새로운 수학 클래스를 사용한다는 정도로만 인식하고 넘어가겠습니다.^^


아래는 우리가 애플리케이션 전역으로 사용할 변수들의 선언입니다.



그 동안의 DirectX11을 언급하면서 꾸준히 언급되던 내용이기에 자세한 설명은 생략하겠습니다.

특이할 만한 것이라면, 래스터라이져 스테이트 오브젝트를 2개 만드는 것입니다.
이는 우리의 샘플이 솔리드( Solid ) 한 렌더링과 와이어프레임( Wire-Frame ) 기반의 렌더링으로
전환이 가능하기 때문입니다.

다음은 상수버퍼( ConstantBuffer ) 에 관한 전역 선언들 입니다.



우리는 월드 좌표계의 정점을 버퍼에 입력할 것입니다.
그래서 View-Projection 행렬만 변환을 위해서 필요합니다.
그리고 얼마나 테셀레이션 작업을 세밀하게 할지를 결정하는 상수를 하나 추가합니다.



쉐이더를 컴파일 해주는 보조 함수를 다음과 같이 하나 만듭니다.


이제 본격적으로 시작을 합니다.
InitD3D() 에 각종 초기화 작업을 수행합니다.
앞서 잠깐 언급드렸듯이,
DirectX11을 지원하는 하드웨어가 아니면, 강제로 REF 모드로 동작하도록 합니다.
또한 이 함수에서는 각 쉐이더 스테이지에 대응되는 HLSL 코드를 컴파일 해줍니다.
그리고 이들에 대한 각 오브젝트를 만듭니다.
초기화 작업은 주로 반복적인 작업이 많기 때문에, 설명은 생략합니다.

InitD3D() 에 버텍스버퍼의 데이터를 설정해 줘야 합니다.
이번 샘플에서는 월드 좌표로 정의된 삼각형을 사용할 것입니다.
또한 카메라 공간에 대한 설정도 같이 해 줍니다.
이들에 대한 코드는 아래와 같습니다.


이 정도로 초기화와 관련된 작업을 마무리 합니다.
이제는 프레임 관련한 처리를 작성합니다.( Render() )

이 Render() 부분에서는 상수버퍼에 설정할 데이터들을 다음과 같이 업데이트 합니다.

 


우리는 와이어프레임 모드와 솔리드 모드의 렌더링 방식 둘 다를 표현할 것이기에,
이들에 대한 설정도 아래와 같이 고려해 주어야 합니다.



그리고 마지막으로 입력되는 버텍스 형식을 알려주고 버텍스 버퍼를 연결한 후에,
그리기 작업을 수행합니다.^^



이제 키보드 이벤트에 따라 약간의 변화를 주는 작업을 합니다.
현재는 'w' 키로 렌더링 모드를 Wire 와 Solid 간의 토글이 되도록 설정합니다.
그리고 위/아래 방향키로 테셀레이션의 분할 정도를 증감합니다.

이번 작업은 여기까지 입니다.
지금까지 DX11을 살펴보면서, 언급된 내용들이 대부분이라 전체적으로 설명드리지는 않습니다.
( HLSL 코드도 최대한 간결하게 작성했습니다..^^ )
샘플을 같이 첨부드리니, 직접 작성하시면서 익혀보시기 바랍니다.^^

신고


< Tessellator >

테셀레이터는 Hull Shader 의 결과를 입력으로 받아서 작업을 합니다.
이 스테이지는 프로그래머가 제어할 수 없는 영역입니다.( 정말 다행이죠? ^^ )
앞선 Hull Shader 스테이지에서 정의된 폴리곤 분할 방법과 분할 수치에 따라서
실제로 Vertex 데이터들을 생성할 수 있는 정보를 주게 됩니다. 
즉, 우리는 큰 덩어리 형태의 Vertex 데이터만 HullShader 를 통해서 전달할 뿐입니다.
테셀레이터의 정해진 연산에 의해서,
도메인 쉐이더( DomainShader )에 무게 중심 좌표( BarycentricCoordinates )들을 전달
하게 됩니다.



< 무게 중심 좌표( BarycentricCoordinates ) >

무게 중심 좌표를 언급하기 전에, 벡터의 외적의 성질에 대해서 언급할 사항이 있습니다.
우리가 이미 알고 있듯이, 두 벡터의 외적 연산으로 두 벡터에 수직인 벡터를 구할 수 있습니다.
지금부터 여기에 주목할 것은 이렇게 외적 연산을 통해서 얻어진 벡터의 길이입니다.
이렇게 구해진 벡터의 길이는 기하학적으로 두 벡터를 평행사변형을 만들었을 때, 넓이를 의미합니다.
아래의 그림이 이해에 도움이 되었으면 좋겠습니다.^^
꽤 재미있는 성질이지 않습니까? ^^
( 이미 다들 알고 계셨을 것이라 생각하지만, 처음 접했을때, 저는 꽤 재미있는 성질이라고 생각했습니다..^^ )



두 벡터의 외적으로 나온 결과 벡터의 길이가 평행사변형의 넓이라는 사실을 인지한다면,
우리는 이제 무게 중심 좌표에 대해서 얘기할 수 있습니다.
힌트를 드리면, 무게 중심 좌표는 다른 말로 면적 좌표로도 불리기도 합니다.


삼각형 내부의 임의의 점 P는 점 A,B,C를 구성하는 삼각형들의 비율로 표현할 수 있습니다.
위의 그림에서 나오는 공식과 그림은 바로 이를 보여드리고 있습니다.
w들은 가중치 상수를 의미합니다.
각 가중치들의 합은 반드시 1.0 이여야 합니다.
만약 C의 가중치인 w3 의 경우에는 삼각형 APB 의 넓이 / 삼각형 ABC의 넓이 가 되는 것입니다.
이런 식으로 서로 대응되는 각 가중치들을 삼각형을 구성하는 각각의  정점 위치에 대응시키면,
우리가 원하는 P의 위치를 구할 수 있습니다.


벡터 외적의 기하학적 특징을 이용해서 가중치를 구하는 코드는 아래와 같습니다.
이 코드에서는 삼각형 넓이를 구할 때 수행하는 2를 나누는 작업이 생략되어 있습니다.
이유는 어차피 이 코드의 결과는 비율에 대한 가중치이기 때문에, 2를 나누는 작업은 의미가 없기 때문입니다.


이처럼 무게중심좌표를 구하는 일이 DirectX11의 테셀레이터의 임무 중 하나입니다.
삼각형을 구성하는 세 정점이 주어졌을 때 세 정점의 가중치를 구할 수 있다면,
임의의 점 P를 구할 수 있습니다.
바로 이 역활을 수행하는 것이 테셀레이터의 기능 중 하나입니다.
앞선 언급했듯이 테셀레이터의 기능은 우리가 조작 할 수 있지 않습니다.
즉, 고정 기능입니다.

우리는 Hull Shader를 통해서 Patch를 정의하고,
이렇게 정의된 패치 데이터는 이후에 가공되지 않고, 바로 Domain Shader 에서도 사용됩니다.
( 테셀레이터에서도 이 데이터를 사용해서 연산을 합니다. )
테셀레이터 단계에서는 이 패치 데이터에 대응되는 가중치들을 구성해서,
바로 다음 단계인 Domain Shader 로 전달
하게 되는 것입니다.
물론 내부적으로는 더욱 복잡한 과정을 거치겠지만,
우리가 코딩관점에서 관심을 가질 수 있는 변수 정보는 이들 뿐입니다.

Domain Shader의 기본적인 형태는 다음과 같습니다.

[domain("tri")]
DS_OUTPUT DS( HS_CONSTANT_DATA_OUTPUT input,
                    float3 UVW : SV_DomainLocation,
                    const OutputPatch<HS_OUTPUT, 3> patches
{
   DS_OUTPUT Output;
    ...
    return Output;   
}

Domain Shader의 입력으로 들어오는 인자들을 유심히 보시기 바랍니다.
( 패치 정보와 UVW 에 바로 가중치 정보가 입력됩니다. )
이들에 대해서는 다음 시간에 살펴보도록 하겠습니다.

신고



DirectX11의 파이프라인은 앞선 시간에서 우리는 꾸준히 보았습니다.
복습의 의미에서 이번에는 http://www.realtimerendering.com 에 정의된 Direct3D 의 파이프라인입니다.

Direct3D 10 Pipeline
< Direct3D 10 pipline >

Direct3D 11 Pipeline
< Direct3D 11 pipline >

우리가 그래픽스에서 사용하는 폴리곤은 굉장히 복잡한 방식으로 처리가 됩니다.
많은 스테이지를 통해서 결국 우리는 화면 픽셀로 변환된 최종 결과를 확인하게 되는 것입니다.
그 과정 속에서 Direct3D 9에서는 Vertex와 Pixel 을 조작할 수 있도록 변화되어 왔습니다.
Direct3D 10 은 여기에 Geometry 까지 조작할 수 있도록 프로그래머들에게 개방되었습니다.
Direct3D 11 은 무려 3개의 스테이지가 추가되었습니다.
Hull Shader, Tessellator, Domain Shader 가 바로 그것들입니다.

이 중에 프로그래머가 제어하는 부분은 Hull / Domain Shader 이며,
Tessellator 의 경우에는 하드웨어가 직접 처리하게 됩니다.

테셀레이션을 언급하면서 가장 많이 나오는 주제는 현재 LOD( Level of Detail ) 처리 이지만,
정확하게 테셀레이션이 필요한 이유는 글인 http://vsts2010.net/331 을 통해서 확인할 수 있습니다.

현재 그래픽 파이프라인에서 테셀레이션 작업은 현재 옵션으로 설정되어 있습니다.
여러분이 이 기능을 사용하기 원하지 않는다면, 이들을 활성화 시키지 않으시면 됩니다.
그렇게 된다면, 기존의 파이프라인과 동일한 방식으로 Vertex 데이터를 처리하게 됩니다.


< Hull Shader >

Hull Shader 는 테셀레이션 작업의 시작입니다.
하지만, 실제로 프로그래머의 시작은 Vertex Shader 입니다.
DirectX9에서 VertexShader 는 World-View-Projection 변환을 수행하는 것이 가장 큰 목적이였습니다.
DirectX11에서 VertexShader 의 목적은 Hull Shader 로의 데이터를 전달하는 것입니다.
즉, 테셀레이션이 목적인 경우에는
DirectX11에서 VertexShader 스테이지에서 World-View-Projection 을 수행해서는 안됩니다.
테셀레이션 작업시 VertexShader 에서 처리되는 Vertex는 실제 우리가 사용하는 데이터가 아닙니다.
우리는 VertexShader 의 입력으로 들어오는 데이터를 모아서,
많은 수의 Vertex를 새롭게 생성시켜야 합니다.
그래서 테셀레이션 작업시 VertexShader 스테이지에서는 Vertex를 월드 변환까지만 수행합니다.

Hull Shader 에서는 '폴리곤을 어떻게 분할할 것인가?' 와 '폴리곤을 얼마나 분할할 것인가?' 를 결정합니다.
가장 단순한 형태로 이 Hul Shader의 기능을 표현하면 다음과 같습니다.

Diagram of the hull-shader stage

위의 그림은 MSDN 의 그림입니다.

Hull Shader 는 두 가지의 작업을 동시에 수행합니다.
그것은 제어점( Control Point ) 를 생성하는 작업과 Patch Constant Data 를 계산하는 작업입니다.
이들 작업은 병렬적으로 수행되게 됩니다.
HLSL 코드는 실제로 드라이버 수준의 하드웨어 명령어를 생성하게 되는데,
이 때, 병렬처리가 가능한 형태로 변환되게 됩니다.
이는 Hull Shader 가 빠르게 동작할 수 있는 중요한 이유이기도 합니다. 
 
Hull Shader 의 입력으로 들어오는 제어점( Control Point )들은
낮은 차수의 면을 표현하는 정점들입니다.
이를 높은 차수의 면을 표현하는 제어점들로 만들어 내게 됩니다.
이 때 생성된 제어점들은 Tessellator 스테이지에서 사용되는 것이 아니라,
그 다음 스테이지인 Domain Shader 에서 사용됩니다.



위의 그림은 베지어(Bezier) 제어점들을 이용해서 베지어 곡면을 표현한 것입니다.

근본적으로 테셀레이션은 평면을 곡면으로 생성시키는 개념과 매우 비슷합니다.
( 굳이 평면을 많은 갯수의 폴리곤으로 표현할 필요는 없기 때문이겠죠. )
그렇기 때문에, 분할 방법으로 사용되는 알고리즘들은 베지어처럼 게임 프로그래머들에게 친숙한
개념들이 사용됩니다.

Hull Shader 의 또 하나의 중요한 역활은 불필요한 연산을 줄이기 위해
테셀레이션 단계를 스킵할지를 결정할 수 있다는 것입니다.
즉, Hull Shader 에서 Tessellation Factor 가 0 이하인 경우에
이 패치는 컬링
되어 버린 것으로 간주됩니다.
( Tessellation Factor 는 얼마나 분할할지를 나타내는 수치적 비율입니다. )
이로인해 더 이상 파이프라인 처리가 이루어지지 않음으로써,
성능 향상을 도모할 수 있습니다.
( 폴리곤을 처리하지 않는 것이 가장 큰 성능의 이득이겠죠..^^ )


그러면 과연 Hull Shader 에서의 '폴리곤을 어떻게 분할할 것인가?' 와 '폴리곤을 얼마나 분할할 것인가?'
프로그램 코드에서는 어떻게 표현해야 할까요?

현재 MSDN 에 나와있는 Hull Shader 의 가장 단순한 형태는 다음과 같습니다.
( 물론 실제로 구현되고 동작되는 내용들의 예들은 DirectX11 샘플에 있습니다. )


[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(16)]
[patchconstantfunc("SubDToBezierConstantsHS")]
BEZIER_CONTROL_POINT MainHS( InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, 
                                                    uint i : SV_OutputControlPointID,  uint PatchID : SV_PrimitiveID )
{
    VS_CONTROL_POINT_OUTPUT Output;

    // Insert code to compute Output here.    
    return Output;
}

위의 Hull Shader 는 동작 방식을 설정합니다.
몇몇 정의된 값들을 셋팅해 주면, 이는 테셀레이션 작업을 하는 동안에 사용되게 됩니다.
즉, 위의 셋팅들은 '폴리곤을 어떻게 분할할것인가?' 에 준하는 프로그램 코드라 할 수 있습니다.

이제 남은 것은 '폴리곤을 얼마나 분할할 것인가?' 입니다.
이는 PatchConstantFunc 을 통해서 병렬적으로 처리된다고 앞서 설명을 했습니다.
이곳에서는 Tessellation Factor 를 계산하게 되는데, 그 결과에 따라서 컬링 작업이 실행됩니다.
( 이 값이 0 이하의 경우에는 더 이상 처리가 필요하지 않습니다. )
이 작업을 하는 함수를 우리는 직접 작성해서,
 위의 [patchconstantfunc("SubDToBezierConstantsHS")] 처럼 설정해 주면 자동적으로 동작합니다.
MSDN 에 나와있는 PatchConstantFunc의 기본적인 형태는 다음과 같습니다.

#define MAX_POINTS 32

// Patch Constant Function
HS_CONSTANT_DATA_OUTPUT
SubDToBezierConstantsHS( InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip,
                                         uint PatchID : SV_PrimitiveID )

    HS_CONSTANT_DATA_OUTPUT Output;

    // Insert code to compute Output here    
    return Output;
}

이 PatchConstantFunc 의 결과에 바로 '폴리곤을 얼마나 세밀하게 분할할 것인가?' 에 대한 정보들이 있습니다.

// Output patch constant data.
struct HS_CONSTANT_DATA_OUTPUT
{
    float Edges[4]        : SV_TessFactor;
    float Inside[2]       : SV_InsideTessFactor;
    ...
};

위의 경우의 결과 구조체는 사각형을 분할한 경우이며,
우리가 주로 사용하는 삼각형 분할의 경우에는 다음과 같을 것입니다.

// Output patch constant data.
struct HS_CONSTANT_DATA_OUTPUT
{
    float Edges[3]        : SV_TessFactor;
    float Inside       : SV_InsideTessFactor;
    ...
};

지금까지 Hull Shader의 기본적인 개념과 역활에 대해서 언급해 드렸습니다.
이렇게 얻어진 결과는 테셀레이터로 전달되게 됩니다.
세부적인 Hull Shader 의 작성은 이후의 시간들을 통해서 살펴볼 예정입니다.
( 현재 본 글들은, 개념 위주의 설명에 포커스를 두고 있습니다. ^^ )

신고



앞선 시간들을 통해서, 우리는 테셀레이션에 대해서 꾸준히 살펴보았습니다.
DirectX9 세대에서부터 테셀레이션을 사용했었으며,
ATI 의 일부 그래픽 카드들은 하드웨어 기반의 테셀레이터를 지원했었습니다.

DirectX11의 테셀레이션 작업은 하드웨어 기반으로 처리됩니다.
즉, 이는 무수히 많은 연산을 처리해서 많은 폴리곤을 화면에 보여주겠다는 하나의 의지입니다.
이들이 강력한 이유는 이전 글인 다음을 참고해 주시기 바랍니다.
http://vsts2010.net/331

요약해 보자면,
이제는 텍스쳐링보다는 폴리곤 갯수를 증가시켜서 퀄리티의 향상을 도모하겠다는 것입니다.
사실 이것에 관해서는 많은 우려와 논란이 많았던 것이 사실입니다.
하지만, 현재의 하드웨어 상황은 이들 테셀레이션 기능을 중심으로 변화하고 있는 것이 사실입니다.
아래는 초창기 ATI의 DirectX11 기반의 하드웨어 구조입니다.



ATI의 경우에는 렌더링 목적에 집중하기 위해서 하나의 테셀레이터와 두개의 래스터라이저로 처리를 했었습니다.
물론 이것은 초기 DirectX11을 지원하는 하드웨어의 경우입니다.

ATI가 이런 테셀레이션 기반의 하드웨어를 출시하자,
상대적으로 후발주자였던 NVIDIA의 경우에는 이 테셀레이터를 더 많이 사용한
DirectX11 기반의 하드웨어를 출시하게 됩니다.




위의 빨간 동그라미 영역에 4개씩 보이는 노란 박스가 모두 테셀레이터입니다.
즉 위의 경우에는 16개의 테셀레이터가 존재합니다.( 4개의 래스터라이져 )
NVIDIA의 이런 과감한(?) 테셀레이터의 지원은 ATI의 향후 대응을 기대하게 만들기도 했습니다.

이런 하드웨어적인 논란은 본 글의 취지와는 맞지 않습니다.
이 글은 이런 현재의 상황에 어떻게 대응하는 API 단계의 개발자들을 주요 대상으로 하기 때문입니다.
즉, 어느 것이 더 효과적인지는 분별하기 어렵습니다.
다만 현재까지 나온 의견을 종합해 본다면,
ATI의 경우에는 테셀레이션과 래스터라이져의 본질적인 기능을 중심으로 설계가 되었고,
NVDIA의 경우에는 테셀레이션과 래스터라이져 외에도 GPGPU 환경의 기능을 더 고려해서 설계가 되었다고 합니다.
( NVIDIA의 경우에는 CUDA라는 GPGPU 플랫폼을 XP세대에서부터 강력히 지원했었습니다.^^ )

테셀레이션은 현재까지도 많은 논란이 있습니다.
그 논란의 중심에는 '빠를까?' 라는 의구심과 '엄청난 양의 연산' 에 대한 우려가 있습니다.
또한 하드웨어 기반의 테셀레이션으로의 패러다임 전환이 쉽지 않은 것도 사실입니다.

실제로 테셀레이션 관련 샘플을 실행시켜보면, GPU의 성능에 굉장히 의존적입니다.( 당연한 얘기겠지만요...^^ )
테셀레이션 샘플들은 DirectX11 기반의 하드웨어에서 그렇게 빠르지도, 또한 느리지도 않습니다.
오히려 일반적인 상황에서는 약간의 성능 저하가 일어날 수도 있으며,
최적화를 잘한 경우에는 테셀레이션 처리가 더 느릴 수도 있습니다.
하지만, 이제 하드웨어는 테셀레이터라는 기능을 장착을 했으며,
앞으로는 테셀레이터 기반으로 최적화하는 것이 더 개발 패러다임에 적합할 것입니다.

당분간 개발 패러다임이 과도기적인 상태를 보이겠지만,
이미 그래픽카드의 발전 방향이 테셀레이터 기반으로 변경되고 있다는 것에 우리는 주목해야 합니다.
신고

오늘은 DX9 세대의 테셀레이션 마지막입니다.
ATI 는 DirectX9를 지원하는 일부 그래픽카드들은 하드웨어 기반의 테셀레이션 작업을 지원합니다.
( HD 2000 시리즈 이후 지원되었다고 합니다. )
이 방법은 왜 DirectX11의 테셀레이션 작업이 강력한지를 이해하는 좋은 출발점이 될 수 있습니다.

이 경우에는그래픽 파이프라인 구조가 다음과 같습니다.




이는 현재 X-BOX 360 에도 동일하게 적용되는 그래픽 파이프라인 구조입니다.
주목할 만한 것은 Tessellator의 위치입니다.
즉, 버텍스 쉐이더( VertexShader ) 스테이지의 앞단계에 위치하고 있습니다.
이 위치는 DX11 세대에서는 버텍스 쉐이더 다음 단계로 변경됩니다.


아래의 그림은 ATI 카드에서 지원되는 DX9 기반의 테셀레이션 작업을 보여줍니다.



DirectX9의 테셀레이션을 위해서 총 3번의 패스를 통과해야 합니다.
즉, 3번의 렌더링 작업이 필요합니다.
이렇게 많은 패스가 필요한 이유는 테셀레이션을 위해서 인접한 정점의 정보가 필요하기 때문입니다.
DX9 의 시대에서는 VertexShader 단계에서 인접한 정점의 정보를 쉽게 확인할 수 있는 방법이 없습니다.
그래서 인접한 정보를 구성하는 단계가 첫번째 패스입니다.

첫번째 패스의 렌더타겟은 백버퍼가 아니라, 텍스쳐입니다.
이 텍스쳐에 정점 정보와 정점의 인덱스를 기록하게 됩니다.
즉, rgb 에는 위치 정보가 기록되며 a 에는 정점의 인덱스 정보가 기록됩니다.

이 때, 주의할 것은 메시가 인덱스 버퍼 기반으로 렌더링 되어지는 경우입니다.
이 경우에는 인덱스 버퍼를 모두 풀어서 새로운 버텍스 버퍼를 만들어야 합니다.
우리가 필요한 것은 폴리곤을 렌더링하는 작업이 아닙니다.
인접정보를 구성하는 일임을 잊지 말아야 합니다.
첫번째 패스에서의 렌더링은 TRIANGLELIST가 아니라, POINTLIST 로 수행하게 됩니다.

또 하나 주의할 것이 있습니다.
POINTLIST 로 렌더링을 수행할 때는 WVP( World-View-Projection ) 변환이 아니라,
World-View까지만 변환
을 해야 합니다.
이유는 간단합니다.
테셀레이션은 주로 시점에 근거해서  얼마나 많은 폴리곤을 생성할지를 판단해야 합니다.
이를 앞 시간들을 통해서 Adaptive 한 방식이라고 언급을 했었습니다.
이후의 패스에서는 이들 정점에 근거해서 LOD를 판정해서 Tessellation Factor 를 연산하게 되니다.
그래서 View 좌표계까지만 변환을 합니다.
첫번째 패스에서는 이렇게 View 공간으로 POINTLIST들을 텍스쳐에 렌더링 합니다.

이렇게 생성된 텍스쳐를 기반으로 해서 두번째 패스를 진행할 수 있습니다.
DX9 를 지원하는 모든 그래픽카드가 VertexShader 단계에서 텍스쳐 데이터를 읽어올 수 있는 것은 아닙니다.
이런 제약 사항들은 이제 큰 의미가 있는 것이 아니기 때문에,
개념적인 것에 포커스를 두시기 바랍니다.^^

두번째 패스의 목적은 Tessellation Factor를 구하는 것입니다.
즉, 얼마나 폴리곤을 세분화 할지를 결정합니다.
두번째 패스도 역시 POINTLIST 로 렌더링을 합니다.
그리고 첫번째 패스에서 생성해둔 인접 정점 정보를 가진 텍스쳐를 바인딩 합니다.
인접 정보가 있기 때문에 현재 정점을 기준으로 Tessellation Factor 를 계산할 수 있습니다.
두번째 패스에서 주의할 것은 이들 Tessellation Factor 를 저장하기 위해
R2VB 라는 일종의 버퍼에 렌더링
을 한다는 것입니다.
이는 ATI 테셀레이션 라이브러리에만 존재하는 개념입니다.

세번째 패스는 실제로 지오메트리(Geometry)를 렌더링 하는 단계입니다.
실제 렌더링 작업은 TRIANGLELIST 로 렌더링 합니다.
인덱스 기반의 렌더링이 아니라,
우리가 인덱스를 풀어서 생성한 버텍스버퍼로 렌더링 하는 것에 주의해야 합니다.
이때 스트림(Stream) 을 하나 더 연결하는데,
이것은 앞서 우리가 렌더링 했던 R2VB 라는 버퍼
입니다.

결과적으로 VertexShader 에는 Barycentric coordiate 기반의 가중치 값이 전달됩니다.
즉, 무게 중심 좌표입니다.

float3 vPosTessOS = i.vPositionVert0.xyz * i.vBarycentric.x +
                              i.vPositionVert1.xyz * i.vBarycentric.y + 
                              i.vPositionVert2.xyz * i.vBarycentric.z;


정점을 구성하는 방법은 위처럼 해야 합니다.

이상으로 DX9 세대의 테셀레이션 작업들에 대해서 아주 간단히 살펴보았습니다.
메인으로 다룰 내용이 아니라서, 쉽게 넘어간 부분이 많습니다.
아무래도 거의 사용하지 않기 때문에, 깊이있게 다루는 것은 의미가 없다고 생각합니다.

하지만, DX9 세대의 테셀레이션 작업은 이렇게 복잡한 방법과 절차를 통과해야 합니다.
DX11 의 테셀레이션 작업은 상대적으로 빠른 성능으로 구현이 됩니다.
왜냐하면 1 Pass 이기 때문입니다.


ATI 는 DX9 세대의 테셀레이션 작업을 위해서, 라이브러리를 제공하고 있습니다.
더 필요한 정보가 있으시면, 아래의 링크를 참고하시기 바랍니다.

http://developer.amd.com/gpu/radeon/Tessellation/Pages/default.aspx#d3d9
신고


DirectX SDK February 2010  버전까지는 'EnhancedMesh' 라는 샘플이 있었습니다.
아쉽게도 2010 June 버전에서 이 샘플은 사라졌습니다.
메시의 퀄리티를 향상시키는 샘플인데, 실제로는 폴리곤 갯수를 증가시키고 있습니다.
굳이 실행을 실켜보실 이유는 없습니다. ^^

ID3DXMesh 인터페이스에는 멤버함수로 CloneMeshFVF() 를 가지고 있습니다.
이 멤버함수의 옵션으로 D3DXMESH_NPATCHES 을 사용하게 되면,
하드웨어 가속을 받아서 폴리곤을 증가시킬 수 있습니다.
물론 내부적으로는 많은 연산을 수행할 것입니다.



만약 테셀레이션 작업이 그래픽카드에서 지원을 해주지 않는다면,
이는 CPU 기반으로 작업을 수행해야 합니다.
바로 이를 도와주는 API 가 D3DXTessellateNPatches() 입니다.



이렇듯 DirectX9 세대에도 테셀레이션을 위해서 API들을 지원해 주고 있었습니다.
물론 정식으로 그래픽카드에서 지원을 하지 않았기 때문에,
성능에 많은 문제점을 가지고 있었습니다.
테셀레이션 자체가 근본적으로 많은 연산을 수반하기 때문입니다.

다음 시간에는, 마지막으로 ATI의 DirectX9 기반의 테셀레이션 작업에 대해서 살펴보도록 하겠습니다.^^

신고


앞선 시간을 통해서 ID3DXPatchMesh 를 이용하면
간단하게 테셀레이션이 적용된 메시를 만들 수 있음을 언급했었습니다.
실제로 D3DX 유틸리티 클래스들이 테셀레이션을 손쉽게 적용할 수 있도록 구비가 되어있습니다.
그렇다는 것은 실제로는 DirectX 내부적으로 코어한 API가 있다는 얘기입니다.

테셀레이션과 관련한 DirectX 에서 코어한 API가 바로
IDirect3DDevice9::DrawTriPatch() 와 IDirect3DDevice9::DrawRectPatch() 입니다.
API 이름에서 쉽게 이해할 수 있듯이 전자는 삼각형과 관련한 것이고 후자는 사각형과 관련한 것입니다.
두 함수의 원형은 다음과 같습니다.

HRESULT DrawTriPatch
(
  [in]  UINT Handle,
  [in]  const float *pNumSegs,
  [in]  const D3DTRIPATCH_INFO *pTriPatchInfo
);


HRESULT DrawRectPatch(
  [in]  UINT Handle,
  [in]  const float *pNumSegs,
  [in]  const D3DRECTPATCH_INFO *pRectPatchInfo
);


그런데 조금 생소한 구조체 정보를 함수 인자로 받습니다.
이 두 API들은 함수 이름에서도 알 수 있듯이 실제로 렌더링을 수행하는 API 입니다.
테셀레이션을 위해서는 테셀레이션을 위한 정보들이 존재해야 합니다.
이들에 대한 설정 작업이 이루어져야 하는데,
이를 위한 구조체가 세번째 인자인 D3DTRIPATCH_INFO와 D3DRECTPATCH_INFO 입니다.
사각형과 관련한 작업은 삼각형과 유사하기 때문에 지금부터는 삼각형에 국한에서 글을 진행하겠습니다.


D3DTRIPATCH 구조체의 원형은 다음과 같습니다.

typedef struct D3DTRIPATCH_INFO
{
  UINT          StartVertexOffset;
  UINT          NumVertices;
  D3DBASISTYPE  Basis;
  D3DDEGREETYPE Degree;
} D3DTRIPATCH_INFO, *LPD3DTRIPATCH_INFO;


이 구조체는 버텍스 버퍼처럼 오프셋과 버텍스 갯수를 먼저 설정합니다.
D3DBASISTYPE 은 고차원 패치( high-order patch )의 기본 타입을 설정합니다.
삼각형의 경우에는 D3DBASIS_BEZIER 만 설정할 수 있습니다.

D3DDEGREETYPE 는 고차원 패치의 차수 정도를 설정하게 됩니다.
즉, 곡선을 표현하는 방정식의 차수를 표현하는데,
높은 차수를 선택할 수록 당연히 연산량이 많아질 것입니다.

이들에 대한 종류는 다음과 같습니다.

종류

버텍스 갯수
D3DDEGREE_CUBIC 10 ( 3차 방정식 )
D3DDEGREE_LINEAR 3   ( 1차 방정식 )
D3DDEGREE_QUADRATIC N/A ( 지원되지 않음 ) ( 2차 방정식 )
D3DDEGREE_QUINTIC 21 ( 4차 방정식 )


아래의 그림은 Cubic Bézier 방식의 삼각형 패치를 보여주고 있습니다.


Diagram of a triangular high-order patch with nine vertices


중간 중간에 생성된 정점을 기준으로 테셀레이션 작업이 수행될 것입니다.^^
이런 테셀레이션과 관련한 API들이 DirectX9 에 있었지만, 사실 거의 사용되지는 못했습니다.
왜냐하면, 정말이지 많은 연산을 필요로 하기 때문이겠죠? ^^
즉, DirectX9의 테셀레이션 작업은 소프트웨어적으로 에뮬레이션 되는 테셀레이션입니다.
신고


DirectX11 을 통해서 가장 많은 관심을 가지고 있는 부분 중 하나인 테셀레이션( Tessellation )은
갑자기 등장한 새로운 기능이 아닙니다.


< DirectX9에서의 테셀레이션의 등장 >

DirectX9 이 처음 세상에 등장할 때, 아래와 같은 특징들을 나열했었습니다.

- 2-D support
blt, copy, fill operations, GDI dialogs
- Adaptive tessellation
- Displacement mapping
- Two-sided stencil operations
- Scissor test rect
- Vertex stream offset
- Asynchronous notifications
- VS / PS 2.0
Flow control, float pixels
- Multiple render targets
- Gamma correction


Adaptive tessellation 이 보이시죠?
저도 그냥 무심코 지났던 DirectX9 소개 자료에서 우연히 찾았습니다.^^


< Adaptive tessellation >

테셀레이션에는 몇 가지 방법이 있는데,
그 중에 가장 유명한 것이 Adaptive 형식과 Uniform 형식입니다.
아래의 이미지를 보시기 바랍니다.


< 이미지 출처 : GPU Gems 2권 >


좌측의 경우가 Adaptive 한 방식입니다.
Adaptive 한 방식을 간단히 설명드리면,
시점의 위치에 근거에서 얼마나 많은 면을 생성할 지를 판단해서,
테셀레이션 작업
을 하는 것입니다.

반면에 Uniform 한 방식은,
모두 균일한 면의 갯수로 테셀레이션 작업을 수행하는 방법
입니다.
Uniform 한 방식이 더 연산 수가 많은 것이 일반적이기 때문에,
Adaptive 한 방식이 게임 분야에서 주로 사용됩니다.



< 테셀레이션을 위해 필요한 정보 >

테셀레이션 작업을 위해서는 두 가지가 필요합니다.
그것은 제어점들( Control Points )과 테셀레이션 팩터들( Tessellation Factors ) 입니다.
제어점들은 파이프라인에 입력으로 들어감으로써 패치( Patch ) 형태로 변환되어서
최종적으로 렌더링
되게 됩니다.
이 과정에 대한 자세한 설명은 앞으로도 꾸준히 언급될 것입니다.
지금은 간단하게 이 정도로만 설명하고 넘어가겠습니다.^^



< ID3DXPatchMesh >

그러면 DirectX9 은 어떤 방식으로 테셀레이션 작업을 지원했을까요?
그것은 ID3DXPatchMesh 라는 인터페이스를 통해서 간접적으로 지원했습니다.

참고적으로 얘기드리면, DirectX 에서는 D3DX 라는 유틸리티를 통해서
메시를 관리할 수 있는 클래스를 제공했습니다.
ID3DXBaseMesh, ID3DXMesh, ID3DXSPMesh, ID3DXPMesh,
그리고 마지막으로 언급드렸던 ID3DXPatchMesh 입니다.

ID3DXPatchMesh 인터페이스는 다른 메시들을 지원하는 클래스와 다릅니다. 
일반적인 메시 인터페이스들은 ID3DXBaseMesh와 계층 관계를 이루는 반면에,
ID3DXPatchMesh 는 완전히 별도로 구성된 클래스입니다.
즉, ID3DXPatchMesh 클래스는 IUnknown 인테페이스를 상속받습니다.


ID3DXPatchMesh는 테셀레이션 작업을 위해서 각종 멤버 함수를 가지고 있습니다.
실제로 테셀레이션 작업을 하는 함수는 ID3DXPatchMesh::Tessellate() 와
ID3DXPatchMesh::TessellateAdaptive()
입니다.
이들 함수에 대한 형태는 다음과 같습니다.

HRESULT Tessellate
(
  [in]  FLOAT fTessLevel,
  [in]  LPD3DXMESH pMesh
);

HRESULT TessellateAdaptive
(
  [in]  const D3DXVECTOR4 *pTrans,
  [in]  DWORD dwMaxTessLevel,
  [in]  DWORD dwMinTessLevel,
  [in]  LPD3DXMESH pMesh
);

두 멤버함수 모두 LPD3DXMESH 형태의 테셀레이션 작업이 끝난 메시를 리턴합니다.

이들에 대한 모든 작업은 CPU 가 담당합니다.
또한 연산량도 많기 때문에 Adaptive Tessellation을 처리하기는 상당한 무리가 있습니다.
왜냐하면 Adaptive Tessellation은 시점에 근거해서 매번 폴리곤을 생성해야하기 때문입니다.
ID3DXPatchMesh::Optimize() 라는 최적화 함수를 미리 호출해 줄수도 있지만,
그래도 이는 분명 매우 부담스러운 연산입니다.

< 마치면서... >
이상으로 ID3DXPatchMesh 를 활용한 DirectX9 의 테셀레이션 작업에 대해서 살펴보았습니다.
DirectX9 에서의 테셀레이션 작업의 불편함과 성능 문제를 이해한다면,
DirectX11 에서의 테셀레이션 작업의 우수성을 알 수 있을 것이라 생각됩니다.
다음 시간에도 계속 DirectX9 에서의 테셀레이션 작업에 대해서 살펴보겠습니다.^^
신고

[JumpToDX11-13] Tessellation 등장.

DirectX 11 2010.06.15 09:00 Posted by 조진현


요즘 vs2010 에 아티클이 많아져서 너무 좋습니다..^^


< Tessellation 개념 잡기 >

지금부터 언급할 내용은 Tessellation 입니다.
Tessellation을 간단히 정의하자면, 적은 수의 폴리곤이 그래픽 파이프라인을 통과했을 때,
많은 수의 폴리곤들을 생성해서 최종 화면에서는 훨씬 정밀한 결과를 나타내는 기술
이라고
할 수 있습니다.
간단히 이미지를 보시면 쉽게 개념화 할 수 있을 것입니다.






감동적이신가요?
저는 꽤 큰 감동을 받기는 했는데, 어마어마한 연산량이 걱정이 되었습니다.
물론 여러 분들도 저와 같은 생각일 것이라고 생각합니다.



< Tessellation 의 등장 배경 >

오늘 날의 컴퓨터 그래픽스의 발전은 정말이지 급격하게 변화했습니다.



유명한 파이날 판타지7 과 최신작을 비교해 보았습니다.
그래픽적인 큰 변화가 느껴지시나요?
아마도 느껴지실 것입니다.( 안느껴지시면 곤란합니다..^^ )

저 변화의 중심에 서 있는 기법 혹은 기술은 어떤 것일까요?
다양한 의견이 있을 수 있지만, 개인적인 견해를 전제로 제가 언급하면 텍스쳐링이라고 생각합니다.
오늘 날의 하나의 폴리곤에 적용되는 텍스쳐의 갯수는 하나가 아닙니다.
노말맵이 거의 표준적으로 사용되고 있는 현 세대에서는
각종 라이팅 처리를 위해서 많은 갯수의 텍스쳐가 사용되고 있습니다.
그래서 우리는 현실감 있는 게임을 즐길 수 있습니다.
이러한 발전의 방향은 폴리곤 갯수를 증가시키는 것보다,
텍스쳐링을 활용하는 것이 성능적인 측면에서 더욱 효과적이기 때문입니다.

그러던 과정에서 이제는 GPU의 성능이 급격히 발전하기 시작했습니다.
많은 사람들이 GPU의 활용에 대해서 고민하기 시작했고,
DirectX9 부터 이런 GPU을 활용한 Tessellation 위한 기법들이 공개적으로 소개되기 시작했습니다.
특히나 ATI 쪽에서는 DirectX9 을 위한 Tessllation SDK 를 제공했었습니다.
여담이지만, 엔비디아쪽에서는 자사의 GPGPU 인 CUDA 를 DirectX9 에서 지원했었습니다.
두 회사의 발전 방향이 이때부터 사실 조금씩 차이가 나기 시작했었다고 볼 수 있습니다.



위의 그림은 ATI 사에서 Tessellation의 필요성을 표현하고 있는 그림입니다.
텍스쳐링을 아무리 많이해도, 폴리곤 갯수가 적으면 더 큰 현실감을 느끼는데는 제한이 있다는 정도로 정리할 수 있을 것입니다.
( 그림(c) 에서 몬스터의 부자연스러운 손가락이 보이시죠? )

그래서 조금 더 큰 현실감을 위해서 폴리곤을 증가시키는 방법을 고안하게 되었고,
급기야 이것이 현 DirectX11 의 정식 그래픽 파이프라인 스테이지로 추가되었습니다.
즉, 공부할 것이 훨씬 더 많아졌습니다...T.T


< 왜 Tessellation 인가? >

조금 과장된 표현을 해서, 게임에서 폴리곤을 많이 사용하는 것은 범죄(?) 행위에 해당합니다.
그래픽 카드가 놀라운 속도로 발전을 하고 있지만,
아직도 게임 개발자들은 비디오 메모리의 부족을 호소하고 있습니다.
당연한 얘기지만, 이는 폴리곤 갯수와 퀄리티의 증가에 의한 것입니다.



위의 그림처럼 그래픽 카드는 약간 독특한 성능을 가지고 있습니다.

첫번째로 대역폭입니다.
CPU쪽 대역폭보다 훨씬 크기 때문에, 대량의 데이터를 전송할 수 있습니다.

두번째는 비디오 메모리가 시스템 메모리 보다 훨씬 작다는 것입니다.

세번째는 수치 연산과 병렬연산에 강한 GPU 라는 것입니다.
실제로 Tessellation 파이프라인 스테이지는 병렬적으로 처리됩니다.
( 다음 시간에 이에 대한 언급이 있을 것입니다. )

결과적으로 Tessellation 의 이점은
폴리곤 갯수를 줄임으로써 비디오 메모리 사용량을 감소시킵니다.
이는 결국 적은 데이터 전송으로 인해 대역폭을 절약할 수 있습니다.
하지만, Tessellation 은 GPU 의 성능에 좌우된다고 할 수 있습니다.
연산량이 실제로 많기 때문에, 정말이지 빠른 성능이어야 한다는 것입니다.
다행스러운 것은 GPU 의 성능이 비디오 메모리의 확장보다는 더 빨라지고 있다는 것입니다.

사실 Tessellation 에 대한 가장 큰 의구심은 '과연 빠를까?' 입니다.
이것에 대한 정답은 아직은 없습니다.
적절한 곳에서 사용한다면 유용할 수도 있을 것이고, 그렇지 않을 수도 있을 것입니다.
다만, 현재 DirectX 의 새로운 패러다임으로 Tessellation 이 선택되어졌으며,
좋은 성능을 위해서 꾸준히 노력할 것이라는 것입니다.^^
신고