Udostępnij przez


Używanie efektu (Direct3D 9)

Na tej stronie przedstawiono sposób generowania i używania efektu. Poruszane tematy obejmują, jak:

Tworzenie efektu

Oto przykład tworzenia efektu pobranego z BasicHLSL Sample. Kod tworzenia efektu dla debugowania shadera pochodzi z OnCreateDevice:

ID3DXEffect* g_pEffect = NULL;
DWORD dwShaderFlags = 0;

    dwShaderFlags |= D3DXSHADER_FORCE_VS_SOFTWARE_NOOPT;
    dwShaderFlags |= D3DXSHADER_FORCE_PS_SOFTWARE_NOOPT;
    dwShaderFlags |= D3DXSHADER_NO_PRESHADER;

    // Read the D3DX effect file
    WCHAR str[MAX_PATH];
    DXUTFindDXSDKMediaFileCch( str, MAX_PATH, L"BasicHLSL.fx" );

    D3DXCreateEffectFromFile( 
        pd3dDevice, 
        str, 
        NULL, // CONST D3DXMACRO* pDefines,
        NULL, // LPD3DXINCLUDE pInclude,
        dwShaderFlags, 
        NULL, // LPD3DXEFFECTPOOL pPool,
        &g_pEffect, 
        NULL );

Ta funkcja przyjmuje następujące argumenty:

  • Urządzenie.
  • Nazwa pliku efektowego.
  • Wskaźnik do listy #defines zakończonej wartością NULL, który ma być używany w czasie analityki cieniowania.
  • Opcjonalny wskaźnik dla obsługi dołączania napisanej przez użytkownika. Procedura obsługi jest wywoływana przez procesor zawsze, gdy musi rozpoznać #include.
  • Flaga kompilatora cieniowania zawierająca wskazówki kompilatora dotyczące sposobu użycia cieniowania. Dostępne są następujące opcje:
    • Pomijanie walidacji, jeśli kompilowane są wcześniej sprawdzone shadery.
    • Pomijanie optymalizacji (czasami stosowane, gdy optymalizacje utrudniają debugowanie).
    • Żądanie o włączenie informacji debugowania do cieniowania, aby można je było debugować.
  • Pula efektów. Jeśli więcej niż jeden efekt używa tego samego wskaźnika puli pamięci, zmienne globalne w efektach są współdzielone ze sobą. Jeśli nie ma potrzeby współużytkowania zmiennych efektów, można ustawić pulę pamięci na wartość null.
  • Wskaźnik do nowego efektu.
  • Wskaźnik do buforu, do którego można wysłać błędy weryfikacji. W tym przykładzie parametr został ustawiony na wartość null i nie jest używany.

Notatka

Począwszy od zestawu SDK z grudnia 2006 r., kompilator DirectX 10 HLSL jest teraz domyślnym kompilatorem zarówno w trybie DirectX 9, jak i DirectX 10. Aby uzyskać szczegółowe informacje, zobacz Effect-Compiler Tool.

 

Renderowanie efektu

Sekwencja wywołań do zastosowania stanu efektu w urządzeniu to:

Kod renderowania efektu jest również prostszy niż odpowiedni kod renderowania bez efektu. Oto kod renderowania z efektem:

// Apply the technique contained in the effect 
g_pEffect->Begin(&cPasses, 0);

for (iPass = 0; iPass < cPasses; iPass++)
{
    g_pEffect->BeginPass(iPass);

    // Only call CommitChanges if any state changes have happened
    // after BeginPass is called
    g_pEffect->CommitChanges();

    // Render the mesh with the applied technique
    g_pMesh->DrawSubset(0);

    g_pEffect->EndPass();
}
g_pEffect->End();

Pętla renderowania składa się z wykonywania zapytań względem efektu w celu sprawdzenia, ile przebiegów zawiera, a następnie wywołanie wszystkich przebiegów dla techniki. Pętlę renderowania można rozszerzyć w celu wywołania wielu technik, z których każda ma wiele przebiegów.

