Поделиться через


Использование эффекта (Direct3D 9)

На этой странице показано, как создать и использовать эффект. Темы включают в себя следующее:

Создание эффекта

Ниже приведен пример создания эффекта, взятого из примера BasicHLSL Sample. Код создания эффекта для создания отладочного шейдера состоит из 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 );

Эта функция принимает следующие аргументы:

  • Устройство.
  • Имя файла эффекта.
  • Указатель на список #defines, завершаемый значением NULL, который следует использовать при синтаксическом анализе шейдера.
  • Необязательный указатель на обработчик подключения, написанный пользователем. Процессор вызывает обработчик всякий раз, когда ему нужно разрешить #include.
  • Флаг компиляции шейдера, который дает компилятору указания о том, как будет использоваться шейдер. К ним относятся следующие варианты:
    • Пропустить проверку, если скомпилированы известные хорошие шейдеры.
    • Пропуск оптимизации (иногда используется, когда оптимизация затрудняет отладку).
    • Запрос на включение отладочной информации в шейдер, чтобы его можно было отладить.
  • Пул эффектов. Если несколько эффектов используют один и тот же указатель пула памяти, глобальные переменные в эффектах совместно используются друг с другом. Если нет необходимости совместно использовать переменные эффекта, пул памяти можно задать для null.
  • Указатель на новый эффект.
  • Указатель на буфер, в который могут быть отправлены ошибки проверки. В этом примере параметру присвоено значение NULL и не используется.

Заметка

Начиная с пакета SDK за декабрь 2006 г. компилятор DirectX 10 HLSL теперь является компилятором по умолчанию в DirectX 9 и DirectX 10. Для получения подробной информации см. инструмент Effect-Compiler.

 

Отображение эффекта

Последовательность шагов для применения состояния эффекта к устройству:

Код отрисовки эффектов также проще, чем соответствующий код отрисовки без эффекта. Ниже приведен код отрисовки с эффектом:

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

Цикл рендеринга включает в себя запрос эффекта, чтобы узнать, сколько проходов он содержит, а затем вызов всех проходов для техники. Цикл отрисовки можно развернуть для вызова нескольких методов, каждый из которых имеет несколько проходов.

Использование семантики для поиска параметров эффекта

Семантика — это идентификатор, присоединенный к параметру эффекта, позволяющий приложению искать параметр. Параметр может иметь по крайней мере одну семантику. Семантика расположена после двоеточия (:) после имени параметра. Например:

float4x4 matWorldViewProj : WORLDVIEWPROJ;

Если вы объявили глобальную переменную эффекта без использования семантики, это будет выглядеть следующим образом:

float4x4 matWorldViewProj;

Интерфейс эффекта может использовать семантику для получения идентификатора конкретного параметра эффекта. Например, следующее возвращает дескриптор матрицы:

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

Помимо поиска по семантическому имени, интерфейс эффекта имеет множество других методов для поиска параметров.

Используйте дескрипторы для эффективного получения и задания параметров

Хэндлы предоставляют эффективные средства для обращения к параметрам эффекта, техникам, проходам и заметкам эффекта. Дескрипторы (которые имеют тип D3DXHANDLE) являются строковыми указателями. Объекты, передаваемые в такие функции, как GetParameterxxx или GetAnnotationxxx, могут быть представлены в одной из трех форм:

  • Дескриптор, возвращаемый такой функцией, как, например, GetParameterxxx.
  • Строка, содержащая имя параметра, метода, передачи или заметки.
  • Указатель установлен на NULL.

В этом примере возвращается дескриптор параметра с семантикой WORLDVIEWPROJ, подключенной к нему:

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

Добавление сведений о параметрах с заметками

Заметки — это пользовательские данные, которые могут быть присоединены к любому методу, передаче или параметру. Заметка — это гибкий способ добавления сведений в отдельные параметры. Сведения можно считывать обратно и использовать любым способом выбора приложения. Заметка может быть любого типа данных и может быть добавлена динамически. Объявления аннотаций ограничены угловыми скобками. Заметка содержит следующее:

  • Тип данных.
  • Имя переменной.
  • Знак равенства (=).
  • Значение данных.
  • Кончающая точка с запятой (;).

Например, оба предыдущих примера в этом документе содержат эту заметку:

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

Заметка присоединена к объекту текстуры и указывает файл текстуры, который следует использовать для инициализации объекта текстуры. Заметка не инициализирует объект текстуры, это просто часть пользовательской информации, присоединенной к переменной. Приложение может прочитать аннотацию с помощью ID3DXBaseEffect::GetAnnotation или ID3DXBaseEffect::GetAnnotationByName, чтобы получить строку. Заметки также можно добавить приложением.

Каждая заметка:

  • Должен быть числовым или строковым.
  • Всегда должен быть инициализирован со значением по умолчанию.
  • Можно связать с методами и проходами (Direct3D 9) и параметрами эффекта верхнего уровня .
  • Можно записывать и читать из ID3DXEffect или ID3DXEffectCompiler.
  • Добавление возможно с помощью ID3DXEffect.
  • На эффект нельзя ссылаться изнутри.
  • Невозможно иметь подсемантику или поданнотации.

Совместные параметры эффекта

