Uso di un effetto (Direct3D 9)

Questa pagina illustra come generare e usare un effetto. Gli argomenti illustrati includono le procedure seguenti:

Creare un effetto

Ecco un esempio di creazione di un effetto tratto dall'esempio BasicHLSL. Il codice di creazione dell'effetto per la creazione di uno shader di debug è da 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 );

Questa funzione accetta questi argomenti:

  • Dispositivo.
  • Nome file del file di effetto.
  • Puntatore a un elenco con terminazione NULL di #defines da usare durante l'analisi dello shader.
  • Puntatore facoltativo a un gestore di inclusione scritto dall'utente. Il gestore viene chiamato dal processore ogni volta che deve risolvere un #include.
  • Flag di compilazione shader che fornisce agli hint del compilatore il modo in cui verrà usato lo shader. Queste opzioni includono:
    • Ignorare la convalida, se vengono compilati gli shader noti.
    • Ottimizzazione ignorata (talvolta usata quando le ottimizzazioni rendono più difficile il debug).
    • Richiesta di informazioni di debug da includere nello shader in modo che sia possibile eseguire il debug.
  • Pool di effetti. Se più di un effetto usa lo stesso puntatore del pool di memoria, le variabili globali negli effetti vengono condivise tra loro. Se non è necessario condividere variabili di effetto, il pool di memoria può essere impostato su NULL.
  • Puntatore al nuovo effetto.
  • Puntatore a un buffer a cui è possibile inviare errori di convalida. In questo esempio il parametro è stato impostato su NULL e non usato.

Nota

A partire da dicembre 2006 SDK, il compilatore DirectX 10 HLSL è ora il compilatore predefinito in DirectX 9 e DirectX 10. Per informazioni dettagliate, vedere Strumento del compilatore effetto .

 

Eseguire il rendering di un effetto

La sequenza di chiamate per applicare lo stato dell'effetto a un dispositivo è:

Il codice di rendering degli effetti è anche più semplice del codice di rendering corrispondente senza un effetto. Ecco il codice di rendering con un effetto:

// 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();

Il ciclo di rendering consiste nell'eseguire query sull'effetto per verificare il numero di passaggi che contiene e quindi chiamare tutti i passaggi per una tecnica. Il ciclo di rendering può essere espanso per chiamare più tecniche, ognuna con più passaggi.

Usare semantica per trovare i parametri dell'effetto

Una semantica è un identificatore collegato a un parametro di effetto per consentire a un'applicazione di cercare il parametro. Un parametro può avere al massimo una semantica. La semantica si trova seguendo un punto (:) dopo il nome del parametro. Ad esempio:

float4x4 matWorldViewProj : WORLDVIEWPROJ;

Se la variabile globale dell'effetto è stata dichiarata senza usare una semantica, sarà simile alla seguente:

float4x4 matWorldViewProj;

L'interfaccia dell'effetto può usare una semantica per ottenere un handle per un determinato parametro di effetto. Ad esempio, il seguente restituisce l'handle della matrice:

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

Oltre alla ricerca in base al nome semantico, l'interfaccia dell'effetto ha molti altri metodi per cercare parametri.

Usare handle per ottenere e impostare i parametri in modo efficiente

Gli handle forniscono un mezzo efficiente per fare riferimento ai parametri degli effetti, alle tecniche, ai passaggi e alle annotazioni con un effetto. Handle (che sono di tipo D3DXHANDLE) sono puntatori di stringa. Gli handle passati a funzioni quali GetParameterxxx o GetAnnotationxxx possono essere in uno dei tre moduli:

  • Handle restituito da una funzione, ad esempio GetParameterxxx.
  • Stringa contenente il nome del parametro, della tecnica, del passaggio o dell'annotazione.
  • Handle impostato su NULL.

In questo esempio viene restituito un handle al parametro con la semantica WORLDVIEWPROJ collegata:

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

Aggiungere informazioni sui parametri con annotazioni

Le annotazioni sono dati specifici dell'utente che possono essere collegati a qualsiasi tecnica, passaggio o parametro. Un'annotazione è un modo flessibile per aggiungere informazioni ai singoli parametri. Le informazioni possono essere letti di nuovo e usate in qualsiasi modo scelto dall'applicazione. Un'annotazione può essere di qualsiasi tipo di dati e può essere aggiunta dinamicamente. Le dichiarazioni di annotazione sono delimitate da parentesi angolari. Un'annotazione contiene:

  • Tipo di dati.
  • Nome variabile.
  • Segno di uguale (=).
  • Valore dei dati.
  • Punto e virgola finale (;).

Ad esempio, entrambi gli esempi precedenti di questo documento contengono questa annotazione:

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

L'annotazione è associata all'oggetto trama e specifica il file di trama che deve essere usato per inizializzare l'oggetto trama. L'annotazione non inizializza l'oggetto trama, è semplicemente una parte delle informazioni utente associate alla variabile. Un'applicazione può leggere l'annotazione con ID3DXBaseEffect::GetAnnotation o ID3DXBaseEffect::GetAnnotationByName per restituire la stringa. È anche possibile aggiungere annotazioni dall'applicazione.

Ogni annotazione:

Parametri dell'effetto di condivisione

I parametri degli effetti sono tutte le variabili non statiche dichiarate in un effetto. Ciò può includere variabili globali e annotazioni. I parametri degli effetti possono essere condivisi tra diversi effetti dichiarando parametri con la parola chiave "condivisa" e quindi creando l'effetto con un pool di effetti.

