Задача
Вы хотите разместить содержимое экрана в текстуре, которая, например, позволяет вам сохранять скриншот в файл. Как другой пример, вы, возможно, хотите вывести сцену в текстуру, которая будет использоваться как карта глубины/преломления или на вход эффекта постобработки.
Решение
Самое простое решение использует метод device.ResolveBackBuffer, который сохраняет текущее содержание заднего буфера в текстуру.
Однако, если вы хотите, чтобы сцена была выведена только в текстуру, или в текстуру определенного размера, сначала вы должны изменить цель рендеринга от экрана до заданной в памяти, используя метод device.SetRenderTarget. Как только сцена будет полностью сохранена в этой цели, вы можете сохранить ее содержимое в текстуру.
Как это сделать
Самый простой подход вывести сцену на экран и скопировать содержимое заднего буфера в ResolveTexture2D. Поэтому, сначала вы должны создать объект ResolveTexture2D, способный сохранить содержимое заднего буфера. Это означает, что его размер и формат должны быть теми же самыми, как размером и формат заднего буфера. Добавьте этот код к своему методу LoadContent, чтобы создать объект ResolveTexture2D:
PresentationParameters pp = device.PresentationParameters;
resolveTexture = new ResolveTexture2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format);
Первая строка получает текущие PresentationParameters, содержащие все подробности о текущей конфигурации графического устройства. Они включают текущую ширину, высоту, и формат данных текущего заднего буфера. Вторая строка создает объект ResolveTexture2D на основании этих параметров настройки.
Создав объект ResolveTexture2D, вы можете использовать его, чтобы получить текущее содержимое заднего буфера. В вашем методе Draw, в точке, где вы хотите фиксировать содержимое заднего буфера, добавте эти строки:
device.ResolveBackBuffer(resolveTexture); resolveTexture.SaveCoutput.bmp", ImageFileFormat.Bmp);
Это разместит текущее содержимое заднего буфера в вашей переменной resolveTexture. Поскольку ResolveTexture2D наследуется от класса Texture2D, Вы можете использовать ее в любом случае, где вы использовали бы нормальный Texture2D. В этом примере текстура сохраняется в файл.
■Предупреждение. Из-за аппаратных особенностей, после вызова метода device. ResolveBackBuffer, содержимое заднего буфера сбрасывается!
Использование пользователькой цели рендеринга с размером, равным экрану
Вместо того, чтобы рендерить в задний буфер, вы можете определить пользовательский RenderTarget2D и активизировать его перед визуализацией. Таким образом, ничто не будет рисоваться на экране, в то время как активной является пользовательская цель рендеринга. Цель рендеринга (RenderTarget) это часть памяти на видеокарте, которая может использоваться для вывода в нее изображения. После того, как все визуализировано, вы можете копировать содержимое RenderTarget в текстуру.
Добавьте переменную, которая будет хранить RenderTarget:
RenderTarget2D renderTarget;
и создайте ее экземпляр в методе LoadContent:
PresentationParameters pp = device.PresentationParameters;
renderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format);
Вы определяете ширину и высоту вашего RenderTarget, указываете, сколько уровней mipmap он должен иметь (см. примечание относительно mipmaps в разделе 3-6), и установите формат данных. В этом примере вы используете одинаковые значения с текущим задним буфером.
■ Примечание: Как всегда, mipmapping (см. примечание в предыдущем рецепте) может быть применен, только если ширина/высота вашей текстуры кратна степени 2. Так, если вы получаете ошибку, когда определяете любое другое число mip-уровней более 1, это может быть причиной.
Как только вы инициализировали свою цель рендеринга, вы можете установить ее как активную цель рендеринга в вашем методе Draw, используя следующую строку. Все что вывизуализируете после этой строки, будет выводится на вашу пользовательскую цель рендеринга:
device.SetRenderTarget(0, renderTarget);
См. раздел 6-10 для объяснения первого параметра этого метода. Вы должны поместить эту строку перед выполнением очистки в вашем методе Draw, потому что содержимое вашего RenderTarget должно быть очищено прежде, чем вы выведите что-нибудь на него. Затем, вы рендерите целую сцену, и после того, как рендеринг закончен, вы хотите скопировать содержимое RenderTarget в текстуру. Прежде, чем вы сможете сделать это, вы должны активировать другую цель рендеринга. В этом коде вы устанавливаете значение по умолчанию, представляя задний буфер (содержимое которого будет выводиться на экран) как активную цель рендеринга, определяя второй параметр как null:
device.SetRenderTarget(o, null);
Texture2D resolvedTexture = renderTarget.GetTexture();
Так как вы активизировали задний буфер, все, что вы рендерите, с этого времени будет выводиться на экран, как обычно. Кроме того, последняя строка загружает содержимое, которое вы визуализировали в пользовательский RenderTarget в переменную resolvedTexture. Удостоверьтесь, что вы делаете все в этом порядке:
- Во-первых, активизируете свой пользовательский RenderTarget.
- Очищаете пользовательскую цель рендеринга (если нужно).
- Визуализируете все, что вам нужно.
- Дезактивируйте пользовательскую цель рендеринга, активизируя другую цель рендеринга.
- Размещаете содержимое вашего RenderTarget в текстуру.
Установка пользовательской цели рендеринга, не равной экрану
Вы можете также хотеть рисовать в RenderTarget, у которой иной размер, чем у экрана. Это часто может быть полезно, например, для промежуточных шагов во время эффектов постобработки. Шаги, например, размывание, интенсивность, и обнаружение краев можно делать на меньшем изображении, уменьшая работу, которую должна сделать видеокарта.
Если отношение между шириной и высотой - то же самое что и у окна (другими словами, как у заднего буфера), это не проблема.
Если вы будете хотеть поставить цель рендеринга с другим отношением ширины/высоты, чем ваше окно, то ваша матрица проекции для окна не будет больше допустима для вашего RenderTarget, потому что он был создан с использованием отношения ширины/высоты вашего экрана (см. раздел 2-1). Это означает, что вы должны будете определить новую матрицу проекции, которая соответствует вашей пользовательской цели рендеринга:
PresentationParameters pp = device.PresentationParameters;
int width = pp.BackBufferWidth / 2;
int height = pp.BackBufferHeight / 4;
renderTarget = new RenderTarget2D(device, width, height, 1, device.DisplayMode.Format);
rendertargetProjectionMatrix = Matrix.CreatePerspectiveField0fview(MathHelper.Pi0ver4, (float)width / (float)height, 0.5f, l00.0f);
Сначала вы определяете разрешение, которое должна иметь цель рендеринга и создаете соответствующую цели рендеринга матрицу проекции. Ширина вашей новой цели рендеринга будет половиной ширины вашего окна, в то время как ее высота будет четвертью высоты окна. Теперь всякий раз, когда вы передаете сцену в новый RenderTarget, вы должны указывать, что хотите использовать матрицу проекции вашего RenderTarget вместо матрицы проекции, соответствующей окну.
Код
Инициализируйте объект RenderTarget2D, и установите его разрешение, которое вам необходимо. Обратите внимание, что если вы сохраняете ее содержимое в текстуру, размер текстуры будет тем же самым что и у RenderTarget.
protected override void LoadContent()
{
device = graphics.GraphicsDevice;
basicEffect = new BasicEffect(device, null);
cCross = new CoordCross(device);
SpriteBatch = new SpriteBatch(device);
presentationParameters pp = device.PresentationParameters;
int width = pp.BackBufferWidth / 2;
int height = pp.BackBufferHeight / 4;
renderTarget = new RenderTarget2D(device, width, height, 1, device.DisplayMode.Format);
rendertargetProjectionMatrix = Matrix.CreatePerspectiveField0fview(MathHelper.Pi0ver4, (float)width / (float)height, 0.5f, l00.0f);
}
В методе Draw вы активизируете свою пользовательскую цель рендеринга, очищаете ее, рендерите в нее, дезактивируете, и сохраняете содержимое в текстуру. Важно отметить то, что вы используете свой rendertargetProjectionMatrix, чтобы рисовать трехмерные объекты в вашу пользовательскую цель рендеринга (показано в следующем коде). Опционально, эта текстура отображается на экран, с использованием SpriteBatch:
protected override void Draw(GameTime gameTime)
{
device.SetRenderTarget(0, renderTarget);
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.CornflowerBlue, 1,0);
cCross.Draw(fpsCam.ViewMatrix, rendertargetProjectionMatrix); device.SetRenderTarget(0, null);
Texture2D resolvedTexture = renderTarget.GetTexture();
graphics.GraphicsDevice.Clear(Color.Tomato); spriteBatch.Begin();
spriteBatch.Draw(resolvedTexture, new Vector2(100, 100), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
Перевод раздела 3-8 из книги "XNA 3.0 Game Programming Recipes: A Problem-Solution Approach". Остальные переводы смотрите на сайте www.x-graph.ru
|