Delen via


Een effect gebruiken (Direct3D 9)

Op deze pagina ziet u hoe u een effect genereert en gebruikt. De behandelde onderwerpen omvatten het volgende:

Een effect maken

Hier volgt een voorbeeld van het maken van een effect dat afkomstig is van de BasicHLSL Sample. De code voor het creëren van een debugshader is afkomstig van 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 );

Deze functie gebruikt deze argumenten:

  • Het apparaat.
  • De bestandsnaam van het effectbestand.
  • Een aanwijzer naar een door NULL beëindigde lijst met #defines, die moet worden gebruikt tijdens het parseren van de shader.
  • Een optionele aanwijzer naar een door de gebruiker geschreven include-handler. De handler wordt door de processor aangeroepen wanneer deze een #include moet oplossen.
  • Een shader-compileervlag die de compiler hints geeft over hoe de shader zal worden gebruikt. De opties zijn onder andere:
    • Als bekende goede shaders worden gecompileerd, wordt validatie overgeslagen.
    • Optimalisatie overslaan (soms gebruikt wanneer optimalisaties foutopsporing moeilijker maken).
    • Het aanvragen van foutopsporingsgegevens die moeten worden opgenomen in de shader, zodat deze foutopsporing kan worden uitgevoerd.
  • De effectpool. Als meer dan één effect dezelfde aanwijzer voor de geheugengroep gebruikt, worden de globale variabelen in de effecten met elkaar gedeeld. Als er geen effectvariabelen hoeven te worden gedeeld, kan de geheugengroep worden ingesteld op NULL-.
  • Een aanwijzer naar het nieuwe effect.
  • Een aanwijzer naar een buffer waarnaar validatiefouten kunnen worden verzonden. In dit voorbeeld is de parameter ingesteld op NULL- en niet gebruikt.

Notitie

Vanaf de SDK van december 2006 is de DirectX 10 HLSL-compiler nu de standaardcompilatie in zowel DirectX 9 als DirectX 10. Zie Effect-Compiler Tool voor meer informatie.

 

Een effect weergeven

De volgorde van aanroepen voor het toepassen van de effectstatus op een apparaat is:

Effectweergavecode is ook eenvoudiger dan de bijbehorende rendercode zonder effect. Dit is de rendercode met een effect:

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

De renderlus bestaat uit het opvragen van het effect om te zien hoeveel passages het bevat, en vervolgens het aanroepen van alle passages voor een techniek. De renderlus kan worden uitgebreid om meerdere technieken aan te roepen, elk met meerdere passen.

Semantiek gebruiken om effectparameters te zoeken

Een semantische id is een id die is gekoppeld aan een effectparameter, zodat een toepassing naar de parameter kan zoeken. Een parameter kan maximaal één semantisch hebben. De semantische waarde bevindt zich na de parameternaam, gevolgd door een dubbele punt (:). Bijvoorbeeld:

float4x4 matWorldViewProj : WORLDVIEWPROJ;

Als u de globale effectvariabele hebt gedeclareerd zonder een semantische variabele te gebruiken, ziet deze er als volgt uit:

float4x4 matWorldViewProj;

De effectinterface kan een semantiek gebruiken om een referentie te verkrijgen naar een bepaalde effectparameter. Het volgende retourneert bijvoorbeeld de handle van de matrix:

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

Naast het zoeken op semantische naam heeft de effectinterface veel andere methoden om naar parameters te zoeken.

Handles gebruiken om parameters efficiënt op te halen en in te stellen

Handles bieden een efficiënte manier om te verwijzen naar effectparameters, technieken, slagen en aantekeningen met een effect. Handles (van het type D3DXHANDLE) zijn stringpointers. De ingangen die worden doorgegeven aan functies zoals GetParameterxxx of GetAnnotationxxx, kunnen zich in een van de volgende drie vormen bevinden:

  • Een hdl die door een functie zoals GetParameterxxx wordt geretourneerd.
  • Een tekenreeks die de naam bevat van de parameter, techniek, stap of annotatie.
  • Een hendel ingesteld op NULL -.