Un pool di effetti contiene parametri di effetto condivisi. Il pool viene creato chiamando D3DXCreateEffectPool, che restituisce un'interfaccia ID3DXEffectPool . L'interfaccia può essere fornita come input a una qualsiasi delle funzioni D3DXCreateEffectxxx quando viene creato un effetto. Per condividere un parametro in più effetti, il parametro deve avere lo stesso nome, il tipo e la semantica in ognuno degli effetti condivisi.

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

    D3DXCreateEffectPool( &g_pEffectPool );

Gli effetti che condividono i parametri devono usare lo stesso dispositivo. Questo viene applicato per impedire la condivisione di parametri dipendenti dal dispositivo (ad esempio shader o trame) tra dispositivi diversi. I parametri vengono eliminati dal pool ogni volta che vengono rilasciati gli effetti che contengono i parametri condivisi. Se i parametri di condivisione non sono necessari, specificare NULL per il pool di effetti quando viene creato un effetto.

Gli effetti clonati usano lo stesso pool di effetti dell'effetto da cui vengono clonati. La clonazione di un effetto crea una copia esatta di un effetto, incluse variabili globali, tecniche, passaggi e annotazioni.

Compilare un effetto offline

È possibile compilare un effetto in fase di esecuzione con D3DXCreateEffect oppure compilare un effetto offline usando lo strumento del compilatore della riga di comando fxc.exe. L'effetto nell'esempio CompiledEffect contiene un vertex shader, un pixel shader e una tecnica:

// 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();
    }
}

Uso dello strumento del compilatore di effetti per compilare lo shader per vs_1_1 generato le istruzioni dello shader di assembly seguenti:

//
// 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

Migliorare le prestazioni con i preshader

Un preshader è una tecnica per aumentare l'efficienza dello shader pre-calcolando le espressioni di shader costanti. Il compilatore dell'effetto estrae automaticamente i calcoli dello shader dal corpo di uno shader e li esegue nella CPU prima dell'esecuzione dello shader. Di conseguenza, i preshader funzionano solo con gli effetti. Ad esempio, queste due espressioni possono essere valutate all'esterno dello shader prima dell'esecuzione.

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

I calcoli shader che possono essere spostati sono quelli associati ai parametri uniformi; ovvero i calcoli non cambiano per ogni vertice o pixel. Se si usano effetti, il compilatore di effetti genera ed esegue automaticamente un preshader; non sono presenti flag da abilitare. I preshader possono ridurre il numero di istruzioni per shader e possono anche ridurre il numero di registri costanti utilizzati da uno shader.

Si pensi al compilatore di effetti come un tipo di compilatore multiprocessore perché compila il codice shader per due tipi di processori: una CPU e una GPU. Inoltre, il compilatore dell'effetto è progettato per spostare il codice dalla GPU alla CPU e quindi migliorare le prestazioni dello shader. Questo è molto simile al pull di un'espressione statica all'esterno di un ciclo. Uno shader che trasforma la posizione dallo spazio globale allo spazio di proiezione e copia le coordinate della trama sarà simile al seguente in 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();
    }
}

L'uso dello strumento Effect-Compiler per compilare lo shader per vs_1_1 genera le istruzioni di assembly seguenti:

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
    }
}

In questo modo vengono utilizzati circa 14 slot e vengono utilizzati 9 registri costanti. Con un preshader, il compilatore sposta le espressioni statiche all'esterno dello shader e nel preshader. Lo stesso shader sarà simile al seguente con un preshader:

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
    }
}

Un effetto esegue un preshader subito prima di eseguire uno shader. Il risultato è la stessa funzionalità con prestazioni di shader aumentate perché il numero di istruzioni che devono essere eseguite (per ogni vertice o pixel a seconda del tipo di shader) è stato ridotto. Inoltre, un minor numero di registri costanti viene utilizzato dallo shader in seguito allo spostamento delle espressioni statiche nel preshader. Ciò significa che gli shader precedentemente limitati dal numero di registri costanti necessari possono ora essere compilati perché richiedono meno registri costanti. È ragionevole aspettarsi un 5% e un miglioramento delle prestazioni del 20% rispetto ai preshader.

Tenere presente che le costanti di input sono diverse dalle costanti di output in un preshader. L'output c1 non corrisponde al registro di input c1. La scrittura in un registro costante in un preshader scrive effettivamente nello slot di input (costante) dello shader corrispondente.

// 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)

L'istruzione cmp precedente leggerà il valore c1 del preshader, mentre l'istruzione mul scriverà nei registri hardware shader da usare dal vertex shader.

Usare i blocchi di parametri per gestire i parametri di effetto

I blocchi di parametri sono blocchi di modifiche dello stato dell'effetto. Un blocco di parametri può registrare le modifiche dello stato, in modo che possano essere applicate in un secondo momento con una singola chiamata. Creare un blocco di parametri chiamando 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();

Il blocco di parametri salva quattro modifiche applicate dalle chiamate API. La chiamata a ID3DXEffect::BeginParameterBlock avvia la registrazione delle modifiche dello stato. ID3DXEffect::EndParameterBlock interrompe l'aggiunta delle modifiche al blocco di parametri e restituisce un handle. L'handle verrà usato quando si chiama ID3DXEffect::ApplyParameterBlock.

Nell'esempio EffectParam il blocco di parametri viene applicato nella sequenza di rendering:

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();
    }

Il blocco di parametri imposta il valore di tutte e quattro le modifiche dello stato appena prima che venga chiamato ID3DXEffect::Begin . I blocchi di parametri sono un modo pratico per impostare più modifiche di stato con una singola chiamata API.

Effetti