Udostępnij przez


Wprowadzenie do etapu Stream-Output

W tej sekcji opisano sposób użycia cieniownika geometrycznego z etapem wyjściowym strumienia.

Kompilowanie shadera geometrii

Ten cieniator geometrii (GS) oblicza wektor normalny dla każdej ściany trójkąta i zwraca dane dotyczące pozycji, normalnych i współrzędnych tekstury.

struct GSPS_INPUT
{
    float4 Pos : SV_POSITION;
    float3 Norm : TEXCOORD0;
    float2 Tex : TEXCOORD1;
};

[maxvertexcount(12)]
void GS( triangle GSPS_INPUT input[3], inout TriangleStream<GSPS_INPUT> TriStream )
{
    GSPS_INPUT output;
    
    //
    // Calculate the face normal
    //
    float3 faceEdgeA = input[1].Pos - input[0].Pos;
    float3 faceEdgeB = input[2].Pos - input[0].Pos;
    float3 faceNormal = normalize( cross(faceEdgeA, faceEdgeB) );
    float3 ExplodeAmt = faceNormal*Explode;
    
    //
    // Calculate the face center
    //
    float3 centerPos = (input[0].Pos.xyz + input[1].Pos.xyz + input[2].Pos.xyz)/3.0;
    float2 centerTex = (input[0].Tex + input[1].Tex + input[2].Tex)/3.0;
    centerPos += faceNormal*Explode;
    
    //
    // Output the pyramid
    //
    for( int i=0; i<3; i++ )
    {
        output.Pos = input[i].Pos + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = input[i].Norm;
        output.Tex = input[i].Tex;
        TriStream.Append( output );
        
        int iNext = (i+1)%3;
        output.Pos = input[iNext].Pos + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = input[iNext].Norm;
        output.Tex = input[iNext].Tex;
        TriStream.Append( output );
        
        output.Pos = float4(centerPos,1) + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = faceNormal;
        output.Tex = centerTex;
        TriStream.Append( output );
        
        TriStream.RestartStrip();
    }
    
    for( int i=2; i>=0; i-- )
    {
        output.Pos = input[i].Pos + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = -input[i].Norm;
        output.Tex = input[i].Tex;
        TriStream.Append( output );
    }
    TriStream.RestartStrip();
}

Mając na uwadze ten kod, należy wziąć pod uwagę, że cieniowanie geometrii wygląda podobnie jak wierzchołek lub cieniowanie pikseli, ale z następującymi wyjątkami: typ zwracany przez funkcję, deklaracje parametrów wejściowych i funkcja wewnętrzna.

Przedmiot Opis
typ zwracany funkcji
Zwracany typ funkcji wykonuje jedną rzecz, deklaruje maksymalną liczbę wierzchołków, które może wyprowadzić cieniator. W tym przypadku
maxvertexcount(12)

definiuje dane wyjściowe jako maksymalnie 12 wierzchołków.

deklaracje parametrów wejściowych

Ta funkcja przyjmuje dwa parametry wejściowe:

triangle GSPS_INPUT input[3] , inout TriangleStream<GSPS_INPUT> TriStream

Pierwszy parametr to tablica wierzchołków (3 w tym przypadku) zdefiniowana przez strukturę GSPS_INPUT (która definiuje dane wierzchołka jako położenie, normalną i współrzędną tekstury). Pierwszy parametr używa również słowa kluczowego 'triangle', co oznacza, że etap asemblera danych wejściowych musi wyprowadzać dane do shadera geometrii jako jeden z typów prymitywów trójkąta (lista trójkątów lub pasek trójkątów).

Drugi parametr to strumień trójkątów zdefiniowany przez typ TriangleStream<GSPS_INPUT>. Oznacza to, że parametr jest tablicą trójkątów, z których każdy składa się z trzech wierzchołków zawierających dane członków GSPS_INPUT.

