netlib.narod.ru< Назад | Оглавление | Далее >

Реализация постэкранных шейдеров

Уже были представлены несколько вспомогательных классов, таких как VBScreenHelper и PreScreenSkyCubeMapping, но для постэкранных шейдеров вам необходима цель визуализации, доступ к которой и управление осуществляются через класс XNA RenderTarget. Проблема с этим классом заключается в том, что вы должны вызывать достаточно много методов и самостоятельно обрабатывать многие вещи. Это особенно верно, если вы также хотите использовать буфер глубины для цели визуализации и восстанавливать их после модификации, что полезно для шейдеров наложения теней.

Класс RenderToTexture

По этой причине вводится новый вспомогательный класс, RenderToTexture, предоставляющий важные методы для упрощения поддержки постэкранных шейдеров (рис. 8.10). Наиболее важными для вас методами являются конструктор, получающий в параметре SizeType, Resolve и SetRenderTarget. Также очень полезно получение через свойства XnaTexture и RenderTarget. Заметьте также, что этот класс наследует все возможности класса Texture.


Рис. 8.10

Рис. 8.10


Например, если вы хотите создать полноэкранную карту сцены для шейдера PostScreenDarkenBorder.fx, надо использовать следующую строку:

sceneMapTexture = new RenderToTexture(
  RenderToTexture.SizeType.FullScreen);

Конструктор делает следующее:

/// <summary>
/// Создание внеэкранной текстуры заданного размера,
/// которая может использоваться для визуализации в текстуру
/// </summary>
public RenderToTexture(SizeType setSizeType)
{
  sizeType = setSizeType;
  CalcSize();

  texFilename = "RenderToTexture instance " +
    RenderToTextureGlobalInstanceId++;
[...]

  SurfaceFormat format = SurfaceFormat.Color;
  // Создание цели визуализации заданного размера
  renderTarget = new RenderTarget2D(
    BaseGame.Device,
    texWidth, texHeight, 1,
    format);
} // RenderToTexture(setSizeType)

Чтобы использовать эту цель визуализации и позволить направить всю визуализацию в нее, вам надо сейчас только вызвать метод SetRenderTarget, который использует несколько новых вспомогательных методов в классе BaseGame, чтобы обеспечить несколько расположенных в стеке целей визуализации (это требуется для более сложных постэкранных шейдеров):

sceneMapTexture.SetRenderTarget();

После того, как вся визуализация выполнена, вы вызываете метод Resolve, чтобы скопировать результаты из цели визуализации во внутреннюю текстуру (которая доступна через свойство XnaTexture). Это новый этап в XNA; он не требовался в DirectX и причина его появления заключается в поддержке оборудования Xbox 360, работа которого значительно отличается от функционирования видеокарт PC. На PC вы можете напрямую обратиться к цели визуализации и использовать ее для постэкранных шейдеров, но на Xbox 360 результаты цели визуализации помещаются в аппаратную область с доступом только для записи и к ним нельзя получить доступ. Вместо этого вы копируете цель визуализации во внутреннюю текстуру. Это потребует некоторого времени, но выполняется очень быстро, так что не стоит беспокоиться.

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

// Получаем содержимое текстуры sceneMap
sceneMapTexture.Resolve();
// Выполняем полный сброс к начальному состоянию
// вторичного буфера
BaseGame.ResetRenderTarget(true);

Вот почти все, что надо знать о работе с классом RenderToTexture. Дополнительная функциональность пока не требуется (до последних глав книги). Посмотрите тестовые модули класса RenderToTexture, чтобы больше узнать о нем.

Класс PostScreenDarkenBorder

Перед тем, как начать думать о написании класса PostScreenDarkenBorder, вы должны определить тестовый модуль и установить полный набор возможностей, которые вы хотите иметь в вашем классе постэкранного эффекта. Обратите внимание, что вы наследуете этот класс от ShaderEffect, подобно тому, как поступали с классом предэкранного эффекта, и имеете простой доступ к классу эффекта и параметрам эффекта. Но вам по-прежнему еще надо определить новые параметры эффекта, используемые в PostScreenDarkenBorder.fx.

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

public static void TestPostScreenDarkenBorder()
{
  PreScreenSkyCubeMapping skyCube = null;
  Model testModel = null;
  PostScreenDarkenBorder postScreenShader = null;

  TestGame.Start("TestPostScreenDarkenBorder",
    delegate
    {
      skyCube = new PreScreenSkyCubeMapping();
      testModel = new Model("Asteroid4");
      postScreenShader = new PostScreenDarkenBorder();
    },
    delegate
    {
      // Запускаем постэкранный шейдер
      // для визуализации в sceneMap
      postScreenShader.Start();

      // Рисуем фоновый небесный куб
      skyCube.RenderSky();

      // И нашу testModel (астероид)
      testModel.Render(Matrix.CreateScale(10));

      // И, наконец, показываем постэкранный шейдер
      postScreenShader.Show();
  });
} // TestPostScreenDarkenBorder()

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

В тестовом модуле вы используете три переменные:

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

Структура классов постэкранных шейдеров очень похожа на ту, которую мы уже видели в PreScreenSkyCubeMapping. Вам необходимы только новые методы Start и Show и несколько внутренних переменных для хранения новых параметров эффекта и проверки того, запущен или нет постэкранный шейдер (рис. 8.11).


