여러 기하 도형 인스턴스를 효율적으로 그리기(Direct3D 9)

동일한 기하 도형을 사용하는 많은 개체가 포함된 장면에서는 렌더러에 제공해야 하는 데이터의 양을 줄여 훨씬 더 나은 성능으로 다양한 방향, 크기, 색 등에서 해당 기하 도형의 많은 인스턴스를 그릴 수 있습니다.

인덱싱된 기하 도형을 그리는 첫 번째 기술과 인덱싱되지 않은 기하 도형의 경우 두 번째 기술을 사용하여 이 작업을 수행할 수 있습니다. 두 기술 모두 두 개의 꼭짓점 버퍼를 사용합니다. 하나는 기하 도형 데이터를 제공하고 다른 하나는 개체별 인스턴스 데이터를 제공하는 것입니다. 인스턴스 데이터는 변환, 색 데이터 또는 조명 데이터와 같은 다양한 정보일 수 있으며, 기본적으로 꼭짓점 선언에서 설명할 수 있는 모든 정보일 수 있습니다. 이러한 기술을 사용하여 기하 도형의 많은 인스턴스를 그리면 렌더러로 전송되는 데이터의 양을 크게 줄일 수 있습니다.

인덱싱된 기하 도형 그리기

꼭짓점 버퍼에는 꼭짓점 선언으로 정의된 꼭짓점당 데이터가 포함됩니다. 다음 다이어그램과 같이 각 꼭짓점의 부분에 기하 도형 데이터가 포함되고 각 꼭짓점의 일부에 개체별 인스턴스 데이터가 포함되어 있다고 가정합니다.

인덱싱된 기하 도형에 대한 꼭짓점 버퍼 다이어그램

이 기술을 사용하려면 3개의 _ 꼭짓점 셰이더 모델을 지원하는 디바이스가 필요합니다. 이 기술은 프로그래밍 가능한 셰이더에서는 작동하지만 고정 함수 파이프라인에서는 작동하지 않습니다.

위에 표시된 꼭짓점 버퍼의 경우 해당 꼭짓점 버퍼 선언은 다음과 같습니다.

const D3DVERTEXELEMENT9 g_VBDecl_Geometry[] =
{
{0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},
{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  0},
{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0},
{0, 48, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
D3DDECL_END()
};

const D3DVERTEXELEMENT9 g_VBDecl_InstanceData[] =
{
{1, 0,  D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
{1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2},
{1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3},
{1, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4},
{1, 64, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,    0},
D3DDECL_END()
};

이러한 선언은 두 개의 꼭짓점 버퍼를 정의합니다. 첫 번째 선언(스트림 0의 경우 열 1의 0으로 표시)은 위치, 보통, 탄젠트, 이진 및 질감 좌표 데이터로 구성된 기하 도형 데이터를 정의합니다.

두 번째 선언(스트림 1의 경우 열 1의 선언으로 표시)은 개체별 인스턴스 데이터를 정의합니다. 각 인스턴스는 4개 구성 요소 부동 소수점 숫자 4개와 4개 구성 요소 색으로 정의됩니다. 처음 네 개의 값을 사용하여 4x4 행렬을 초기화할 수 있습니다. 즉, 이 데이터는 기하 도형의 각 인스턴스를 고유하게 크기, 위치 및 회전합니다. 처음 4개 구성 요소는 질감 좌표 의미 체계를 사용합니다. 이 경우 "일반 4개 구성 요소 숫자"를 의미합니다. 꼭짓점 선언에서 임의 데이터를 사용하는 경우 질감 좌표 의미 체계를 사용하여 표시합니다. 스트림의 마지막 요소는 색 데이터에 사용됩니다. 꼭짓점 셰이더에 적용하여 각 인스턴스에 고유한 색을 제공할 수 있습니다.

렌더링하기 전에 SetStreamSourceFreq를 호출하여 꼭짓점 버퍼 스트림을 디바이스에 바인딩해야 합니다. 다음은 두 꼭짓점 버퍼를 모두 바인딩하는 예제입니다.

// Set up the geometry data stream
pd3dDevice->SetStreamSourceFreq(0,
    (D3DSTREAMSOURCE_INDEXEDDATA | g_numInstancesToDraw));
pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
    D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

// Set up the instance data stream
pd3dDevice->SetStreamSourceFreq(1,
    (D3DSTREAMSOURCE_INSTANCEDATA | 1));
pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
    D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

SetStreamSourceFreq는 D3DSTREAMSOURCE _ INDEXEDDATA를 사용하여 인덱싱된 기하 도형 데이터를 식별합니다. 이 경우 스트림 0에는 개체 기하 도형을 설명하는 인덱싱된 데이터가 포함됩니다. 이 값은 그릴 기하 도형의 인스턴스 수와 논리적으로 결합됩니다.

D3DSTREAMSOURCE _ INDEXEDDATA 및 그릴 인스턴스 수는 항상 스트림 0으로 설정해야 합니다.

두 번째 호출에서 SetStreamSourceFreq는 D3DSTREAMSOURCE _ INSTANCEDATA를 사용하여 인스턴스 데이터가 포함된 스트림을 식별합니다. 각 꼭짓점은 하나의 인스턴스 데이터 집합을 포함하기 때문에 이 값은 논리적으로 1과 결합됩니다.

SetStreamSource에 대한 마지막 두 호출은 꼭짓점 버퍼 포인터를 디바이스에 바인딩합니다.

인스턴스 데이터 렌더링을 마쳤으면 꼭짓점 스트림 빈도를 기본 상태(인스턴스를 사용하지 않음)로 다시 설정해야 합니다. 이 예제에서는 두 개의 스트림을 사용했으므로 아래와 같이 두 스트림을 모두 설정합니다.

pd3dDevice->SetStreamSourceFreq(0,1);
pd3dDevice->SetStreamSourceFreq(1,1);

인덱싱된 기하 도형 성능 비교

이 기술이 모든 애플리케이션에서 렌더링 시간을 줄일 수 있는 정도에 대해 단일 결론을 내릴 수는 없지만, 인탄싱 기술을 사용하는 경우 런타임으로 스트리밍되는 데이터의 양과 축소되는 상태 변경 횟수의 차이를 고려합니다. 이 렌더링 시퀀스는 동일한 기하 도형의 여러 인스턴스를 그리는 이점을 활용합니다.

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    // Set up the geometry data stream
    pd3dDevice->SetStreamSourceFreq(0,
                (D3DSTREAMSOURCE_INDEXEDDATA | g_numInstancesToDraw));
    pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

    // Set up the instance data stream
    pd3dDevice->SetStreamSourceFreq(1,
                (D3DSTREAMSOURCE_INSTANCEDATA | 1));
    pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
                D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

    pd3dDevice->SetVertexDeclaration( ... );
    pd3dDevice->SetVertexShader( ... );
    pd3dDevice->SetIndices( ... );

    pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    
    pd3dDevice->EndScene();
}

렌더링 루프가 한 번 호출되고 geometry 데이터가 한 번 스트리밍되고 n 인스턴스가 한 번 스트리밍됩니다. 이 다음 렌더링 시퀀스는 기능에서 동일하지만 인탄싱을 활용하지는 않습니다.

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    for(int i=0; i < g_numObjects; i++)
    {
        pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));


        pd3dDevice->SetVertexDeclaration( ... );
        pd3dDevice->SetVertexShader( ... );
        pd3dDevice->SetIndices( ... );

        pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    }                             
    
    pd3dDevice->EndScene();
}