Użyj słów kluczowych trójkąt i strumień trójkątów, aby zidentyfikować poszczególne trójkąty lub strumień trójkątów w GS.

funkcja wewnętrzna

Wiersze kodu w funkcji cieniowania używają funkcji wewnętrznych common-shader-core HLSL, z wyjątkiem dwóch ostatnich wierszy, które wywołują Append i RestartStrip. Te funkcje są dostępne tylko dla cieniowania geometrii. Operacja Apend informuje shader geometrii o dołączeniu wyników do aktualnego pasma; RestartStrip tworzy nowe pierwotne pasmo. Nowy pasek jest tworzony niejawnie w ramach każdego wezwania etapu GS.

Reszta cieniowania wygląda bardzo podobnie do cieniowania wierzchołka lub cieniowania pikseli. Shader geometrii używa struktury do deklarowania parametrów wejściowych i oznacza członka reprezentującego pozycję za pomocą semantyki SV_POSITION, aby poinformować sprzęt, że są to dane pozycyjne. Struktura danych wejściowych identyfikuje pozostałe dwa parametry wejściowe jako współrzędne tekstury (mimo że jedna z nich będzie zawierać normalne oblicze). Jeśli wolisz, możesz użyć własnej semantyki niestandardowej dla twarzy.

Po zaprojektowaniu cieniowania geometrii wywołaj D3DCompile, aby skompilować, jak pokazano w poniższym przykładzie kodu.

DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
ID3DBlob** ppShader;

D3DCompile( pSrcData, sizeof( pSrcData ), 
  "Tutorial13.fx", NULL, NULL, "GS", "gs_4_0", 
  dwShaderFlags, 0, &ppShader, NULL );

Podobnie jak shaderów wierzchołków i pikseli, potrzebujesz flagi shadera, aby poinformować kompilator, jak chcesz skompilować shader (do debugowania, zoptymalizowany pod kątem szybkości itd.), funkcji punktu wejścia i modelu shadera do walidacji. W tym przykładzie tworzony jest shader geometrii oparty na pliku Tutorial13.fx, przy użyciu funkcji GS. Cieniowanie jest kompilowane dla modelu cieniowania 4.0.

Tworzenie obiektu Geometry-Shader przy użyciu danych wyjściowych strumienia

Gdy już wiesz, że dane będą przesyłane strumieniowo z obszaru geometrii, a shader został pomyślnie skompilowany, następnym krokiem jest wywołanie ID3D11Device::CreateGeometryShaderWithStreamOutput w celu utworzenia obiektu shaderu geometrii.

Najpierw należy zadeklarować sygnaturę wejściową etapu wyjściowego strumienia (SO). Ten podpis jest zgodny lub weryfikuje dane wyjściowe GS i dane wejściowe SO w momencie tworzenia obiektu. Poniższy kod jest przykładem deklaracji SO.

D3D11_SO_DECLARATION_ENTRY pDecl[] =
{
    // semantic name, semantic index, start component, component count, output slot
    { "SV_POSITION", 0, 0, 4, 0 },   // output all components of position
    { "TEXCOORD0", 0, 0, 3, 0 },     // output the first 3 of the normal
    { "TEXCOORD1", 0, 0, 2, 0 },     // output the first 2 texture coordinates
};

D3D11Device->CreateGeometryShaderWithStreamOut( pShaderBytecode, ShaderBytecodesize, pDecl, 
    sizeof(pDecl), NULL, 0, 0, NULL, &pStreamOutGS );