Параметры эффекта — это все нестатические переменные, объявленные в эффекте. Это может включать глобальные переменные и заметки. Параметрами эффектов можно поделиться между разными эффектами, объявляя параметры с ключевым словом "shared" и создавая эффект в пуле эффектов.

Пул эффектов содержит параметры общего эффекта. Пул создается путем вызова D3DXCreateEffectPool, который возвращает интерфейс ID3DXEffectPool. Интерфейс можно предоставить в качестве входных данных для любой из функций D3DXCreateEffectxxxx при создании эффекта. Для совместного использования параметра в нескольких эффектах параметр должен иметь одинаковое имя, тип и семантику в каждом из общих эффектов.

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

    D3DXCreateEffectPool( &g_pEffectPool );

Эффекты, которые делят параметры, должны использовать одно и то же устройство. Это применяется для предотвращения совместного использования зависимых от устройств параметров (таких как шейдеры или текстуры) на разных устройствах. Параметры удаляются из пула всякий раз при освобождении эффектов, содержащих общие параметры. Если параметры общего доступа не нужны, укажите NULL для пула эффектов при создании эффекта.

Клонированные эффекты используют тот же пул эффектов, что и эффект, из которого они клонированы. Клонирование эффекта создает точную копию эффекта, включая глобальные переменные, техники, пассы и аннотации.

Компиляция эффекта в автономном режиме

Вы можете скомпилировать эффект во время выполнения с помощью D3DXCreateEffectили скомпилировать эффект в автономном режиме с помощью средства компилятора командной строки fxc.exe. Эффект в примере CompiledEffect содержит вершинный шейдер, пиксельный шейдер и одну технику:

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

С помощью инструмента Effect-Compiler для компиляции шейдера для vs_1_1 созданы следующие инструкции шейдера для ассемблера:

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

Повышение производительности с помощью прошейдеров

Предварительный шейдер — это метод повышения эффективности шейдера путем предварительного вычисления константных выражений шейдера. Компилятор эффектов автоматически извлекает вычисления шейдера из тела шейдера и выполняет их на ЦП до запуска шейдера. Следовательно, прешейдеры работают только с эффектами. Например, эти два выражения можно оценить за пределами шейдера перед запуском.

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

Вычисления шейдера, которые можно переместить, являются теми, которые связаны с универсальными параметрами; То есть вычисления не изменяются для каждой вершины или пикселя. Если вы используете эффекты, компилятор эффектов автоматически создает и запускает предварительное средство для вас; Флаги для включения отсутствуют. Предварительные шейдеры могут уменьшить количество инструкций на шейдер, а также уменьшить количество регистров констант, потребляемых шейдером.

Думайте о компиляторе эффектов как о компиляторе с несколькими процессорами, так как он компилирует код шейдера для двух типов процессоров: ЦП и GPU. Кроме того, компилятор эффектов предназначен для перемещения кода с GPU на ЦП и, следовательно, повышения производительности шейдера. Это очень похоже на извлечение статического выражения из цикла. Шейдер, который преобразует положение из мирового пространства в пространство проекции и копирует координаты текстуры, выглядел бы так в 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();
    }
}

Использование средства Effect-Compiler для компиляции шейдера для vs_1_1 создает следующие инструкции по сборке:

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

Это использует около 14 слотов и потребляет 9 постоянных регистров. С помощью предшейдера компилятор перемещает статические выражения из шейдера в предшейдер. Тот же шейдер с прошейдером будет выглядеть следующим образом:

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

Эффект выполняет предварительное представление непосредственно перед выполнением шейдера. Результатом является та же функциональность с повышенной производительностью шейдера, так как количество инструкций, которые необходимо выполнить (для каждой вершины или пикселя в зависимости от типа шейдера) было сокращено. Кроме того, шейдер потребляет меньше регистров констант благодаря переносу статических выражений в предшейдер. Это означает, что шейдеры, ранее ограниченные количеством необходимых регистров констант, теперь могут компилироваться, так как для них требуется меньше регистров констант. Разумно ожидать улучшение производительности на 5 и 20 процентов благодаря прешейдерам.

Имейте в виду, что входные константы отличаются от выходных констант в пресейдере. Выходные данные c1 не совпадают с регистром ввода c1. Запись в постоянный регистр в прешейдере фактически пераписывает соответствующий входной слот (константный) шейдера.

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

Инструкция cmp выше считывает значение предварительного шейдера c1, а инструкция mul записывает в регистры аппаратных шейдеров, которые будут использоваться шейдером вершин.

Использование блоков параметров для управления параметрами эффекта

Блоки параметров — это блоки изменений состояния эффекта. Блок параметров может записывать изменения состояния, чтобы их можно было применить позже с помощью одного вызова. Создание блока параметров путем вызова 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();

Блок параметров сохраняет четыре изменения, примененные вызовами API. Вызов ID3DXEffect::BeginParameterBlock начинает запись изменений состояния. ID3DXEffect::EndParameterBlock завершает добавление изменений в блок параметров и возвращает дескриптор. Дескриптор будет использоваться при вызове ID3DXEffect::ApplyParameterBlock.

В примере EffectParamблок параметров применяется в последовательности рендеринга:

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

Блок параметров задает значение всех четырех изменений состояния непосредственно перед вызовом ID3DXEffect::Begin. Блоки параметров — это удобный способ настройки нескольких изменений состояния с помощью одного вызова API.

эффекты