Reference = add reference, в висуал студия 2010 не могу найти в вкладке Solution Explorer, Microsoft.Xna.Framework. Его нету. |
Визуализация примитивов
2.3.3. Использование эффектов в XNA Framework
Одним из основных классов XNA Framework, предназначенным для работы с эффектами, является класс Effect. Класс Effect является довольно сложным классом, содержащим ряд коллекций, отражающих структуру файла эффекта (рисунок 2.5). Как говорилось в прошлом разделе, в каждом эффекте HLSL имеется несколько техник, которые в свою очередь содержат несколько проходов. При этом минимально возможный эффект включает хотя бы одну технику и один проход. Соответственно, класс Effect содержит коллекцию Techniques с экземплярами классов EffectTechnique, инкапсулирующих техники. В свою очередь, каждая техника содержит коллекцию Passes экземпляров класса EffectPass с информацией об эффекте.
Загрузка и компиляция файла эффекта
Загрузка эффекта из файла *.fx с последующей компиляцией осуществляется при помощи статического метода Effect.CompileEffectFromFile:
public static CompiledEffect CompileEffectFromFile (string effectFile, CompilerMacro[] preprocessorDefines, CompilerIncludeHandler includeHandler, CompilerOptions options, TargetPlatform platform);
где
- effectFile – имя файла с эффектом.
- preprocessorDefines – массив макроопределений (аналогов директивы #define в C# ), используемых при компиляции эффекта. Мы будем использовать значение null.
- includeHandler – объект, используемый для обработки директив #include в fx -файле. Так как наш файл не содержит директив #include, мы будем использовать значение null.
- options – опции компилятора HLSL, задаваемые с использованием перечислимого типа CompilerOptions (таблица 2.5). Члены типа CompilerOptions являются битовыми флагами, что позволяет комбинировать их с использованием оператора OR. В качестве этого параметра, как правило, передается значение CompilerOptions.None.
- platform – значение перечислимого типа TargetPlatform, указывающее платформу, для которой компилируется эффект. В XNA Framework 1.0 поддерживаются две платформы: TargetPlatform.Windows и TargetPlatform.Xbox360, названия которых говорят за себя. Все примеры этого курса будут использовать значение TargetPlatform.Windows.
(*) – не поддерживается методом CompileEffectFromFile.
Если метод Effect.CompileEffectFromFile не сможет открыть fx -файл (например, из-за его отсутствия), то будет сгенерировано одно из исключений производных от System.IO.IOException вроде System.IO.FileNotFoundException или System.IO.DirectoryNotFoundException.
Метод CompileEffectFromFile возвращает структуру CompiledEffect, содержащую откомпилированный код эффекта, а так же отчет о компиляции эффекта (возникли ли какие-либо проблемы при компиляции эффекта и т.п.).
public struct CompiledEffect { // Сообщения о проблемах, возникших при компиляции эффекта public string ErrorsAndWarnings { get; } // Был ли эффект откомпилирован удачно public bool Success { get; } // Если свойство Success равное true, содержит откомпилированный код эффекта public byte[] GetEffectCode(); ... }
Стоит отметить, что метод GetEffectCode возвращает байт-код промежуточного языка наподобие того, что содержится в exe -файлах для платформы .NET. Соответственно, этот код с точки зрения человека является лишь бессмысленным набором байт. Тем не менее, как мы увидим далее, при необходимости этот байт-код может быть легко дизассемблирован удобочитаемый текстовый вид.
Примечание
При желании приложение может сохранить откомпилированный байт-код в каком-нибудь файле, и при следующих запусках считывать из файла уже готовый откомпилированный байт-код. Кстати, Visual C# 2005 Express при компиляции проектов, использующих Content Pipeline, автоматически выполняет компиляцию fx -файлов проекта и сохраняет полученный промежуточный код в файлах с расширением nvb. Таким образом, приложениям, использующим Content Pipeline, нет нужды самостоятельно компилировать fx -файлы.
Следующий этап - компиляция байт-кода промежуточного языка в машинный код вершинных и пиксельных процессоров текущей видеокарты. Эта операция автоматически осуществляется конструктором класса Effect:
public Effect(GraphicsDevice graphicsDevice, byte[] effectCode, CompilerOptions options, EffectPool pool);
где
- graphicsDevice - устройство Direct3D, которое будет использоваться для работы с эффектом
- byte[] effectCode -код эффекта, предварительно скомпилированный при помощи метода CompileEffectFromFile.
- options - опции компилятора, задающиеся использованием перечислимого типа CompilerOptions (таблица 2.5.). Довольно часто в качестве этого параметра передается значение CompilerOptions.NotCloneable, что позволяет несколько сэкономить объем используемой оперативной памяти.
- pool - экземпляр класса EffectPool, позволяющий нескольким эффектам использовать общие параметры. В наших первых примерах мы будем использовать не более одного fx -файла, этот параметр будет равен null.
После вызова конструктора класса Effect мы наконец-то получим готовый эффект. Теперь нам необходимо выбрать одну из техник эффекта и проверить ее поддержку текущей видеокартой. Техники эффекта хранятся в коллекции Techniques эффекта:
public EffectTechniqueCollection Techniques { get; }
Однако XNA -приложения достаточно редко обращаются к этой коллекции. Дело в том, что конструктор класса Effect автоматически находит первую попавшуюся технику эффекта и присваивает ее свойству CurrentTechnique.
public EffectTechnique CurrentTechnique { get; set; }
Соответственно, если эффект содержит лишь единственную технику, приложению для получения информации об этой техники достаточно обратиться к свойству CurrentTechnique, возвращающему экземпляр класса EffectTechnique, инкапсулирующий технику эффекта. Ниже приведено сокращенное определение класса EffectTechnique:
public sealed class EffectTechnique { // Название техники public string Name { get; } // Коллекция проходов техники public EffectPassCollection Passes { get; } // Выполняет валидацию техники public bool Validate(); ... }
И так, каждый эффект может содержать несколько техник. При этом некоторые техники эффекта могут нормально работать на текущем GPU, а некоторые (наиболее продвинутые) нет. Если требования техники ( technique ) превышают возможности текущего GPU (например, пользователь пытается запустить эффект использующий профиль ps_1_4 на NV2x ), XNA Framework проигнорирует технику. В результате примитивы, использующие эту технику, будут отображаться некорректно. Во избежание подобных неприятностей необходимо заранее проверить возможность выполнения данной техники средствами текущего графического устройства. Для этой цели в классе EffectTechnique предусмотрен метод Validate. Если техника может быть выполнена на текущем устройстве, метод Validate возвращает значение true, иначе - false. Во втором случае, приложение может попытаться подобрать альтернативную технику с меньшими системными требованиями или завершить приложение с сообщением о недостаточной "мощности" текущей видеокарты.
Резюмируя все вышесказанное можно предположить, что код для загрузки эффекта и выбора техники, как правило, имеет следующую структуру:
GraphicsDevice device; // Флаг, устанавливаемый в значение true при аварийном завершении работы приложения из-за // проблем в обработчике события Load closing = false; ... // Этот код обычно размещается в обработчике события Load. CompiledEffect compiledEffect; try { // Загружаем эффект из файла и компилируем в промежуточный код compiledEffect = Effect.CompileEffectFromFile (effectFileName, null, null, CompilerOptions.None, TargetPlatform.Windows); } // Если при загрузке файла эффекта возникли проблемы catch (IOException ex) { // Выводим сообщение об ошибке MessageBox.Show(ex.Message, "Критическая ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); // Завершаем работу приложения. Так как метод Close() нельзя вызвать из обработчика // события Load, приходится идти на хитрости (использовать обработчик события // Application.Idle, вызывающий внутри себя метод Close главной формы приложения, если // флаг closing равен true). closing = true; Application.Idle += new EventHandler(ApplicationIdle); return; } // Если эффект был скомпилирован с ошибками if (!compiledEffect.Success) { // Выдаем сообщение об ошибке MessageBox.Show(String.Format("Ошибка при компиляции эффекта: \r\n{0}", compiledEffect.ErrorsAndWarnings), "Критическая ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); closing = true; Application.Idle += new EventHandler(ApplicationIdle); return; } // Компилируем байт-код промежуточного языка и создаем объект эффекта. effect = new Effect(device, compiledEffect.GetEffectCode(), CompilerOptions.NotCloneable, null); // Если текущая техника не может быть выполнена на текущем графическом устройстве. if (!effect.CurrentTechnique.Validate()) { // Выводим сообщение об ошибке и завершаем работу приложения MessageBox.Show(String.Format("Ошибка при валидации техники \"{0}\" эффекта \"{1}\"\n\r" + "Скорее всего, функциональность шейдера превышает возможности GPU", effect.CurrentTechnique.Name, effectFileName), "Критическая ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); closing = true; Application.Idle += new EventHandler(ApplicationIdle); return; }
Визуализация объекта, использующего эффект
Визуализация примитивов, использующих эффект, начинается с вызова метода Effect.Begin:
public void Begin();
Далее приложение должно перебрать все проходы (коллекция passes) текущей техники ( CurrentTechnique ) и для каждой техники:
- Вызвать метод Pass текущего эффекта.
- Визуализировать примитивы с использованием метода GraphicsDevice .DrawUserPrimitives.
- Вызывать метод End текущего эффекта.
По окончанию визуализации эффекта приложение должно вызвать метод Effect.End. В итоге код визуализации примитива выглядит следующим образом:
Effect effect; ... // Фрагмент типового обработчика события Paint ... // Начинаем визуализацию примитивов с использованием эффекта effect. effect.Begin(); // Перебираем все проходы визуализации текущей техники foreach (EffectPass pass in effect.CurrentTechnique.Passes) { // Начинаем визуализацию текущего прохода pass.Begin() ; // Визуализируем примитивы device.DrawUserPrimitives(...); ... device.DrawUserPrimitives(...); // Завершаем проход pass.End(); } // Заканчиваем визуализацию эффекта effect.End();
Ну что ж, этих знаний вполне достаточно, для того, чтобы попробовать свои силы в визуализации простых примитивов.
Примечание
Как известно, оператор foreach, используемый нами для перебора коллекции проходов ( effectCurrentTechnique.Passes ), обладает несколько более низкой производительностью по сравнению с классическим оператором for. Однако при небольшом количестве итераций эта особенность не является сколь либо заметным недостатком.