Znajdowanie parametrów efektu za pomocą semantyki

Semantyka to identyfikator dołączony do parametru efektu, który umożliwia aplikacji wyszukiwanie parametru. Parametr może mieć co najwyżej jedną semantyczną. Semantyka znajduje się po dwukropku (:) po nazwie parametru. Na przykład:

float4x4 matWorldViewProj : WORLDVIEWPROJ;

Jeśli zadeklarowaliśmy efekt zmiennej globalnej bez użycia semantyki, będzie ona wyglądać następująco:

float4x4 matWorldViewProj;

Interfejs efektu pozwala używać semantyki, aby dostać dostęp do określonego parametru efektu. Na przykład, następujące polecenie zwraca uchwyt macierzy:

D3DHANDLE handle = 
    m_pEffect->GetParameterBySemantic(NULL, "WORLDVIEWPROJ");

Oprócz wyszukiwania według nazwy semantycznej interfejs efektu ma wiele innych metod wyszukiwania parametrów.

Uzyskuj i ustawiaj parametry wydajnie za pomocą uchwytów

Uchwyty zapewniają efektywne środki do odwoływania się do parametrów efektu, technik, przejść i adnotacji w kontekście efektu. Dojścia (które są typu D3DXHANDLE) to wskaźniki ciągów. Dojścia przekazywane do funkcji, takich jak GetParameterxxx lub GetAnnotationxxx, mogą znajdować się w jednej z trzech form:

  • Uchwyt zwracany przez funkcję, taką jak GetParameterxxx.
  • Ciąg zawierający nazwę parametru, techniki, etapu lub adnotacji.
  • Uchwyt ustawiony na NULL.

Ten przykład zwraca uchwyt do parametru, do którego dołączony jest semantyk WORLDVIEWPROJ.

D3DHANDLE handle = 
    m_pEffect->GetParameterBySemantic(NULL, "WORLDVIEWPROJ");

Dodawanie informacji o parametrach z adnotacjami

Adnotacje to dane specyficzne dla użytkownika, które mogą być dołączone do dowolnej techniki, operacji lub parametru. Adnotacja to elastyczny sposób dodawania informacji do poszczególnych parametrów. Informacje mogą być odczytywane i używane w dowolny sposób wybierany przez aplikację. Adnotacja może być dowolnego typu danych i może być dodawana dynamicznie. Deklaracje adnotacji są rozdzielane nawiasami kątowymi. Adnotacja zawiera:

  • Typ danych.
  • Nazwa zmiennej.
  • Znak równości (=).
  • Wartość danych.
  • Średnik końcowy (;).

Na przykład oba poprzednie przykłady w tym dokumencie zawierają tę adnotację:

texture Tex0 < string name = "tiger.bmp"; >;

Adnotacja jest dołączona do obiektu tekstury i określa plik tekstury, który należy użyć do zainicjowania obiektu tekstury. Adnotacja nie inicjuje obiektu tekstury. Jest to po prostu element informacji o użytkowniku dołączony do zmiennej. Aplikacja może odczytać adnotację za pomocą ID3DXBaseEffect::GetAnnotation lub ID3DXBaseEffect::GetAnnotationByName, aby zwrócić ciąg. Adnotacje można również dodać przez aplikację.

Każda adnotacja:

  • Musi być liczbami lub ciągami tekstowymi.
  • Należy zawsze inicjować z wartością domyślną.
  • Można skojarzyć z technikami i przebiegami (Direct3D 9) oraz parametrami efektu najwyższego poziomu .
  • Można zapisywać i odczytywać przy użyciu ID3DXEffect lub ID3DXEffectCompiler.
  • Można dodać za pomocą ID3DXEffect.
  • Nie można odwoływać się wewnątrz efektu.
  • Nie można mieć podsemantyki ani podadnotacji.

Parametry efektu udostępniania

Parametry efektu to wszystkie zmienne niestatyczne zadeklarowane w efekcie. Może to obejmować zmienne globalne i adnotacje. Parametry efektu mogą być współużytkowane między różnymi efektami, deklarując parametry za pomocą słowa kluczowego "shared", a następnie tworząc efekt z pulą efektów.