Рис. 8.11

Рис. 8.11


Метод Start просто вызывает SetRenderTarget для текстуры карты сцены; важный код расположен в методе Show:

/// <summary>
/// Выполняем шейдеры и показываем результат на экране,
/// Start(..) должен быть вызван раньше и сцена должна быть
/// визуализирована в sceneMapTexture.
/// </summary>
public virtual void Show()
{
  // Применяем постэкранную засветку только если
  // текстура и эффект существуют
  if (sceneMapTexture == null || Valid == false || started == false)
    return;

  started = false;

  // Выполняем разрешение цели визуализации sceneMapTexture
  // для поддержки Xbox360
  sceneMapTexture.Resolve();

  // Не используем Z-буфер и запись в него
  BaseGame.Device.RenderState.DepthBufferEnable = false;
  BaseGame.Device.RenderState.DepthBufferWriteEnable = false;
  // Не используем никакого смешивания
  BaseGame.Device.RenderState.AlphaBlendEnable = false;

  if (windowSize != null)
    windowSize.SetValue(new float[]
      { sceneMapTexture.Width, sceneMapTexture.Height });
  if (sceneMap != null)
    sceneMap.SetValue(sceneMapTexture.XnaTexture);

  effect.CurrentTechnique = effect.Techniques["ScreenDarkenBorder"];

  // У нас должен быть ровно 1 проход!
  if (effect.CurrentTechnique.Passes.Count != 1)
    throw new Exception("This shader should have exactly 1 pass!");
  effect.Begin();
  for (int pass= 0; pass < effect.CurrentTechnique.Passes.Count; pass++)
  {
    if (pass == 0)
      // Выполняем полный сброс в исходное состояние вторичного буфера
      BaseGame.ResetRenderTarget(true);

    EffectPass effectPass = effect.CurrentTechnique.Passes[pass];
    effectPass.Begin();
    VBScreenHelper.Render();
    effectPass.End();
  } // for (pass, <, ++)
  effect.End();

  // Восстанавливаем состояния Z-буфера
  BaseGame.Device.RenderState.DepthBufferEnable = true;
  BaseGame.Device.RenderState.DepthBufferWriteEnable = true;
} // Show()

Сначала вы проверяете был ли должным образом вызван метод Start и запущен ли шейдер до сих пор; в ином случае цель визуализации с картой сцены не будет содержать никаких полезных данных. Затем вы выполняете разрешение цели визуализации для полной поддержки Xbox 360 и можете начинать устанавливать все необходимые параметры эффекта и техники. Вы можете спросить, почему здесь техника добавляется по имени, и будете правы, поскольку быстрее делать это по ссылке на технику, которая должна быть инициализирована в конструкторе. Однако в данном случае это не имеет большого значения; вы вызываете данный шейдер только один раз в кадре и возникающее различие в производительности не отмечает ни один инструмент профилировки (поскольку одна строка для кадра, который формируется многими тысячами строк кода, почти не влияет на быстродействие). Если вы вызываете шейдер более часто, то определенно, вам следует кэшировать эту ссылку на технику и, может быть, даже не устанавливать ее снова и снова в каждом кадре (она остается установленной с предыдущего кадра). За дополнительной информацией о таком виде оптимизации обращайтесь к коду класса ShaderEffect.

Теперь шейдер запущен и вы визуализируете экранный квадрат с помщью класса VBScreenHelper как обычно, но вам надо убедиться, что для каждого прохода правильно установлена цель визуализации. В нашем случае есть только один проход, и все, что надо сделать, это сбросить цель визуализации в состояние по умолчанию для вторичного буфера (вы всегда должны делать это для последнего прохода экранного шейдера, иначе ничего не увидите на экране). Более сложные примеры вы можете найти в классе PostScreenGlow.

Результаты тестового модуля

После того, как вы завершаете визуализацию на устройстве восстанавливаются режимы визуализации (может быть, вы все еще хотите визуализировать какие-нибудь трехмерные данные после отображения шейдера). Взглянув на код проекта вы увидите несколько дополнительных блоков try и catch, которые обеспечивают стабильность движка визуализации даже если в шейдере произошел сбой. Шейдер просто установит недопустимый режим и не будет больше применяться. Ошибка фиксируется в журнале и пользователь больше не видит результаты работы постэкранного шейдера, но остальная часть игры продолжает выполняться.

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

Запустив тестовый модуль TestPostScreenDarkenBorder вы должны увидеть результат, показанный на рис. 8.12. Можете свободно экспериментировать с шейдером и кодом в вашем приложении. Например, вы можете попробовать выполнять визуализацию в меньшую цель визуализации, например, используя только четверть размера экрана, и посмотреть, какое влияние это оказывает на итоговый результат (большие квадратные пиксели).


Рис. 8.12

Рис. 8.12


Вы также должны сразу, как только это возможно, проверить все ваши постэкранные шейдеры на Xbox 360, если она у вас есть. Это очень важно, поскольку иногда шейдеры могут вести себя по-другому, и вам необходимо гарантировать, что все цели визуализации поместятся в память Xbox 360, и все хорошо выполняется. Классы PostScreenDarkenBorder и RenderToTexture полностью совместимы с Xbox 360; они так же хорошо работают на Xbox 360.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz