Отладка приложений с использованием примитивов в XNA Framework 4.0.

Отладка приложений с использованием примитивов в XNA Framework 4.0.

Почти всегда возникает задача отладки приложений. Безусловно, XNA приложения не являются исключением.

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

Рассмотрим следуюший пример: предполжим, что в нашем приложении нужно обрабатывать пересечения трехмерных объектов, на самом деле, вы будете делать это практически в каждом приложении.

Итак, мы разработали код, который проверяет пересечение объектов. Скорее всего существование пересечение определяется на основании ограничивающих объемов для объеков (BoundingBox, BoundingShpere, BoundingFrustum). Но вот оказывается, что в каких-то случаях пересечения не находятся, а в других случаях находятся несуществующие пересечения.

Такие ошибки обычно крайне трудно найти (если, конечно, это не какие-нибудь очевидные ошибки). Для того, чтобы проще было определить «корень всех зол» нужно рисовать ограничивающие объемы.

Как я уже не раз замечал, в XNA Framework не существует классов, описывающих трехмерные графические примитивы (такие как сферы, параллелепипеды и т.д.). Если быть более точным, то раньше я приводил примеры использования дополнительной библиотеки графических примитивов с сайта creators.xna.com, но в данном случае эта библиотека может быть не совсем уместна.

В этот раз мы рассмотрим новую библиотеку, поставляемую Microsoft через сайт create.msdn.com (новое имя creators.xna.com), которая создана специально для помощи в отладке сценариев, подобных описанному ранее.

Библиотека может быть скачана по ссылке (существуют версии проекта для Windows, Xbox 360, Windows Phone 7):

http://create.msdn.com/education/catalog/sample/shape_rendering

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

Самое время скачать архив и открыть проект в Visual Studio 2010.

Запустим проект и увидим следующую картинку:

Конечно, видно, что нарисованные примитивы в принципе не являются правильными. Например, сфера обычно создается совершенно иным способом.

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

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

Итак, на рисунке можно различить отдельные примитивы следующих видов:

  • Линия
  • Треугольник
  • Сфера
  • Параллелепипед (в данном случае куб)
  • Усеченная пирамида

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

Линия – линия удобно использовать, например, при анализе траекторий полета снарядов в играх, особенно если сами снаряды являются невидимыми. По всей видимости, линии предполагалось использовать именно в этих целях. Еще одно подтверждение этого факта мы увидим дальше (имеется в виду «время жизни» примитива).

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

Сфера – чаще всего удобно на первом этапе алгоритма нахождения сталкновений проверять именно пересечение ограничивающих сфер. Почему? Дело в том, что ограничивающую сферу очень легко построить по любой модели (есть встроенные методы для этого), в то же время сфера достаточно хорошо описаывает практически любой объект. Более того, проверка пересечения сфер осуществляется за одну операцию, в то время как нахождение пересечения параллелепипедов является в общем случае нетривиальной задачей (нужно ведь учитывать ориентацию параллелепипедов)

Параллелепипед – если же сфера не достаточно хорошо описывает объект (например, длинное, тонкое ружье очень нежелательно описывать сферой, поскольку в нее попадет слишком много пространства, которое не относится к этому ружью), то нужно использовать параллелепипеды. Как было отмечено ранее, нахождение пересечения переллелепипедов является в общем случае нетривиальной задачей. К сожалению, на сегодняшний день XNA Framework и рассматриваемая библиотека не поддерживают работу с ориентированными параллелепипедами… почему так сделано, конечно, не очень понятно.

Усеченная пирамида – наиболее очевидное применение – это рисование области обзора камеры или просто некоторой области обзора. Например, представим себе, что в нашей игре противник может напасть на игрока только тогда, когда тот попадет в его зону видимости. Как раз такую зону видимости можно легко нарисовать.

Теперь перейдем к исходному коду. Рассмотрим сначала ShapeRenderingSampleGame.cs.

#region File Description//-----------------------------------------------------------------------------// ShapeRenderingSampleGame.cs//// Microsoft XNA Community Game Platform// Copyright (C) Microsoft Corporation. All rights reserved.//-----------------------------------------------------------------------------#endregion

using System;using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Graphics;using Microsoft.Xna.Framework.Input;

namespace ShapeRenderingSample{    /// <summary>    /// This is the main type for your game    /// </summary>    public class ShapeRenderingSampleGame : Game    {        GraphicsDeviceManager graphics;        SpriteBatch spriteBatch;

        // The shapes that we'll be drawing        BoundingBox box;        BoundingFrustum frustum;        BoundingSphere sphere;

        public ShapeRenderingSampleGame()        {            graphics = new GraphicsDeviceManager(this);            Content.RootDirectory = "Content";

#if WINDOWS_PHONE            TargetElapsedTime = TimeSpan.FromTicks(333333);            graphics.IsFullScreen = true;#endif        }

        /// <summary>        /// LoadContent will be called once per game and is the place to load        /// all of your content.        /// </summary>        protected override void LoadContent()        {            // Create a new SpriteBatch, which can be used to draw textures.            spriteBatch = new SpriteBatch(GraphicsDevice);

            // Create a box that is centered on the origin and extends from (-3, -3, -3) to (3, 3, 3)            box = new BoundingBox(new Vector3(-3f), new Vector3(3f));

            // Create our frustum to simulate a camera sitting at the origin, looking down the X axis, with a 16x9            // aspect ratio, a near plane of 1, and a far plane of 5            Matrix frustumView = Matrix.CreateLookAt(Vector3.Zero, Vector3.UnitX, Vector3.Up);            Matrix frustumProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 16f / 9f, 1f, 5f);            frustum = new BoundingFrustum(frustumView * frustumProjection);

            // Create a sphere that is centered on the origin and has a radius of 3            sphere = new BoundingSphere(Vector3.Zero, 3f);

            // Initialize our renderer            DebugShapeRenderer.Initialize(GraphicsDevice);        }

        /// <summary>        /// Allows the game to run logic such as updating the world,        /// checking for collisions, gathering input, and playing audio.        /// </summary>        /// <param name="gameTime">Provides a snapshot of timing values.</param>        protected override void Update(GameTime gameTime)        {            // Allow the game to exit            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||                Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Escape))                this.Exit();

            base.Update(gameTime);        }

