Введение в программирование трехмерных игр с DX9

         

Активация эффекта


19.6.2. Активация эффекта

После получения дескриптора выбранной техники надо ее активировать. Это делает следующий метод:

HRESULT ID3DXEffect::SetTechnique( D3DXHANDLE hTechnique // Handle to the technique to set. );

ПРИМЕЧАНИЕ

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

HRESULT ID3DXEffect::ValidateTechnique( D3DXHANDLE hTechnique // Дескриптор проверяемой техники );

Вспомните, что в файле эффекта может быть несколько техник, каждая из которых пытается реализовать требуемый эффект, используя свой набор возможностей оборудования, в надежде на то, что хотя бы одна реализация техники сможет работать на компьютере пользователя. Поэтому для эффекта вам надо перебрать все доступные техники и проверить каждую из них с помощью метода ID3DXEffect::ValidateTechnique, чтобы убедиться какие техники работают, а какие — нет, и дальше действовать соотвественно результатам.

Аннотации



19.2.5. Аннотации

Помимо указания способа использования о котором мы уже говорили, к переменным могут присоединяться аннотации. Аннотации никак не используются в HLSL, но к ним можно получить доступ из приложения через каркас эффектов. Аннотации используются просто для присоединения к переменным «примечаний», которые приложение хотело бы видеть связанными с данной переменной. Добавляются аннотации с использованием синтаксиса <аннотация>, который иллюстрирует следующий фрагмент кода:

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

В данном примере аннотация — это <string name = "tiger.bmp";>. Она связывает с переменной tex0 строку, а именно имя файла, содержащего данные текстуры. Ясно, что добавление к текстуре аннотации с именем соответсвующего файла может оказаться полезным.

Получить аннотации можно с помощью следующего метода:

D3DXHANDLE ID3DXEffect::GetAnnotationByName( D3DXHANDLE hObject, LPCSTR pName );

Здесь pName — это имя аннотации для которой мы хотим получить дескриптор, а hObject — это дескриптор родительского блока в котором расположена аннотация, такого как техника, проход или структурный блок. После того, как мы получили дескриптор аннотации, можно получить информацию о ней с помощью метода ID3DXEffect::GetParameterDesc, который заполняет структуру D3DXCONSTANT_DESC. За подробностями обращайтесь к документации DirectX SDK.



EffectEdit


Перед тем, как завершить данную главу, упомянем о программе EffectEdit, которая поставляется вместе с DirectXSDK. Вы найдете ее в каталоге \DXSDK\Samples\C++\Direct3D\Bin. Окно этой программы показано на Рисунок  19.3.



Каркас эффектов




Графический эффект обычно состоит из нескольких компонентов: вершинный и/или пиксельный шейдер, список режимов устройства, которые должны быть установлены, и один или несколько проходов визуализации. Кроме того, часто для графических эффектов желательно наличие механизма обработки сбоев, позволяющего эффекту выполняться на различных видеокартах (то есть, необходимо наличие нескольких версий эффекта, которые реализуют его или пытаются достичь наиболее близкого соответствия реализации, используя возможности установленного оборудования). Ясно, что все эти задачи связаны с одним эффектом. Следовательно, логичным шагом будет попытка объединения всех этих задач в один блок.

Каркас эффектов Direct3D предоставляет подобный механизи для объединения связанных с визуальным эффектом задач в один файл эффекта. Такая реализация эффектов имеет ряд преимуществ. Во-первых, она позволяет нам менять реализацию эффекта без перекомпиляции исходного кода приложения. Это делает процесс обновления эффектов гораздо проще, независимо от того, вызван ли он исправлением обнаруженных ошибок, или усовершенствованиями, связанными с появлением нового оборудования. Во-вторых, мы объединяем все компоненты эффекта в одном файле.

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

Цели

Получить представление о структуре и организации файла эффекта.

Узнать о некоторых дополнительных встроенных объектах HLSL.

Узнать как в файле эффектов указываются режимы устройства.

Научиться создавать и использовать эффекты.

Приобрести опыт работы с каркасом эффектов, изучив примеры программ.



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


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

Получить дескриптор техники из файла эффектов, которая будет использоваться.

Активировать требуемую технику.

Начать исполнение активной техники.

Для каждого прохода визуализации из активной техники визуализировать необходимые объекты. Вспомните, что техника может состоять из нескольких проходов визуализации и мы должны визуализировать объекты по одному разу для каждого прохода.

Закончить исполнение активной техники.



Файл эффекта объединяет завершенную реализацию


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

Начало эффекта



19.6.3. Начало эффекта

Для визуализации объектов с использованием эффекта мы должны поместить вызовы функций рисования между вызовами методов ID3DXEffect::Begin и ID3DXEffect::End. Эти функции включают и выключают эффект соответственно.

HRESULT ID3DXEffect::Begin( UINT* pPasses, DWORD Flags );

pPasses— Возвращает количество проходов в активной технике.

Flags — Любой из следующих флагов:

Zero (0) — Приказывает эффекту сохранить текущее состояние устройства и состояние шейдеров и восстановить их после завершения эффекта (при вызове ID3DXEffect::End). Это очень полезно, поскольку файл эффекта может менять состояния и часто требуется вернуть состояния, которые были до начала обработки эффекта.

D3DXFX_DONOTSAVESTATE — Приказывает не сохранять и не восстанавливать состояния устройства (за исключением состояния шейдеров).

D3DXFX_DONOTSAVESHADERSTATE — Приказывает эффекту не сохранять и не восстанавливать состояния шейдеров.



Объекты текстуры


19.2.1. Объекты текстуры

Встроенный тип HLSL texture представляет объект IDirect3DTexture9. Используя объект texture мы можем связывать текстуру с заданным этапом выборки непосредственно в файле эффекта. У объекта texture есть следующие доступные члены данных:

type— Тип текстуры (т.е., 2D, 3D).

format — Формат пикселей текстуры.

width — Ширина текстуры в пикселях.

height — Высота текстуры в пикселях.

depth — Глубина (для трехмерных объемных текстур) текстуры в пикселях.

ПРИМЕЧАНИЕ

До сих пор мы использовали текстуры только для хранения изображений, но познакомившись с провессиональными техниками вы обнаружите, что текстуры применяются для хранения произвольной табличной информации. Другими словами, текстура — это просто таблица с данными; совсем необязательно, чтобы она хранила изображение. Например, в рельефном текстурировании (bump mapping) используются карты нормалей (normal map), представляющие собой текстуры, содержащие векторы нормалей для каждого элемента.

Объекты вершинных и пиксельных шейдеров


19.2.3. Объекты вершинных и пиксельных шейдеров

Встроенные типы HLSL vertexshader и pixelshader представляют вершинные и пиксельные шейдеры соответственно. Они используются в каркасе эффектов для ссылки на конкретный вершинный и/или пиксельный шейдер, который должен использоваться в данном проходе визуализации. Типы vertexshader и pixelshader могут инициализироваться из приложения через интерфейс ID3DXEffect с помощью методов ID3DXEffect::SetVertexShader и ID3DXEffect::SetPixelShader соответственно. Например, пусть Effect— это корректный объект ID3DXEffect, VS — это корректный объект IDirect3DVertexShader9 и VSHandle  — это значение типа D3DXHANDLE, которое ссылается на объект vertexshader в файле эффекта; тогда мы можем инициализировать вершинный шейдер, на который ссылается VSHandle написав:

Effect->SetVertexShader(VSHandle, VS);

Мы больше узнаем о методах SetVertexShader и SetPixelShader когда будем обсуждать инициализацию переменных файла эффекта из приложения.

Кроме того, мы можем написать вершинный и/или пиксельный шейдер непосредственно в файле эффекта. Затем, используя специальный синтаксис компиляции мы можем инициализировать переменную шейдера. Приведенный ниже фрагмент кода показывает инициализацию переменной ps типа pixelshader.

// Определение Main: OUTPUT Main(INPUT input){...}

// Компиляция Main: pixelshader ps = compile ps_2_0 Main();

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

И, наконец, мы связываем шейдер с конкретным проходом, как показано ниже:

// Определение Main: OUTPUT Main(INPUT input){...}

// Компиляция Main: vertexshader vs = compile vs_2_0 Main();

pass P0 { // Устанавливаем vs в качестве // вершинного шейдера для данного прохода vertexshader = (vs);

...

}

Или в более компактной форме:

pass P0 { // Устанавливаем вершинный шейдер с точкой входа Main() // в качестве вершинного шейдера для данного прохода vertexShader = compile vs_2_0 Main();

...

}

ПРИМЕЧАНИЕ

Следует упомянуть, чтобы вы имели представление об этой возможности, что типы vertexshader и pixelshader можно инициализировать с использованием следующего синтаксиса:

vertexshader vs = asm { /* здесь размещаются ассемблерные инструкции */ };

pixelshader ps = asm { /* здесь размещаются ассемблерные инструкции */ };

Этот синтаксис используется если вы пишете свои шейдеры на языке ассемблера.

Объекты выборки и режимы выборки



19.2.2. Объекты выборки и режимы выборки

Мы обсуждали объекты выборки в главе 18; однако каркас эффектов добавляет новое ключевое слово sampler_state. С помощью ключевого слова sampler_state мы можем инициализировать объект sampler (то есть, устанавливать текстуру и режимы выборки для объекта выборки непосредственно из файла эффкетов). Следующий фрагмент кода иллюстрирует эту возможность:

Texture Tex;

sampler S0 = sampler_state { Texture = (Tex); MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; };

Здесь мы связываем текстуру Tex с этапом текстурирования, которому соответствует объект S0, а также задаем режимы выборки для этапа выборки, которому соответствует S0. Все эти параметры мы явно устанавливаем непосредственно из файла эффектов!



Окно приложения Lighting and Texturing Текстуры, материал и режимы освещения заданы в файле эффекта



Рисунок 19.1. Окно приложения Lighting and Texturing. Текстуры, материал и режимы освещения заданы в файле эффекта


Файл эффекта выглядит следующим образом:

// // Файл: light_tex.txt // Описание: Файл эффекта, устанавливающий состояния устройства // для освещения и текстурирования трехмерной модели //

// // Глобальные переменные //

matrix WorldMatrix; matrix ViewMatrix; matrix ProjMatrix;

texture Tex;

// // Выборка //

// Связываем текстуру Tex c соответствующим этапом текстурирования S0 // и задаем режимы выборки для этапа выборки S0 sampler S0 = sampler_state { Texture = (Tex); MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; };

// // Эффект //

technique LightAndTexture { pass P0 { // // Устанавливаем разные режимы визуализации // pixelshader = null; // Пиксельных шейдеров нет vertexshader = null; // Вершинных шейдеров нет fvf = XYZ | Normal | Tex1; // Настраиваемый формат вершин Lighting = true; // Разрешаем освещение NormalizeNormals = true; // Нормализуем нормали SpecularEnable = false; // Отключаем отражаемый свет

// // Установка состояний преобразования // WorldTransform[0] = (WorldMatrix); ViewTransform = (ViewMatrix); ProjectionTransform = (ProjMatrix);

// // Инициализируем источник освещения с индексом 0. // Мы заполняем все компоненты light[0] потому что // в документации Direct3D рекомендуется заполнять все // компоненты для повышения производительности // LightType[0] = Directional; LightAmbient[0] = {0.2f, 0.2f, 0.2f, 1.0f}; LightDiffuse[0] = {1.0f, 1.0f, 1.0f, 1.0f}; LightSpecular[0] = {0.0f, 0.0f, 0.0f, 1.0f}; LightDirection[0] = {1.0f, -1.0f, 1.0f, 0.0f}; LightPosition[0] = {0.0f, 0.0f, 0.0f, 0.0f}; LightFalloff[0] = 0.0f; LightRange[0] = 0.0f; LightTheta[0] = 0.0f; LightPhi[0] = 0.0f; LightAttenuation0[0] = 1.0f; LightAttenuation1[0] = 0.0f; LightAttenuation2[0] = 0.0f;

// Разрешаем использовать этот источник света

LightEnable[0] = true;

// // Устанавливаем компоненты материала. Это аналогично // вызову IDirect3DDevice9::SetMaterial. // MaterialAmbient = {1.0f, 1.0f, 1.0f, 1.0f}; MaterialDiffuse = {1.0f, 1.0f, 1.0f, 1.0f}; MaterialEmissive = {0.0f, 0.0f, 0.0f, 0.0f}; MaterialPower = 1.0f; MaterialSpecular = {1.0f, 1.0f, 1.0f, 1.0f};

// // Привязываем объект выборки S0 к // этапу выборки 0, который задается Sampler[0]. // Sampler[0] = (S0); } }

В этом файле эффекта мы сперва устанавливаем состояния устройства, как было описано в разделе 19.3. Например, непосредственно в файле эффекта мы устанавливаем источник света и материал. Кроме того, мы задаем матрицы преобразования, текстуру и режимы выборки. Эти состояния будут применены для любых объектов, которые визуализируются с использованием техники LightAndTexture в проходе визуализации P0.

ПРИМЕЧАНИЕ

Обратите внимание, что ссылаясь на переменные из файла эффекта мы заключаем их имена в скобки. Например, для ссылок на матрицы преобразований мы должны писать (WorldMatrix), (ViewMatrix) и (ProjMatrix). Отсутствие скобок недопустимо. Поскольку большая часть подготовительной работы, такая как установка освещения, материалов и текстур, выполняется в файле эффекта, в коде приложения достаточно создать эффект и разрешить его использование. В примере объявлены следующие глобальные переменные, относящиеся к рассматриваемой теме:

ID3DXEffect* LightTexEffect = 0;

D3DXHANDLE WorldMatrixHandle = 0; D3DXHANDLE ViewMatrixHandle = 0; D3DXHANDLE ProjMatrixHandle = 0; D3DXHANDLE TexHandle = 0;

D3DXHANDLE LightTexTechHandle = 0;

Здесь нет ничего интересного — только указатель на ID3DXEffect и несколько дескрипторов. LightTexTechHandle — это дескриптор техники, на что указывает строка «Tech» в его имени.

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

bool Setup() { HRESULT hr = 0;

// // ...[Пропущена загрузка X-файла] //

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

ID3DXBuffer* errorBuffer = 0; hr = D3DXCreateEffectFromFile( Device, // связанное устройство "light_tex.txt", // имя файла эффекта 0, // нет определений препроцессора 0, // нет интерфейса ID3DXInclude D3DXSHADER_DEBUG, // флаги компиляции 0, // нет совместного использования параметров &LightTexEffect, // возвращает указатель на интерфейс эффекта &errorBuffer); // возвращает сообщения об ошибках



// Выводим любые сообщения об ошибках if(errorBuffer) { ::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0); d3d::Release<ID3DXBuffer*>(errorBuffer); }

if(FAILED(hr)) { ::MessageBox(0, "D3DXCreateEffectFromFile() - FAILED", 0, 0); return false; }

// // Сохраняем дескрипторы часто используемых параметров //

WorldMatrixHandle = LightTexEffect->GetParameterByName(0, "WorldMatrix"); ViewMatrixHandle = LightTexEffect->GetParameterByName(0, "ViewMatrix"); ProjMatrixHandle = LightTexEffect->GetParameterByName(0, "ProjMatrix"); TexHandle = LightTexEffect->GetParameterByName(0, "Tex"); LightTexTechHandle = LightTexEffect->GetTechniqueByName("LightAndTexture");

// // Устанавливаем параметры эффекта //

// Матрицы D3DXMATRIX W, P;

D3DXMatrixIdentity(&W); LightTexEffect->SetMatrix(WorldMatrixHandle, &W);

D3DXMatrixPerspectiveFovLH( &P, D3DX_PI * 0.25f, // 45 градусов (float)Width / (float)Height, 1.0f, 1000.0f);

LightTexEffect->SetMatrix(ProjMatrixHandle, &P);

// Текстура IDirect3DTexture9* tex = 0; D3DXCreateTextureFromFile(Device, "Terrain_3x_diffcol.jpg", &tex);

LightTexEffect->SetTexture(TexHandle, tex);

d3d::Release<IDirect3DTexture9*>(tex);

return true; }

Функция Display прямолинейна и выполняет действия, описанные в разделе 19.6:

bool Display(float timeDelta) { if(Device) { // // ...[Пропущено обновление камеры] //

// Устанавливаем обновленную матрицу вида LightTexEffect->SetMatrix(ViewMatrixHandle, &V);

// // Активируем технику и выполняем визуализацию //

Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); Device->BeginScene();

// Устанавливаем используемую технику LightTexEffect->SetTechnique(LightTexTechHandle);

UINT numPasses = 0; LightTexEffect->Begin(&numPasses, 0);

for(int i = 0; i < numPasses; i++) { LightTexEffect->Pass(i);

for(int j = 0; j < Mtrls.size(); j++) { Mesh->DrawSubset(j); } } LightTexEffect->End();

Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; }


Окно программы EffectEdit, поставляемой вместе с DirectX SDK



Рисунок 19.3. Окно программы EffectEdit, поставляемой вместе с DirectX SDK


Программа EffectEdit полезна для написания и тестирования файлов эффектов. Мы рекомендуем вам выделить время для знакомства с этой утилитой.



Окно программы Fog Effect В этом



Рисунок 19.2. Окно программы Fog Effect. В этом примере мы используем линейную функцию тумана и режимы визуализации тумана задаются в файле эффекта




Получение дескриптора эффекта


19.6.1. Получение дескриптора эффекта

Первый этап использования техники— это получение значения D3DXHANDLE для данной техники. Дескриптор техники может быть получен с помощью следующего метода:

D3DXHANDLE ID3DXEffect::GetTechniqueByName( LPCSTR pName // Имя техники );

ПРИМЕЧАНИЕ

На практике в файле эффекта обычно содержится несколько техник, разработанных для различных типов оборудования. Таким образом, приложение обычно выполняет провекру возможностей системы для определения возможностей оборудования и выборе на основе данных проверки наилучшей техники. Взгляните на описание метода ID3DXEffect::ValidateTechnique в следующем разделе.

Приведенный ниже фрагмент кода иллюстрирует



19.6.6. Пример

Приведенный ниже фрагмент кода иллюстрирует все пять этапов, необходимых для использования эффекта:
// Файл эффекта: technique T0 { pass P0 {
...
} } ====================================
// Исходный код приложения
// Получаем дескриптор техники. D3DXHANDLE hTech = 0; hTech = Effect->GetTechniqueByName("T0");
// Активируем технику Effect->SetTechnique(hTech);
// Начинаем активную технику UINT numPasses = 0; Effect->Begin(&numPasses, 0);
// Для каждого прохода визуализации for(int i = 0; i < numPasses; i++) { // Устанавливаем текущий проход Effect->Pass(i);
// Визуализируем объекты для i-ого прохода Sphere->Draw(); } // Завершаем эффект Effect->End();

Пример приложения: мультипликационный эффект


Оба рассмотренных нами к настоящему моменту файла эффектов не используют шейдеры. Поскольку шейдеры обычно являются важной составной частью эффектов, стоит рассмотреть хотя бы один пример их совместного использования. Пример CartoonEffect реализует мультипликационное затенение, которое обсуждалось в главе 17, но на этот раз мы воспользуемся каркасом эффектов. Посмотрите на слегка урезанную версию файла эффекта:

// // Файл: tooneffect.txt // Описание: Мультипликационное затенение в файле эффектов. //

extern matrix WorldMatrix; extern matrix ViewMatrix; extern matrix ProjMatrix; extern vector Color; extern vector LightDirection; static vector Black = {0.0f, 0.0f, 0.0f, 0.0f}; extern texture ShadeTex;

struct VS_INPUT { vector position : POSITION; vector normal : NORMAL; };

struct VS_OUTPUT { vector position : POSITION; float2 uvCoords : TEXCOORD; vector diffuse : COLOR; };

// Шейдер мультипликационного затенения VS_OUTPUT Main(VS_INPUT input) { ...[Код шейдера для краткости пропущен] }

sampler ShadeSampler = sampler_state { Texture = (ShadeTex); MinFilter = POINT; // фильтрация в мультипликационном // затенении отключается MagFilter = POINT; MipFilter = NONE; };

technique Toon { pass P0 { // Устанавливаем вершинный шейдер прохода P0 vertexShader = compile vs_1_1 Main();

// Связываем объект выборки с этапом выборки 0. Sampler[0] = (ShadeSampler); } }

Обратите внимание, что функция шейдера мультипликационного затенения определена в файле эффекта, и мы указываем, какой шейдер будет применяться в данном проходе, с помощью синтаксиса vertexShader = compile vs_1_1 Main(); в блоке прохода. Состояния устройства как обычно устанавливаются в файле эффекта.



Пример приложения: освещение и текстурирование в файле эффектов


Для разминки давайте создадим файл эффекта, который выполняет освещение и текстурирование трехмерной модели. Пример в работе использует только функции фиксированного конвейера, показывая что каркас эффектов не ограничен эффектами, использующими шейдеры. На Рисунок 19.1 показано окно примера Lighting and Texturing.



Пример приложения: туман


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

Хотя мы и не можем уделить этой теме то внимание, которого она заслуживает, здесь мы приведем краткий пример реализации тумана. Мы не будем вдаваться в детали, но покажем и исследуем код Direct3D, который является интуитивно понятным.

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

ПРИМЕЧАНИЕ

Direct3D также поддерживает пиксельный туман (также называемый табличным туманом), который является более точным, чем вершинный туман.

// // Файл: fog.txt // Описание: Файл эффекта, устанавливающий режимы визуализации // для линейного вершинного тумана // technique Fog { pass P0 { // // Устанавливаем различные режимы визуализации //

pixelshader = null; vertexshader = null; fvf = XYZ | Normal; Lighting = true; NormalizeNormals = true; SpecularEnable = false;

// // Режимы тумана //

FogVertexMode = LINEAR; // Линейная функция тумана FogStart = 50.0f; // Туман начинается в // 50 единицах от камеры. FogEnd = 300.0f; // Туман заканчивается в // 300 единицах от камеры FogColor = 0x00CCCCCC; // Туман серого цвета FogEnable = true; // Разрешить вершинный туман } }

Как видите, линейный вершинный туман управляется через пять простых режимов визуализации:

FogVertexMode — Задает функцию тумана, которая будет использоваться для вершинного тумана. Функция тумана определяет как изменяется плотность тумана с увеличением расстояния до камеры, поскольку естественно, что туман менее плотный возле камеры и становится более плотным по мере увеличения расстояния. Можно использовать значения LINEAR, EXP и EXP2. Эти функции определены следующим образом:

Функция тумана LINEAR:

Состояния устройства в файле эффекта

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

Состояние = Значение;

Чтобы просмотреть полный список доступных состояний, поищите слово «states» в алфавитном указателе документации DirectX SDK или на вкладке Contents справочной системы SDK выберите пункт DirectX Graphics\Reference\Effect Reference\Effect Format\States.

Возьмем, к примеру, состояние FillMode. Если вы поищете его упоминание в документации SDK, то увидите, что доступные для него значения — это те же значения, которые доступны для режима D3DFILLMODE, только без префикса D3DFILL_. Если посмотреть описание D3DFILLMODE в документации SDK, мы обнаружим допустимые значения D3DFILL_POINT, D3DFILL_WIREFRAME, и D3DFILL_SOLID. Таким образом, для файла эффектов мы отбрасываем префикс и получаем следующие допустимые значения состояния FillMode: POINT, WIREFRAME и SOLID. Например, мы можем написать в файле эффектов:

FillMode = WIREFRAME; FillMode = POINT; FillMode = SOLID;

ПРИМЕЧАНИЕ

В последующих разделах мы будем устанавливать некоторые состояния устройства в примерах программ. В большинстве случаев назначение состояния ясно видно из его названия, но если вам потребуются дополнительные разъяснения, обратитесь к документации SDK.

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


Эффект представляется интерфейсом ID3DXEffect, который мы создаем с помощью следующей функции из библиотеки D3DX:

HRESULT D3DXCreateEffectFromFile( LPDIRECT3DDEVICE9 pDevice, LPCSTR pSrcFile, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, DWORD Flags, LPD3DXEFFECTPOOL pPool, LPD3DXEFFECT* ppEffect, LPD3DXBUFFER *ppCompilationErrors );

pDevice — Устройство, связанное с создаваемым эффектом ID3DXEffect.

pSrcFile — Имя текстового файла (файла эффекта) содержащего исходный код того эффекта, который мы хотим скомпилировать.

pDefines — Необязательный параметр, в этой книге мы всегда будем указывать в нем null.

pInclude — Указатель на интерфейс ID3DXInclude. Этот интерфейс разработан для тех приложений, которым требуется переопределить устанавливаемое по умолчанию поведение включения. В общем случае поведение по умолчанию замечательно работает и поэтому мы игнорируем данный параметр, передавая в нем null.

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

D3DXSHADER_DEBUG — Приказывает компилятору включать в скомпилированный файл отладочную информацию.

D3DXSHADER_SKIPVALIDATION — Приказывает компилятору не выполнять проверку корректности кода. Этот флаг следует использовать только при работе с теми шейдерами в правильности кода которых вы абсолютно уверены.

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

pPool — Необязательный указатель на интерфейс ID3DXEffectPool, используемый для определения того, как параметры эффекта будут совместно использоваться несколькими экземплярами эффекта. В этой книге в данном параметре мы всегда указываем null, сообщая тем самым что параметры не будут совместно использоваться несколькими файлами эффектов.


ppEffect — Возвращает указатель на интерфейс ID3DXEffect, представляющий созданный эффект.

ppCompilationErrors — Возвращает указатель на интерфейс ID3DXBuffer, содержащий строку с кодами обнаруженых при компиляции ошибок и их описанием.

Вот пример вызова функции D3DXCreateEffectFromFile:

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

ID3DXEffect* Effect = 0; ID3DXBuffer* errorBuffer = 0; hr = D3DXCreateEffectFromFile( Device, // связанное устройство "effect.txt", // имя исходного файла 0, // нет объявлений препроцессора 0, // нет интерфейса ID3DXInclude D3DXSHADER_DEBUG, // флаги компиляции 0, // параметры не используются совместно &Effect, // возвращает результат &errorBuffer); // возвращает строку с ошибками

// Выводим любые сообщения об ошибках if( errorBuffer ) { ::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0); d3d::Release<ID3DXBuffer*>(errorBuffer); }

if(FAILED(hr)) { ::MessageBox(0, "D3DXCreateEffectFromFile() - FAILED", 0, 0); return false; }


Строки



19.2.4. Строки

Существуют и строковые объекты, которые можно использовать следующим образом:

string filename = "texName.bmp";

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



Техники и проходы

Файл эффекта состоит из одной или нескольких техник (techniques). Техникой называется конкретный способ реализации спецэффекта. Другими словами, файл эффекта описывает один или несколько способов реализации одного и того же спецэффекта. Зачем надо несколько различных реализаций одного и того же эффекта? Дело в том, что установленное на компьютере оборудование может не поддерживать определенную реализацию эффекта. Следовательно необходимо реализовать несколько версий одного и того же эффекта, ориентированных на различное оборудование.

ПРИМЕЧАНИЕ

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

Возможность реализовать все версии эффекта в одном файле дает нам более полную инкапсуляцию эффекта в целом, а объединение реализации эффекта это и есть одна из целей каркаса эффектов.

Каждая техника объединяет один или несколько проходов визуализации (rendering passes). Проход визуализации объединяет режимы устройства, режимы выборки и шейдеры, используемые для визуализации на данном этапе.

ПРИМЕЧАНИЕ

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

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

В качестве примера приведем скелет файла эффектов с двумя техниками, где первая техника состоит из одного прохода, а вторая — из двух:

// effect.txt ... technique T0 { // Первый и единственный проход для данной техники pass P0 { ...[указываем состояния устройства, шейдеры, выборки и т.д. для прохода] } }

technique T1 { // Первый проход pass P0 { ...[указываем состояния устройства, шейдеры, выборки и т.д. для прохода] }

// Второй проход pass P1 { ...[указываем состояния устройства, шейдеры, выборки и т.д. для прохода] } }



Установка констант


Как и в случае с вершинными и пиксельными шейдерами нам необходима возможность инициализировать переменные эффекта из приложения. Однако, вместо таблицы констант, которой мы пользовались при работе с вершинными и пиксельными шейдерами, интерфейс ID3DXEffect предоставляет встроенные методы для инициализации переменных. Мы не будем приводить список всех методов для инициализации различных типов переменных, поскольку он очень велик и в нем много повторений. Если вам все же хочется увидеть полный список — обратитесь к документации DirectX SDK. Вот сокращенный список методов:

Функция Описание
HRESULT ID3DXEffect::SetFloat(
   D3DXHANDLE hParameter,
   FLOAT f
);
Присваивает идентифицируемой дескриптором hParameter переменной с плавающей точкой из файла эффекта значение f
HRESULT ID3DXEffect::SetMatrix(
   D3DXHANDLE hParameter,
   CONST D3DXMATRIX* pMatrix
);
Инициализирует идентифицируемую дескриптором hParameter матрицу в файле эффекта, копируя в нее значения из матрицы на которую указывает pMatrix
HRESULT ID3DXEffect::SetString(
   D3DXHANDLE hParameter,
   CONST LPCSTR pString
);
Инициализирует идентифицируемую дескриптором hParameter строковую переменную в файле эффекта, копируя в нее текст из строки на которую указывает pString
HRESULT ID3DXEffect::SetTexture(
   D3DXHANDLE hParameter,
   LPDIRECT3DBASETEXTURE9 pTexture
);
Инициализирует идентифицируемый дескриптором hParameter объект текстуры в файле эффекта на основании текстуры, на которую указывает pTexture
HRESULT ID3DXEffect::SetVector(
   D3DXHANDLE hParameter,
   CONST D3DXVECTOR4* pVector
);
Инициализирует идентифицируемый дескриптором hParameter вектор в файле эффекта, копируя в него значения из вектора на который указывает pVector
HRESULT ID3DXEffect::SetVertexShader(
   D3DXHANDLE hParameter,
   LPDIRECT3DVERTEXSHADER9 pVertexShader
);
Инициализирует идентифицируемый дескриптором hParameter объект вершинного шейдера в файле эффекта на основании вершинного шейдера, на который указывает pVertexShader
HRESULT ID3DXEffect::SetPixelShader(
   D3DXHANDLE hParameter,
   LPDIRECT3DPIXELSHADER9 pPShader
);
Инициализирует идентифицируемый дескриптором hParameter объект пиксельного шейдера в файле эффекта на основании пиксельного шейдера, на который указывает pPShader
< Мы получаем дескрипторы переменных (также называемых параметры эффекта) с помощью следующего метода:

D3DXHANDLE ID3DXEffect::GetParameterByName( D3DXHANDLE hParent, // область видимости переменной - // родительская структура LPCSTR pName // имя переменной );

Его сигнатура аналогична методу ID3DXConstantTable::GetConstantByName. То есть первый параметр — это значение типа D3DXHANDLE, идентифицирующее родительскую структуру в пределах которой живет переменная, дескриптор которой мы хотим получить. Для глобальных переменных родительская структура отсутствует, и в этом параметре мы передаем null. Второй параметр — это имя переменной в том виде, в котором оно приведено в коде файла эффекта.

Для примера давайте взглянем на инициализацию нескольких переменных в файле эффекта:

// Данные для инициализации D3DXMATRIX M; D3DXMatrixIdentity(&M);

D3DXVECTOR4 color(1.0f, 0.0f, 1.0f, 1.0f);

IDirect3DTexture9* tex = 0; D3DXCreateTextureFromFile(Device, "shade.bmp", &tex);

// Получаем дескрипторы параметров D3DXHANDLE MatrixHandle = Effect->GetParameterByName(0, "Matrix"); D3DXHANDLE MtrlHandle = Effect->GetParameterByName(0, "Mtrl"); D3DXHANDLE TexHandle = Effect->GetParameterByName(0, "Tex");

// Инициализируем параметры Effect->SetMatrix(MatrixHandle, &M); Effect->SetVector(MtrlHandle, &color); Effect->SetTexture(TexHandle, tex);

ПРИМЕЧАНИЕ

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

HRESULT ID3DXEffect::GetMatrix( D3DXHANDLE hParameter, D3DXMATRIX* pMatrix ); Чтобы увидеть полный список методов, обратитесь к документации DirectX SDK.


Установка текущего прохода визуализации



19.6.4. Установка текущего прохода визуализации

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

HRESULT ID3DXEffect::Pass( UINT iPass // Идентифицирующий проход индекс );

Проходы визуализации для техники нумеруются в порядке 0 ... n – 1 для n проходов. Таким образом, мы можем перебрать все проходы визуализации с помощью простого цикла for в теле которого будем рисовать все необходимые объекты. Пример реализации такого подхода приведен в разделе 19.6.6.



Встроенные объекты HLSL


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



Завершение эффекта



19.6.5. Завершение эффекта

После того, как завершена визуализация всех объектов во всех проходах, мы завершаем работу эффекта вызовом метода ID3DXEffect::End:

HRESULT ID3DXEffect::End(VOID);