Ta funkcja przyjmuje kilka parametrów, w tym:

  • Wskaźnik do kompilowanego cieniatora geometrii (lub cieniatora wierzchołków, jeśli cieniator geometrii nie będzie obecny i dane będą przesyłane strumieniowo bezpośrednio z cieniatora wierzchołków). Aby uzyskać informacje na temat sposobu uzyskiwania tego wskaźnika, zobacz Uzyskiwanie wskaźnika do skompilowanego cienia.
  • Wskaźnik do tablicy deklaracji opisujących dane wejściowe dla etapu wyjściowego strumienia. (Zobacz D3D11_SO_DECLARATION_ENTRY.) Można podać maksymalnie 64 deklaracje, po jednym dla każdego innego typu elementu jako dane wyjściowe ze etapu SO. Tablica wpisów deklaracji opisuje układ danych niezależnie od tego, czy do wyjścia strumienia jest powiązany tylko jeden bufor, czy wiele buforów.
  • Liczba elementów, które są zapisywane przez etap SO.
  • Wskaźnik do utworzonego obiektu cieniowania geometrii (zobacz ID3D11GeometryShader).

W takiej sytuacji krok buforu ma wartość NULL, indeks strumienia, który ma być wysyłany do rasteryzatora, wynosi 0, a interfejs łączenia klas ma wartość NULL.

Deklaracja danych wyjściowych strumienia definiuje sposób zapisywania danych w zasobie buforu. Możesz dodać dowolną liczbę składników do deklaracji wyjściowej. Użyj etapu SO, aby zapisać w jednym zasobie buforu lub wielu zasobach buforu. W przypadku pojedynczego buforu etap SO może zapisywać wiele różnych elementów na wierzchołek. W przypadku wielu etap SO może zapisywać tylko jeden element danych na wierzchołek do każdego buforu.

Aby użyć etapu SO bez użycia cieniowania geometrii, wywołaj funkcję ID3D11Device::CreateGeometryShaderWithStreamOutput i przekaż wskaźnik do cieniowania wierzchołków do parametru pShaderBytecode.

Ustaw cele wyjściowe

Ostatnim krokiem jest ustawienie buforów etapu SO. Dane mogą być przesyłane strumieniowo do co najmniej jednego buforu w pamięci do późniejszego użycia. Poniższy kod pokazuje, jak utworzyć pojedynczy bufor, który może być używany zarówno do danych wierzchołków, jak i na etapie SO (stream output) do przesyłania strumieniowego danych.

ID3D11Buffer *m_pBuffer;
int m_nBufferSize = 1000000;

D3D11_BUFFER_DESC bufferDesc =
{
    m_nBufferSize,
    D3D11_USAGE_DEFAULT,
    D3D11_BIND_STREAM_OUTPUT,
    0,
    0,
    0
};
D3D11Device->CreateBuffer( &bufferDesc, NULL, &m_pBuffer );

Utwórz bufor, wywołując ID3D11Device::CreateBuffer. W tym przykładzie pokazano domyślne użycie, które jest typowe dla zasobu buforu, który ma być aktualizowany dość często przez procesor CPU. Flaga powiązania identyfikuje etap przetwarzania danych, do którego można przypisać zasób. Każdy zasób używany przez etap SO musi być również utworzony z flagą powiązania D3D10_BIND_STREAM_OUTPUT.

Po pomyślnym utworzeniu buforu ustaw go na bieżące urządzenie, wywołując ID3D11DeviceContext::SOSetTargets:

UINT offset[1] = 0;
D3D11Device->SOSetTargets( 1, &m_pBuffer, offset );

To wywołanie pobiera liczbę buforów, wskaźnik do buforów, oraz tablicę przesunięć (jedno przesunięcie dla każdego buforu, które wskazuje, gdzie rozpocząć zapisywanie danych). Dane zostaną zapisane w tych danych wyjściowych przesyłania strumieniowego po wywołaniu funkcji draw. Zmienna wewnętrzna śledzi położenie, w którym należy rozpocząć zapisywanie danych do buforów wyjściowych przesyłania strumieniowego, a zmienna będzie nadal zwiększana do momentu, aż SOSetTargets zostanie ponownie wywołany, a zostanie określona nowa wartość przesunięcia.

Wszystkie dane zapisane w docelowych buforach będą mieć wartości 32-bitowe.

Stream-Output etapu