        /// <summary>        /// This is called when the game should draw itself.        /// </summary>        /// <param name="gameTime">Provides a snapshot of timing values.</param>        protected override void Draw(GameTime gameTime)        {            GraphicsDevice.Clear(Color.CornflowerBlue);

            // Figure out our camera location. We spin around the origin based on time.            float angle = (float)gameTime.TotalGameTime.TotalSeconds;            Vector3 eye = new Vector3((float)Math.Cos(angle * .5f), 0f, (float)Math.Sin(angle * .5f)) * 12f;            eye.Y = 5f;

            // Construct our view and projection matrices            Matrix view = Matrix.CreateLookAt(eye, Vector3.Zero, Vector3.Up);            Matrix projection = Matrix.CreatePerspectiveFieldOfView(                MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, .1f, 1000f);

            // Add our shapes to be rendered            DebugShapeRenderer.AddBoundingBox(box, Color.Yellow);            DebugShapeRenderer.AddBoundingFrustum(frustum, Color.Green);            DebugShapeRenderer.AddBoundingSphere(sphere, Color.Red);

            // Also add a triangle and a line            DebugShapeRenderer.AddTriangle(new Vector3(-1f, 0f, 0f), new Vector3(1f, 0f, 0f), new Vector3(0f, 2f, 0f), Color.Purple);            DebugShapeRenderer.AddLine(new Vector3(0f, 0f, 0f), new Vector3(3f, 3f, 3f), Color.Brown);

            // Render our shapes now            DebugShapeRenderer.Draw(gameTime, view, projection);

            base.Draw(gameTime);        }    }}

 

Рассмотрим наиболее интересные моменты:

// The shapes that we’ll be drawing
BoundingBox box;
BoundingFrustum frustum;
BoundingSphere sphere;

protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);

// Create a box that is centered on the origin and extends from (-3, -3, -3) to (3, 3, 3)
box = new BoundingBox(new Vector3(-3f), new Vector3(3f));

// Create our frustum to simulate a camera sitting at the origin, looking down the X axis, with a 16×9
// aspect ratio, a near plane of 1, and a far plane of 5
Matrix frustumView = Matrix.CreateLookAt(Vector3.Zero, Vector3.UnitX, Vector3.Up);
Matrix frustumProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 16f / 9f, 1f, 5f);
frustum = new BoundingFrustum(frustumView * frustumProjection);

// Create a sphere that is centered on the origin and has a radius of 3
sphere = new BoundingSphere(Vector3.Zero, 3f);

// Initialize our renderer
DebugShapeRenderer.Initialize(GraphicsDevice);
}

Этот код создаем ограничивающие параллелепипед, усеченную пирамиду и сферу. Параллелепипед задается по двум точкам, лежащим на главной диагонали.

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

Сфера задается позицией центра и радиусом.

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

/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name=»gameTime»>Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);

// Figure out our camera location. We spin around the origin based on time.
float angle = (float)gameTime.TotalGameTime.TotalSeconds;
Vector3 eye = new Vector3((float)Math.Cos(angle * .5f), 0f, (float)Math.Sin(angle * .5f)) * 12f;
eye.Y = 5f;

// Construct our view and projection matrices
Matrix view = Matrix.CreateLookAt(eye, Vector3.Zero, Vector3.Up);
Matrix projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, .1f, 1000f);

// Add our shapes to be rendered
DebugShapeRenderer.AddBoundingBox(box, Color.Yellow);
DebugShapeRenderer.AddBoundingFrustum(frustum, Color.Green);
DebugShapeRenderer.AddBoundingSphere(sphere, Color.Red);

// Also add a triangle and a line
DebugShapeRenderer.AddTriangle(new Vector3(-1f, 0f, 0f), new Vector3(1f, 0f, 0f), new Vector3(0f, 2f, 0f), Color.Purple);
DebugShapeRenderer.AddLine(new Vector3(0f, 0f, 0f), new Vector3(3f, 3f, 3f), Color.Brown);

// Render our shapes now
DebugShapeRenderer.Draw(gameTime, view, projection);

base.Draw(gameTime);
}

Метод Draw использует статические методы класса DebugShapeRenderer для добавления примитивов в список рисования. Обратите внимание на то, что примитивы в данном случае нужно добавлять в список рисования при рисовании каждого кадра.

Кроме уже созданных ограничивающих объемов в список также помещаются треугольник и линия.

Рисование примитивов идет в пакетном режиме и запускается вызовом метода DebugShapeRenderer.Draw. Пожалуй, не очень хорошо, что в него нельзя передать мировую матрицу, хотя это не сложно исправить самостоятельно.

Запись опубликована в рубрике Компьютерная графика с метками , , , , . Добавьте в закладки постоянную ссылку.

3 отзыва на “Отладка приложений с использованием примитивов в XNA Framework 4.0.

  1. Уведомление: Некоторые интересные ссылки (Март) | Александр Богатырев: сфера

  2. Уведомление: ??????????? ????????? ?? ????????? ? ???????? Microsoft ?? ??????? ????? – ?????? 2011 - MSDN Blogs

  3. Уведомление: Технические материалы по продуктам и решениям Microsoft на русском языке – апрель 2011 | Alexander Knyazev: блог

Оставьте комментарий