Pula efektów zawiera wspólne parametry efektów. Pula jest tworzona przez wywołanie D3DXCreateEffectPool, która zwraca interfejs ID3DXEffectPool. Interfejs można dostarczyć jako dane wejściowe do dowolnej funkcji D3DXCreateEffectxxx po utworzeniu efektu. Aby parametr był współużytkowany w wielu efektach, parametr musi mieć taką samą nazwę, typ i semantyka w każdym ze wspólnych efektów.

ID3DXEffectPool* g_pEffectPool = NULL;   // Effect pool for sharing parameters

    D3DXCreateEffectPool( &g_pEffectPool );

Efekty współużytkujące parametry muszą używać tego samego urządzenia. Jest to wymuszane, aby zapobiec udostępnianiu parametrów zależnych od urządzenia (takich jak cieniowanie lub tekstury) na różnych urządzeniach. Parametry są usuwane z puli za każdym razem, gdy zostaną zwolnione efekty, które zawierają te udostępnione parametry. Jeśli parametry udostępniania nie są konieczne, podaj NULL dla puli efektów po utworzeniu efektu.

Sklonowane efekty używają tej samej puli efektów, z której zostały sklonowane. Klonowanie efektu tworzy jego dokładną kopię, łącznie ze zmiennymi globalnymi, technikami, etapami i adnotacjami.

Kompilowanie efektu w trybie offline

Efekt można skompilować w czasie wykonywania za pomocą D3DXCreateEffectlub skompilować efekt offline przy użyciu narzędzia kompilatora wiersza polecenia fxc.exe. Efekt w CompiledEffect Sample zawiera cieniowanie wierzchołków, cieniowanie pikseli i jedną technikę:

// File: CompiledEffect.fx

// Global variables
float4 g_MaterialAmbientColor;    // Material's ambient color
...

// Texture samplers
sampler RenderTargetSampler = 
   ...

// Type: Vertex shader                                      
VS_OUTPUT RenderSceneVS( float4 vPos : POSITION, 
                         float3 vNormal : NORMAL,
                         float2 vTexCoord0 : TEXCOORD0 )
{
   ...
};
// Type: Pixel shader
PS_OUTPUT RenderScenePS( VS_OUTPUT In ) 
{ 
   ...
}

// Type: Technique                                     
technique RenderScene
{
    pass P0
    {          
        ZENABLE = true;
        VertexShader = compile vs_1_1 RenderSceneVS();
        PixelShader  = compile ps_1_1 RenderScenePS();
    }
}

Użycie narzędzia Effect-Compiler Tool w celu skompilowania shadera dla vs_1_1 wygenerowało następujące instrukcje asemblera shadera:

//
// Generated by Microsoft (R) D3DX9 Shader Compiler 4.09.02.1188
//
//   fxc /T vs_1_1 /E RenderSceneVS /Fc CompiledEffect.txt CompiledEffect.fx
//
//
// Parameters:
//
//   float4 g_LightAmbient;
//   float4 g_LightDiffuse;
//   float3 g_LightDir;
//   float4 g_MaterialAmbientColor;
//   float4 g_MaterialDiffuseColor;
//   float g_fTime;
//   float4x4 g_mWorld;
//   float4x4 g_mWorldViewProjection;
//
//
// Registers:
//
//   Name                   Reg   Size
//   ---------------------- ----- ----
//   g_mWorldViewProjection c0       4
//   g_mWorld               c4       3
//   g_MaterialAmbientColor c7       1
//   g_MaterialDiffuseColor c8       1
//   g_LightDir             c9       1
//   g_LightAmbient         c10      1
//   g_LightDiffuse         c11      1
//   g_fTime                c12      1
//
//
// Default values:
//
//   g_LightDir
//     c9   = { 0.57735, 0.57735, 0.57735, 0 };
//
//   g_LightAmbient
//     c10  = { 1, 1, 1, 1 };
//
//   g_LightDiffuse
//     c11  = { 1, 1, 1, 1 };
//

    vs_1_1
    def c13, 0.159154937, 0.25, 6.28318548, -3.14159274
    def c14, -2.52398507e-007, 2.47609005e-005, -0.00138883968, 0.0416666418
    def c15, -0.5, 1, 0.5, 0
    dcl_position v0
    dcl_normal v1
    dcl_texcoord v2
    mov r0.w, c12.x
    mad r0.w, r0.w, c13.x, c13.y
    expp r3.y, r0.w
    mov r0.w, r3.y
    mad r0.w, r0.w, c13.z, c13.w
    mul r0.w, r0.w, r0.w
    mad r1.w, r0.w, c14.x, c14.y
    mad r1.w, r0.w, r1.w, c14.z
    mad r1.w, r0.w, r1.w, c14.w
    mad r1.w, r0.w, r1.w, c15.x
    mad r0.w, r0.w, r1.w, c15.y
    mul r0.w, r0.w, v0.x
    mul r0.x, r0.w, c15.z
    dp3 r1.x, v1, c4
    dp3 r1.y, v1, c5
    dp3 r1.z, v1, c6
    mov r0.yzw, c15.w
    dp3 r2.x, r1, r1
    add r0, r0, v0
    rsq r1.w, r2.x
    dp4 oPos.x, r0, c0
    mul r1.xyz, r1, r1.w
    dp4 oPos.y, r0, c1
    dp3 r1.x, r1, c9
    dp4 oPos.z, r0, c2
    max r1.w, r1.x, c15.w
    mov r1.xyz, c8
    mul r1.xyz, r1, c11
    mov r2.xyz, c7
    mul r2.xyz, r2, c10
    dp4 oPos.w, r0, c3
    mad oD0.xyz, r1, r1.w, r2
    mov oD0.w, c15.y
    mov oT0.xy, v2

// approximately 34 instruction slots used

Zwiększanie wydajności za pomocą przeshaderów

Preshader to technika zwiększania wydajności cieniowania przez wstępne obliczanie wyrażeń cieniowania stałych. Kompilator efektu automatycznie wyciąga obliczenia cieniowania z treści cieniowania i wykonuje je na procesorze przed uruchomieniem cieniowania. W związku z tym preshadery działają tylko z efektami. Na przykład te dwa wyrażenia można ocenić poza modułem cieniowania przed jego uruchomieniem.

mul(World,mul(View, Projection));
sin(time)

Obliczenia cieniowania, które można przenieść, są skojarzone z jednorodnymi parametrami; oznacza to, że obliczenia nie zmieniają się dla każdego wierzchołka lub piksela. Jeśli używasz efektów, kompilator efektu automatycznie generuje i uruchamia dla Ciebie preshader; brak flag do włączenia. Moduły wstępne mogą zmniejszyć liczbę instrukcji na moduł cieniowania, a także zmniejszyć liczbę stałych rejestrów zużywanych przez cieniator.

Kompilator efektu można traktować jako rodzaj kompilatora wieloprocesorowego, ponieważ kompiluje kod cieniowania dla dwóch typów procesorów: procesora CPU i procesora GPU. Ponadto kompilator efektu jest przeznaczony do przenoszenia kodu z procesora GPU do procesora CPU i w związku z tym poprawia wydajność cieniowania. Jest to bardzo podobne do ściągania wyrażenia statycznego z pętli. Shader, który przekształca pozycję z przestrzeni świata do przestrzeni projekcji i kopiuje współrzędne tekstur, będzie wyglądał następująco w HLSL:

float4x4 g_mWorldViewProjection;    // World * View * Projection matrix
float4x4 g_mWorldInverse;           // Inverse World matrix
float3 g_LightDir;                  // Light direction in world space
float4 g_LightDiffuse;              // Diffuse color of the light

struct VS_OUTPUT
{
    float4 Position   : POSITION;   // vertex position 
    float2 TextureUV  : TEXCOORD0;  // vertex texture coords 
    float4 Diffuse    : COLOR0;     // vertex diffuse color
};