In dit voorbeeld wordt een ingang geretourneerd naar de parameter waaraan de WORLDVIEWPROJ-semantische koppeling is gekoppeld:

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

Parametergegevens toevoegen met aantekeningen

Aantekeningen zijn gebruikersspecifieke gegevens die kunnen worden gekoppeld aan elke techniek, pass of parameter. Een aantekening is een flexibele manier om informatie toe te voegen aan afzonderlijke parameters. De informatie kan worden gelezen en gebruikt op elke manier die de toepassing kiest. Een aantekening kan van elk gegevenstype zijn en kan dynamisch worden toegevoegd. Annotatiedeclaraties worden afgebakend door punthaken. Een aantekening bevat:

  • Een gegevenstype.
  • Een variabelenaam.
  • Een gelijkteken (=).
  • De gegevenswaarde.
  • Een eindkomma (;).

Beide van de vorige voorbeelden in dit document bevatten bijvoorbeeld deze aantekening:

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

De aantekening wordt gekoppeld aan het bitmapobject en geeft het bitmapbestand op dat moet worden gebruikt om het bitmapobject te initialiseren. De aantekening initialiseert het bitmapobject niet, het is gewoon een stukje gebruikersgegevens die aan de variabele zijn gekoppeld. Een toepassing kan de aantekening lezen met ID3DXBaseEffect::GetAnnotation of ID3DXBaseEffect::GetAnnotationByName om de string te retourneren. Aantekeningen kunnen ook worden toegevoegd door de toepassing.

Elke aantekening:

Effectparameters delen

Effectparameters zijn alle niet-statische variabelen die in een effect zijn gedeclareerd. Dit kan globale variabelen en aantekeningen bevatten. Effectparameters kunnen worden gedeeld tussen verschillende effecten door parameters te declareren met het trefwoord 'gedeeld' en vervolgens het effect te maken met een effectgroep.

Een effectgroep bevat parameters voor gedeeld effect. De pool wordt gemaakt door D3DXCreateEffectPool-aan te roepen. Hiermee wordt een ID3DXEffectPool-interface geretourneerd. De interface kan worden geleverd als invoer voor een van de D3DXCreateEffectxxx-functies wanneer een effect wordt gemaakt. Als u een parameter wilt delen tussen meerdere effecten, moet de parameter dezelfde naam, hetzelfde type en semantisch hebben in elk van de gedeelde effecten.

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

    D3DXCreateEffectPool( &g_pEffectPool );

Effecten die parameters delen, moeten hetzelfde apparaat gebruiken. Dit wordt afgedwongen om het delen van apparaatafhankelijke parameters (zoals shaders of patronen) op verschillende apparaten te voorkomen. Parameters worden uit de pool verwijderd wanneer de effecten die de gedeelde parameters bevatten, worden vrijgegeven. Als het delen van parameters niet nodig is, geeft u NULL- op voor de effectgroep wanneer er een effect wordt gemaakt.

Gekloonde effecten gebruiken dezelfde effectgroep als het effect waaruit ze worden gekloond. Het klonen van een effect maakt een exacte kopie van een effect, waaronder globale variabelen, technieken, passeringen en aantekeningen.

Een effect offline compileren

U kunt een effect tijdens runtime compileren met D3DXCreateEffectof u kunt een effect offline compileren met behulp van het opdrachtregelprogramma voor compileren fxc.exe. Het effect in de CompiledEffect Sample bevat een hoekpunt-shader, een pixel-shader en één techniek:

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

Gebruik Effect-Compiler Tool om de shader te compileren voor vs_1_1, waarmee de volgende assembly-shader-instructies werden gegenereerd:

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

Prestaties verbeteren met Preshaders

Een preshader is een techniek voor het verhogen van de efficiëntie van shader door constant shader-expressies vooraf te berekenen. De effectcompilator haalt automatisch shaderberekeningen uit de kern van een shader en voert deze uit op de CPU voordat de shader wordt uitgevoerd. Daarom werken preshaders alleen met effecten. Bijvoorbeeld kunnen deze twee expressies buiten de shader worden geëvalueerd voordat deze wordt uitgevoerd.

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

