Использование эффекта (Direct3D 9)
На этой странице показано, как создать и использовать эффект. Рассматриваются следующие темы:
- Создание эффекта
- Отрисовка эффекта
- Использование семантики для поиска параметров эффекта
- Эффективное получение и настройка параметров с помощью дескрипторов
- Добавление сведений о параметрах с помощью заметок
- Параметры эффекта "Общий доступ"
- Компиляция эффекта в автономном режиме
- Повышение производительности с помощью preshaders
- Использование блоков параметров для управления параметрами эффекта
Создание эффекта
Ниже приведен пример создания эффекта, взятого из примера BasicHLSL. Код создания эффекта для создания отладочного шейдера приведен из 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. Дополнительные сведения см. в статье Средство компилятора эффектов .
Отрисовка эффекта
Последовательность вызовов для применения состояния эффекта к устройству:
- ID3DXEffect::Begin задает активный метод.
- ID3DXEffect::BeginPass задает активный проход.
- ID3DXEffect::CommitChanges обновляет изменения для всех вызовов набора в проходе. Этот вызов следует вызывать перед любым вызовом draw.
- ID3DXEffect::EndPass завершает проход.
- ID3DXEffect::End завершает активный метод.
Код отрисовки эффектов также проще, чем соответствующий код отрисовки без эффекта. Ниже приведен код отрисовки с эффектом:
// 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 или GetAnnotationxxxx, могут иметь одну из трех форм:
- Дескриптор, возвращаемый функцией, такой как 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.
- Нельзя ссылаться внутри эффекта.
- Не может иметь подсемантику или вложенные заметки.
Параметры эффекта "Общий доступ"
Параметры эффекта — это все нестатические переменные, объявленные в эффекте. Сюда могут входить глобальные переменные и заметки. Параметры эффекта можно совместно использовать для разных эффектов, объявляя параметры с помощью "общего" ключевое слово, а затем создавая эффект с пулом эффектов.
Пул эффектов содержит общие параметры эффекта. Пул создается путем вызова 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
Повышение производительности с помощью preshaders
Preshader — это метод повышения эффективности шейдера путем предварительного вычисления константных выражений шейдера. Компилятор эффектов автоматически извлекает вычисления шейдера из тела шейдера и выполняет их на ЦП до запуска шейдера. Следовательно, префигуры работают только с эффектами. Например, эти два выражения можно вычислить за пределами шейдера перед его выполнением.
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.
Связанные темы