VS_OUTPUT RenderSceneVS( float4 vPos : POSITION, 
                         float3 vNormal : NORMAL,
                         float2 vTexCoord0 : TEXCOORD0)
{
    VS_OUTPUT Output;
    
    // Transform the position from object space to projection space
    Output.Position = mul(vPos, g_mWorldViewProjection);

    // Transform the light from world space to object space    
    float3 vLightObjectSpace = normalize(mul(g_LightDir, (float3x3)g_mWorldInverse)); 

    // N dot L lighting
    Output.Diffuse = max(0,dot(vNormal, vLightObjectSpace));
    
    // Copy the texture coordinate
    Output.TextureUV = vTexCoord0; 
    
    return Output;    
}
technique RenderVS
{
    pass P0
    {          
        VertexShader = compile vs_1_1 RenderSceneVS();
    }
}

Użycie narzędzia Effect-Compiler Tool do skompilowania shaderu dla vs_1_1 generuje następujące instrukcje w asemblerze:

technique RenderVS
{
    pass P0
    {
        vertexshader = 
            asm {
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float3 g_LightDir;
            //   float4x4 g_mWorldInverse;
            //   float4x4 g_mWorldViewProjection;
            //
            //
            // Registers:
            //
            //   Name                   Reg   Size
            //   ---------------------- ----- ----
            //   g_mWorldViewProjection c0       4
            //   g_mWorldInverse        c4       3
            //   g_LightDir             c7       1
            //
            
                vs_1_1
                def c8, 0, 0, 0, 0
                dcl_position v0
                dcl_normal v1
                dcl_texcoord v2
                mov r1.xyz, c7
                dp3 r0.x, r1, c4
                dp3 r0.y, r1, c5
                dp3 r0.z, r1, c6
                dp4 oPos.x, v0, c0
                dp3 r1.x, r0, r0
                dp4 oPos.y, v0, c1
                rsq r0.w, r1.x
                dp4 oPos.z, v0, c2
                mul r0.xyz, r0, r0.w
                dp4 oPos.w, v0, c3
                dp3 r0.x, v1, r0
                max oD0, r0.x, c8.x
                mov oT0.xy, v2
            
            // approximately 14 instruction slots used
            };

        //No embedded pixel shader
    }
}

Wykorzystuje to około 14 miejsc i zużywa 9 stałych rejestrów. W przypadku preshadera, kompilator przenosi wyrażenia statyczne ze shadera do preshadera. Ten sam moduł cieniowania wygląda następująco z użyciem preshadera:

technique RenderVS
{
    pass P0
    {
        vertexshader = 
            asm {
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float3 g_LightDir;
            //   float4x4 g_mWorldInverse;
            //
            //
            // Registers:
            //
            //   Name            Reg   Size
            //   --------------- ----- ----
            //   g_mWorldInverse c0       3
            //   g_LightDir      c3       1
            //
            
                preshader
                dot r0.x, c3.xyz, c0.xyz
                dot r0.y, c3.xyz, c1.xyz
                dot r0.z, c3.xyz, c2.xyz
                dot r1.w, r0.xyz, r0.xyz
                rsq r0.w, r1.w
                mul c4.xyz, r0.w, r0.xyz
            
            // approximately 6 instructions used
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float4x4 g_mWorldViewProjection;
            //
            //
            // Registers:
            //
            //   Name                   Reg   Size
            //   ---------------------- ----- ----
            //   g_mWorldViewProjection c0       4
            //
            
                vs_1_1
                def c5, 0, 0, 0, 0
                dcl_position v0
                dcl_normal v1
                dcl_texcoord v2
                dp4 oPos.x, v0, c0
                dp4 oPos.y, v0, c1
                dp4 oPos.z, v0, c2
                dp4 oPos.w, v0, c3
                dp3 r0.x, v1, c4
                max oD0, r0.x, c5.x
                mov oT0.xy, v2
            
            // approximately 7 instruction slots used
            };

        //No embedded pixel shader
    }
}