Shader-berekeningen die kunnen worden verplaatst, zijn berekeningen die zijn gekoppeld aan uniforme parameters; Dat wil gezegd, de berekeningen worden niet gewijzigd voor elk hoekpunt of pixel. Als u effecten gebruikt, genereert en voert de compiler automatisch een preshader voor u uit; er zijn geen vlaggen om in te schakelen. Preshaders kunnen het aantal instructies per shader verminderen en kan ook het aantal constante registers verminderen dat een shader verbruikt.

U kunt de effectcompilator beschouwen als een soort compiler met meerdere processoren, omdat hiermee shader-code wordt gecompileerd voor twee typen processors: een CPU en een GPU. Bovendien is de effectcompilator ontworpen om code van de GPU naar de CPU te verplaatsen en daarom de shaderprestaties te verbeteren. Dit is vergelijkbaar met het ophalen van een statische expressie uit een lus. Een shader die de positie van world space naar projectieruimte transformeert en texturecoördinaten kopieert, ziet er als volgt uit 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();
    }
}

Met behulp van Effect-Compiler Tool om de shader voor vs_1_1 te compileren, worden de volgende assembly-instructies gegenereerd:

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

Dit neemt ongeveer 14 sleuven in beslag en verbruikt 9 constante registers. Met een preshader verplaatst de compiler de statische expressies uit de shader naar de preshader. Dezelfde shader ziet er als volgt uit met een 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
    }
}

Een effect voert een preshader uit vlak voordat een shader wordt uitgevoerd. Het resultaat is dezelfde functionaliteit met verbeterde shaderprestaties omdat het aantal instructies dat moet worden uitgevoerd (voor elk hoekpunt of pixel afhankelijk van het type shader) is verminderd. Bovendien worden er minder constante registers door de shader gebruikt als gevolg van de statische expressies die naar de preshader worden verplaatst. Dit betekent dat shaders die eerder zijn beperkt door het aantal constante registers dat ze nodig hebben, nu kunnen worden gecompileerd omdat ze minder constante registers nodig hebben. Het is redelijk om een prestatieverbetering van 5 procent en een prestatieverbetering van 20 procent te verwachten van preshaders.

Houd er rekening mee dat de invoerconstanten verschillen van de uitvoerconstanten in een preshader. De uitvoer c1 is niet hetzelfde als het invoer-c1-register. Schrijven naar een constant register in een preshader schrijft daadwerkelijk naar de corresponderende shader-invoer (constante) slot.

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

De hierboven beschreven cmp-instructie leest de preshader-c1-waarde uit, terwijl de mul-instructie naar de hardware shader registers schrijft die door de vertexshader moeten worden gebruikt.

Parameterblokken gebruiken om effectparameters te beheren

Parameterblokken zijn blokken van effectstatuswijzigingen. Een parameterblok kan statuswijzigingen vastleggen, zodat deze later met één aanroep kunnen worden toegepast. Maak een parameterblok door ID3DXEffect::BeginParameterBlockaan te roepen:

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

In het parameterblok worden vier wijzigingen opgeslagen die worden toegepast door de API-aanroepen. Het aanroepen van ID3DXEffect::BeginParameterBlock begint met het opnemen van de statuswijzigingen. ID3DXEffect::EndParameterBlock stopt met het toevoegen van de wijzigingen aan het parameterblok en retourneert een handle. De handle wordt gebruikt bij het aanroepen van ID3DXEffect::ApplyParameterBlock.

In de EffectParam Samplewordt het parameterblok toegepast in de weergavevolgorde:

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

Met het parameterblok wordt de waarde van alle vier de statuswijzigingen ingesteld vlak voordat ID3DXEffect::Begin wordt aangeroepen. Parameterblokken zijn een handige manier om meerdere statuswijzigingen in te stellen met één API-aanroep.

effecten