전체 렌더링 루프는 두 번째 루프에 의해 래핑되어 각 개체를 그립니다. 이제 기하 도형 데이터는 렌더러에 한 번이 아니라 n번 스트리밍되며, 그려지는 각 개체에 대해 파이프라인 상태를 중복으로 설정할 수도 있습니다. 이 렌더링 시퀀스는 상당히 느려질 수 있습니다. 또한 DrawIndexedPrimitive에 대한 매개 변수는 두 렌더링 루프 간에 변경되지 않았습니다.

인덱싱되지 않은 기하 도형 그리기

인덱싱된 기하 도형 그리기에서꼭짓점 버퍼는 효율성이 더 큰 인덱싱된 기하 도형의 여러 인스턴스를 그리도록 구성되었습니다. SetStreamSourceFreq를 사용하여 인덱싱되지 않은 기하 도형을 그릴 수도 있습니다. 이렇게 하려면 다른 꼭짓점 버퍼 레이아웃이 필요하며 제약 조건이 다릅니다. 인덱싱되지 않은 기하 도형을 그리려면 다음 다이어그램과 같이 꼭짓점 버퍼를 준비합니다.

인덱싱되지 않은 기하 도형에 대한 꼭짓점 버퍼 다이어그램

이 기술은 디바이스의 하드웨어 가속에서 지원되지 않습니다. 소프트웨어 꼭짓점 처리에서만 지원되며 vs _ 3 _ 0 셰이더에서만 작동합니다.

이 기술은 인덱싱되지 않은 기하 도형에서 작동하므로 인덱스 버퍼가 없습니다. 다이어그램과 같이 geometry를 포함하는 꼭짓점 버퍼에는 기하 도형 데이터의 복사본이 n개 포함되어 있습니다. 그려지는 각 인스턴스에 대해 geometry 데이터는 첫 번째 꼭짓점 버퍼에서 읽혀지고 인스턴스 데이터는 두 번째 꼭짓점 버퍼에서 읽습니다.

해당 꼭짓점 버퍼 선언은 다음과 같습니다.