Efekt wykonuje preshader tuż przed wykonaniem cieniowania. Wynikiem jest ta sama funkcja ze zwiększoną wydajnością cieniowania, ponieważ liczba instrukcji, które należy wykonać (dla każdego wierzchołka lub piksela w zależności od typu cieniowania) została zmniejszona. Ponadto mniejsza liczba rejestrów stałych jest zużywana przez shader w wyniku przeniesienia wyrażeń statycznych do preshadera. Oznacza to, że cieniowania wcześniej ograniczone przez liczbę wymaganych stałych rejestrów mogą być teraz kompilowane, ponieważ wymagają one mniejszej liczby rejestrów stałych. Rozsądnie jest oczekiwać 5-procentowej i 20-procentowej poprawy wydajności od wstępnych shaderów.

Należy pamiętać, że stałe wejściowe różnią się od stałych wyjściowych w przedszywce. Dane wyjściowe c1 nie są takie same jak dane wejściowe c1 rejestru. Zapisywanie do stałego rejestru w przedszaderze faktycznie zapisuje do odpowiedniego wejściowego slota cieniowania (stałego).

// BaseDelta c0 1
// Refinements c1 1
preshader
mul c1.x, c0.x, (-2)
add c0.x, c0.x, c0.x
cmp c5.x, c1.x, (1), (0)

Instrukcja cmp powyżej odczytuje wartość preshader c1, podczas gdy instrukcja mul zapisze do rejestrów sprzętowych shaderów, które będą używane przez shader wierzchołków.

Używanie bloków parametrów do zarządzania parametrami efektu

Bloki parametrów to bloki zmian stanu efektu. Blok parametrów może rejestrować zmiany stanu, aby można je było stosować później za pomocą pojedynczego wywołania. Utwórz blok parametrów, wywołując ID3DXEffect::BeginParameterBlock:

    m_pEffect->SetTechnique( "RenderScene" );

    m_pEffect->BeginParameterBlock();
    D3DXVECTOR4 v4( Diffuse.r, Diffuse.g, Diffuse.b, Diffuse.a );
    m_pEffect->SetVector( "g_vDiffuse", &v4 );
    m_pEffect->SetFloat( "g_fReflectivity", fReflectivity );
    m_pEffect->SetFloat( "g_fAnimSpeed", fAnimSpeed );
    m_pEffect->SetFloat( "g_fSizeMul", fSize );
    m_hParameters = m_pEffect->EndParameterBlock();

Blok parametru zapisuje cztery zmiany zastosowane przez wywołania interfejsu API. Wywoływanie ID3DXEffect::BeginParameterBlock rozpoczyna rejestrowanie zmian stanu. ID3DXEffect::EndParameterBlock zatrzymuje dodawanie zmian do bloku parametrów i zwraca uchwyt. Uchwyt będzie używany podczas wywoływania ID3DXEffect::ApplyParameterBlock.

W próbce EffectParamblok parametrów jest stosowany w sekwencji renderowania:

CObj g_aObj[NUM_OBJS];       // Object instances

    if( SUCCEEDED( pd3dDevice->BeginScene() ) )
    {
        // Set the shared parameters using the first mesh's effect.

        // Render the mesh objects
        for( int i = 0; i < NUM_OBJS; ++i )
        {
            ID3DXEffect *pEffect = g_aObj[i].m_pEffect;

            // Apply the parameters
            pEffect->ApplyParameterBlock( g_aObj[i].m_hParameters );

            ...

            pEffect->Begin( &cPasses, 0 );
            for( iPass = 0; iPass < cPasses; iPass++ )
            {
              ...
            }
            pEffect->End();
        }

        ...
        pd3dDevice->EndScene();
    }

Blok parametrów ustawia wartość wszystkich czterech zmian stanu tuż przed wywołaniem ID3DXEffect::Begin. Bloki parametrów to przydatny sposób ustawiania wielu zmian stanu za pomocą jednego wywołania interfejsu API.

efekty