const D3DVERTEXELEMENT9 g_VBDecl_Geometry[] =
{
{0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},
{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  0},
{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0},
{0, 48, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
D3DDECL_END()
};

const D3DVERTEXELEMENT9 g_VBDecl_InstanceData[] =
{
{1, 0,  D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
{1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2},
{1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3},
{1, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4},
{1, 64, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,    0},
D3DDECL_END()
};

이러한 선언은 인덱싱된 geometry 예제에서 만든 선언과 동일합니다. 다시 한 번 첫 번째 선언(스트림 0의 경우)은 기하 도형 데이터를 정의하고 두 번째 선언(스트림 1의 경우)은 개체별 인스턴스 데이터를 정의합니다. 첫 번째 꼭짓점 버퍼를 만들 때 그리려는 geometry 데이터의 인스턴스 수와 함께 로드해야 합니다.

렌더링하기 전에 첫 번째 꼭짓점 버퍼를 n개의 인스턴스로 나누는 방법을 런타임에 알려주는 나누기를 설정해야 합니다. 그런 다음 다음과 같이 SetStreamSourceFreq를 사용하여 구분자를 설정합니다.

// Set the divider
pd3dDevice->SetStreamSourceFreq(0, 1);
// Bind the stream to the vertex buffer
pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
        D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

// Set up the instance data stream
pd3dDevice->SetStreamSourceFreq(1, verticesPerInstance);
pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
        D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

SetStreamSourceFreq에 대한 첫 번째 호출은 스트림 0에 m 꼭짓점의 n 인스턴스가 포함되어 있음을 말합니다. 그런 다음 SetStreamSource는 스트림 0을 기하 도형 꼭짓점 버퍼에 바인딩합니다.

두 번째 호출에서 SetStreamSourceFreq는 스트림 1을 인스턴스 데이터의 원본으로 식별합니다. 두 번째 매개 변수는 각 개체(m)의 꼭짓점 수입니다. 인스턴스 데이터 스트림은 항상 두 번째 스트림으로 선언되어야 합니다. 그런 다음 SetStreamSource는 스트림 1을 인스턴스 데이터가 포함된 꼭짓점 버퍼에 바인딩합니다.

인스턴스 데이터 렌더링을 마쳤으면 꼭짓점 스트림 빈도를 기본 상태로 다시 설정해야 합니다. 이 예제에서는 두 개의 스트림을 사용했으므로 아래와 같이 두 스트림을 모두 설정합니다.

pd3dDevice->SetStreamSourceFreq(0,1);
pd3dDevice->SetStreamSourceFreq(1,1);

인덱싱되지 않은 기하 도형 성능 비교

이 인덱싱 스타일의 주요 장점은 인덱싱되지 않은 기하 도형에서 사용할 수 있다는 것입니다. 이 기술로 모든 애플리케이션의 렌더링 시간을 줄일 수 있는 정도에 대해 단일 결론을 내릴 수는 없지만 런타임으로 스트리밍되는 데이터의 양과 다음 렌더링 시퀀스에 대해 감소되는 상태 변경 횟수의 차이를 고려합니다.

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    // Set the divider
    pd3dDevice->SetStreamSourceFreq(0, 1);
    pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

    // Set up the instance data stream
    pd3dDevice->SetStreamSourceFreq(1, verticesPerInstance));
    pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
                D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

    pd3dDevice->SetVertexDeclaration( ... );
    pd3dDevice->SetVertexShader( ... );
    pd3dDevice->SetIndices( ... );

    pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    
    pd3dDevice->EndScene();
}

렌더링 루프가 한 번 호출됩니다. 기하 도형 데이터는 스트리밍되는 기하 도형의 n 인스턴스가 있더라도 한 번 스트리밍됩니다. 인스턴스 꼭짓점 버퍼의 데이터는 한 번 스트리밍됩니다. 이 다음 렌더링 시퀀스는 기능에서 동일하지만 인탄싱을 활용하지는 않습니다.

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    for(int i=0; i < g_numObjects; i++)
    {
        pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

        pd3dDevice->SetVertexDeclaration( ... );
        pd3dDevice->SetVertexShader( ... );
        pd3dDevice->SetIndices( ... );

        pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    }
    
    pd3dDevice->EndScene();
}

인탄싱이 없으면 렌더링 루프를 두 번째 루프로 래핑하여 각 개체를 그려야 합니다. 두 번째 렌더링 루프를 제거하면 루프 내에서 호출되는 렌더링 상태 변경 수가 줄어들어 성능이 향상됩니다.

전반적으로 인덱싱된 기술은기하 도형데이터의 복사본 하나만 스트리밍하므로 인덱싱되지 않은 기술(인덱싱되지않은 기하 도형 그리기)보다 인덱싱된 기술( 인덱싱된 기하 도형 그리기)이더 잘 수행될 것으로 예상하는 것이 좋습니다. DrawIndexedPrimitive에 대한 매개 변수는 렌더링 시퀀스에 대해 변경되지 않았습니다.

고급 항목

인탄싱 샘플