Разработка компьютерных игр для Windows Phone 7 с использованием технологий Silverlight и XNA

Основы трехмерной графики в XNA

Показывать лекцию целиком

Цель работы: Научиться выводить трёхмерные объекты на игровой экран

23.1. Рисование трехмерных объектов средствами XNA

Рассмотрим особенности рисования трехмерных объектов средствами XNA на примере проекта P16_1. Здесь мы рассматриваем использование вершинного буфера для вывода куба. Пример подготовлен на основе кода, доступного в рамках Microsoft Permissive License (Ms-PL).

В листинге 23.1 вы можете найти код класса Game1 проекта P14_1. Код подробно прокомментирован.

Отметим, что для вывода изображения нам необходимо выполнить следующие шаги:

  • Установить мировую, проекционную и видовую матрицы.
  • Создать и настроить объект типа BasicEffect для вывода изображений.
  • Создать наборы вершин (в нашем случае они представлены данными о координате, нормали и текстуре), которые мы будем использовать при выводе.
  • Создать и заполнить вершинный буфер, установить его в объект GraphicDevice для вывода изображения.
  • Вывести изображение из буфера, пользуясь инструментами объекта типа BasicEffect, который, фактически, является одной из доступных в XNA шейдерных программ.
  • При выводе изображения в методе Draw необходимо очищать экран от предыдущего вывода – иначе он покроется копиями изображений.

    Мы используем мировую матрицу для вращения выведенной сцены при каждом проходе цикла Update.

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    
    namespace P16_1
    {
        /// <summary>
        /// This is the main type for your game
        /// </summary>
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            //Мировая матрица
            Matrix worldMatrix;
            //Матрица вида
            Matrix viewMatrix;
            //Проекционная матрица
            Matrix projectionMatrix;
            //Информация по вершинам объекта
            VertexPositionNormalTexture[] cubeVertices;
            //Объявление вершин, используется для вывода изображения
            VertexDeclaration vertexDeclaration;
            //Вершинный буфер
            VertexBuffer vertexBuffer;
            //Объект для вывода изображений
            BasicEffect basicEffect;
            //Количество вершин
            const int number_of_vertices = 36;
    
            GraphicsDeviceManager graphics;
            //Переменные для хранения текущего значения
            //поворота мировой матрицы
            float wMX = 0;
            float wMY = 0;
            float wMZ = 0;
            //Управление  матрицей вида
            float distanceView = 5;
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
                //Вывод на полный экран
                graphics.IsFullScreen = true;
            }
            //Создание вертексного (вершинного) буфера
            private void CreateVertexBuffer()
            {
                //Задаёт объявление вершин
                //Оно содержит новый массив типа VertexElement, содержащий сведения
                vertexDeclaration = new VertexDeclaration(new VertexElement[]
                    {
                        
                        //О координате
                        new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                        //О нормали
                        new VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
                        //О Координате текстуры
                        new VertexElement(24, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
                    }
                );
                //Создаём вертексный буфер, используя информацию о 
                //графическом устройстве
                //Структуре описания вершин
                //Количестве вершин
                vertexBuffer = new VertexBuffer(
                    graphics.GraphicsDevice,
                    vertexDeclaration,
                    number_of_vertices,
                    BufferUsage.None
                    );
    
                //Новый массив вершин куба
                cubeVertices = new VertexPositionNormalTexture[number_of_vertices];
                //Создаём куб
                InitializeCube();
    
                //Устанавливаем данные для вертексного буфера
                vertexBuffer.SetData<VertexPositionNormalTexture>(cubeVertices);
                //Подключаем вертексный буфер к устройству
                graphics.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            }
    
    
            protected override void Initialize()
            {
                
                CreateVertexBuffer();
    
                base.Initialize();
            }
    
            /// <summary>
            /// LoadContent will be called once per game and is the place to load
            /// all of your content.
            /// </summary>
            protected override void LoadContent()
            {
                InitializeTransform();
                InitializeEffect();
            }
    
            /// <summary>
            /// Инициализирует матрицы, которые можно использовать при 
            /// </summary>
            private void InitializeTransform()
            {
                //мировая матрица, содержащая 1 по диагонали
                //она не влияет на состояние объекта
                worldMatrix = Matrix.Identity;
    
                //матрица вида
                //При ее создании задаем следующие параметры
                //1 - положение камеры
                //2 - направление камеры
                //3 - что считать "верхом" для камеры
                viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, distanceView), Vector3.Zero, Vector3.Up);
    
    
                //Матрица проекции
                //При ее создании задаем следующие параметры:
                //1 - угол зрения в радианах
                //Соотношение сторон пикселей экрана
                //Ближний план пространства
                //Дальний план пространства
    
                projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
                   MathHelper.ToRadians(45),
                   (float)GraphicsDevice.Viewport.Width /
                   (float)GraphicsDevice.Viewport.Height,
                   1.0f, 30.0f);
            }
    
            /// <summary>
            /// Инициализация базовых эффектов (настроек параметров и способов обработки)
            /// используемых для работы с трехмерной моделью
            /// </summary>
            private void InitializeEffect()
            {
                //Создание объекта для вывода изображений
                basicEffect = new BasicEffect(graphics.GraphicsDevice);
                //Установка матриц
                basicEffect.World = worldMatrix;
                basicEffect.View = viewMatrix;
                basicEffect.Projection = projectionMatrix;
    
                // Цвета различных видов освещения
                basicEffect.AmbientLightColor = new Vector3(0.1f, 0.1f, 0.1f);
                basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
                basicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
                basicEffect.SpecularPower = 5.0f;
                basicEffect.Alpha = 1.0f;
                //Включим освещение
                basicEffect.LightingEnabled = true;
                if (basicEffect.LightingEnabled)
                {
                    basicEffect.DirectionalLight0.Enabled = true; // активируем каждый источник света отдельно
                    if (basicEffect.DirectionalLight0.Enabled)
                    {
                        // Направление по Х
                        basicEffect.DirectionalLight0.DiffuseColor = new Vector3(1, 0, 0); // диапазон от 0 до 1
                        basicEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-1, 0, 0));
                        // Направление от источника света к началу координат сцены
                        basicEffect.DirectionalLight0.SpecularColor = Vector3.One;
                    }
    
                    basicEffect.DirectionalLight1.Enabled = true;
                    if (basicEffect.DirectionalLight1.Enabled)
                    {
                        // Направление по У
                        basicEffect.DirectionalLight1.DiffuseColor = new Vector3(0, 0.75f, 0);
                        basicEffect.DirectionalLight1.Direction = Vector3.Normalize(new Vector3(0, -1, 0));
                        basicEffect.DirectionalLight1.SpecularColor = Vector3.One;
                    }
    
                    basicEffect.DirectionalLight2.Enabled = true;
                    if (basicEffect.DirectionalLight2.Enabled)
                    {
                        // Направление по Z
                        basicEffect.DirectionalLight2.DiffuseColor = new Vector3(0, 0, 0.5f);
                        basicEffect.DirectionalLight2.Direction = Vector3.Normalize(new Vector3(0, 0, -1));
                        basicEffect.DirectionalLight2.SpecularColor = Vector3.One;
                    }
                }
    
            }
    
            /// <summary>
            /// Инициализирует вершины и индексы 3D-модели
            /// </summary>
            private void InitializeCube()
            {
                //Вершины
                Vector3 topLeftFront = new Vector3(-1.0f, 1.0f, 1.0f);
                Vector3 bottomLeftFront = new Vector3(-1.0f, -1.0f, 1.0f);
                Vector3 topRightFront = new Vector3(1.0f, 1.0f, 1.0f);
                Vector3 bottomRightFront = new Vector3(1.0f, -1.0f, 1.0f);
                Vector3 topLeftBack = new Vector3(-1.0f, 1.0f, -1.0f);
                Vector3 topRightBack = new Vector3(1.0f, 1.0f, -1.0f);
                Vector3 bottomLeftBack = new Vector3(-1.0f, -1.0f, -1.0f);
                Vector3 bottomRightBack = new Vector3(1.0f, -1.0f, -1.0f);
                //Текстуры
                Vector2 textureTopLeft = new Vector2(0.0f, 0.0f);
                Vector2 textureTopRight = new Vector2(1.0f, 0.0f);
                Vector2 textureBottomLeft = new Vector2(0.0f, 1.0f);
                Vector2 textureBottomRight = new Vector2(1.0f, 1.0f);
                //Нормали
                Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f);
                Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f);
                Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f);
                Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f);
                Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f);
                Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f);
    
                // Передняя стенка, 6 вершин для 2-х треугольников
                //из которых она состоит
                cubeVertices[0] =
                    new VertexPositionNormalTexture(
                    topLeftFront, frontNormal, textureTopLeft);
                cubeVertices[1] =
                    new VertexPositionNormalTexture(
                    bottomLeftFront, frontNormal, textureBottomLeft);
                cubeVertices[2] =
                    new VertexPositionNormalTexture(
                    topRightFront, frontNormal, textureTopRight);
                cubeVertices[3] =
                    new VertexPositionNormalTexture(
                    bottomLeftFront, frontNormal, textureBottomLeft);
                cubeVertices[4] =
                    new VertexPositionNormalTexture(
                    bottomRightFront, frontNormal, textureBottomRight);
                cubeVertices[5] =
                    new VertexPositionNormalTexture(
                    topRightFront, frontNormal, textureTopRight);
    
                // Задняя стенка
                cubeVertices[6] =
                    new VertexPositionNormalTexture(
                    topLeftBack, backNormal, textureTopRight);
                cubeVertices[7] =
                    new VertexPositionNormalTexture(
                    topRightBack, backNormal, textureTopLeft);
                cubeVertices[8] =
                    new VertexPositionNormalTexture(
                    bottomLeftBack, backNormal, textureBottomRight);
                cubeVertices[9] =
                    new VertexPositionNormalTexture(
                    bottomLeftBack, backNormal, textureBottomRight);
                cubeVertices[10] =
                    new VertexPositionNormalTexture(
                    topRightBack, backNormal, textureTopLeft);
                cubeVertices[11] =
                    new VertexPositionNormalTexture(
                    bottomRightBack, backNormal, textureBottomLeft);
    
                // Верхняя стенка
                cubeVertices[12] =
                    new VertexPositionNormalTexture(
                    topLeftFront, topNormal, textureBottomLeft);
                cubeVertices[13] =
                    new VertexPositionNormalTexture(
                    topRightBack, topNormal, textureTopRight);
                cubeVertices[14] =
                    new VertexPositionNormalTexture(
                    topLeftBack, topNormal, textureTopLeft);
                cubeVertices[15] =
                    new VertexPositionNormalTexture(
                    topLeftFront, topNormal, textureBottomLeft);
                cubeVertices[16] =
                    new VertexPositionNormalTexture(
                    topRightFront, topNormal, textureBottomRight);
                cubeVertices[17] =
                    new VertexPositionNormalTexture(
                    topRightBack, topNormal, textureTopRight);
    
                // Нижняя стенка
                cubeVertices[18] =
                    new VertexPositionNormalTexture(
                    bottomLeftFront, bottomNormal, textureTopLeft);
                cubeVertices[19] =
                    new VertexPositionNormalTexture(
                    bottomLeftBack, bottomNormal, textureBottomLeft);
                cubeVertices[20] =
                    new VertexPositionNormalTexture(
                    bottomRightBack, bottomNormal, textureBottomRight);
                cubeVertices[21] =
                    new VertexPositionNormalTexture(
                    bottomLeftFront, bottomNormal, textureTopLeft);
                cubeVertices[22] =
                    new VertexPositionNormalTexture(
                    bottomRightBack, bottomNormal, textureBottomRight);
                cubeVertices[23] =
                    new VertexPositionNormalTexture(
                    bottomRightFront, bottomNormal, textureTopRight);
    
                // Левая стенка
                cubeVertices[24] =
                    new VertexPositionNormalTexture(
                    topLeftFront, leftNormal, textureTopRight);
                cubeVertices[25] =
                    new VertexPositionNormalTexture(
                    bottomLeftBack, leftNormal, textureBottomLeft);
                cubeVertices[26] =
                    new VertexPositionNormalTexture(
                    bottomLeftFront, leftNormal, textureBottomRight);
                cubeVertices[27] =
                    new VertexPositionNormalTexture(
                    topLeftBack, leftNormal, textureTopLeft);
                cubeVertices[28] =
                    new VertexPositionNormalTexture(
                    bottomLeftBack, leftNormal, textureBottomLeft);
                cubeVertices[29] =
                    new VertexPositionNormalTexture(
                    topLeftFront, leftNormal, textureTopRight);
    
                // Правая стенка 
                cubeVertices[30] =
                    new VertexPositionNormalTexture(
                    topRightFront, rightNormal, textureTopLeft);
                cubeVertices[31] =
                    new VertexPositionNormalTexture(
                    bottomRightFront, rightNormal, textureBottomLeft);
                cubeVertices[32] =
                    new VertexPositionNormalTexture(
                    bottomRightBack, rightNormal, textureBottomRight);
                cubeVertices[33] =
                    new VertexPositionNormalTexture(
                    topRightBack, rightNormal, textureTopRight);
                cubeVertices[34] =
                    new VertexPositionNormalTexture(
                    topRightFront, rightNormal, textureTopLeft);
                cubeVertices[35] =
                    new VertexPositionNormalTexture(
                    bottomRightBack, rightNormal, textureBottomRight);
            }
    
            /// <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)
            {
                // Allows the game to exit
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                    this.Exit();
    
    
                //Уменьшаем градус значение поворота матрицы
                wMX = wMX - 1f;
                wMY = wMY - 2f;
                wMZ = wMZ - 3f;
                //Модифицируем мировую матрицу
                worldMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(wMX)) * Matrix.CreateRotationY(MathHelper.ToRadians(wMY)) *
                 Matrix.CreateRotationZ(MathHelper.ToRadians(wMZ));
    
                //Меняем мировую матрицу
                basicEffect.World = worldMatrix;
                
                //Если расстояние меньше 15
                if (distanceView <= 15.0f)
                {
                    //Увеличим
                    distanceView = distanceView + 0.01f;
                }
                //Модифицируем матрицу вида
                viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, distanceView), Vector3.Zero, Vector3.Up);
                //Заменим матрицу вида
                basicEffect.View = viewMatrix;
    
                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)
            {
                //Очистим экран
                graphics.GraphicsDevice.Clear(Color.SteelBlue);
                //Используется для определения того, как преобразовывать векторные данные в пиксельные
                RasterizerState rasterizerState1 = new RasterizerState();
                //Отключаем отсечени
                rasterizerState1.CullMode = CullMode.None;
                //Установим настроенное состояние прорисовки
                graphics.GraphicsDevice.RasterizerState = rasterizerState1;
                //Для каждого прохода в эффекте
                foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
                {
                    //Применить проход
                    pass.Apply();
                    //Вывести
                    graphics.GraphicsDevice.DrawPrimitives(
                        PrimitiveType.TriangleList,
                        0,
                        12
                    );
                }
    
    
                base.Draw(gameTime);
            }
        }
    }

    На рис. 23.1 вы можете видеть игровое окно

    (рис 23.1) Результаты работы игрового проекта P16_1

    23.2. Вывод трехмерных моделей

    Выводя графические примитивы, рассмотренные в предыдущем примере, довольно сложно и трудоемко создавать игровые сцены. Гораздо большими возможностями обладает подход к работе с трехмерной графикой, использующий вывод заранее подготовленных трехмерных моделей. Само по себе создание трехмерных моделей требует немало времени и искусства, для этого применяются специальные программы. В деле создания игр выделяется отдельная группа сотрудников – 3D-моделеров, которые занимаются исключительно созданием трехмерных моделей.

    Мы использовали готовую бесплатную трехмерную модель Free Lowpoly Round Shield с сайта http://www.turbosquid.com.

    Для вывода модели на экран необходимо добавить в проект соответствующие ей ресурсы, в нашем случае – модель в формате .FBX и текстуру в формате .JPG. Далее следует загрузить модель в игру, настроить её свойства и вывести на экран.

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

    Создадим новый проект – P16_2 для демонстрации техники вывода трехмерной модели. На рис. 23.2 вы можете видеть окно Обозреватель решений этого проекта и окно Свойства для текстуры, предназначенной для наложения на модель. Трехмерные модели, которые предусматривают использование текстур, содержат в своих файлах указание на то, каким образом накладывать текстуру на объект, а так же – на файл текстуры. В нашем случае адрес файла текстуры задан в относительном выражении – то есть система будет искать этот файл в той же папке, в которой расположен файл трехмерной модели. Текстура подвергается преобразованиям в соответствии с командами, записанными в файле трехмерной модели, поэтому мы отключили параметр Действия при построении в свойствах текстуры.

    (рис 23.2) Настройка свойств текстуры

    содержит код класса Game1, реализующего вывод модели на экран.

    using System;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    
    namespace P16_2
    {
        /// <summary>
        /// Это главный тип игры
        /// </summary>
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            // Модель для вывода
            Model myModel;
            //Мировая матрица
            Matrix WorldMatrix;
            //Соотношение сторон окна вывода
            float aspectRatio;
            //Угол поворота для вращения модели
            float angle;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
                graphics.IsFullScreen = true;
                // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
                TargetElapsedTime = TimeSpan.FromTicks(333333);
                // Дополнительный заряд аккумулятора заблокирован.
                InactiveSleepTime = TimeSpan.FromSeconds(1);
            }
    
            /// <summary>
            /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
            /// Здесь можно запросить нужные службы и загрузить неграфический
            /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
            /// их инициализации.
            /// </summary>
            protected override void Initialize()
            {
                // ЗАДАЧА: добавьте здесь логику инициализации
    
                base.Initialize();
            }
    
            /// <summary>
            /// LoadContent будет вызываться в игре один раз; здесь загружается
            /// весь контент.
            /// </summary>
            protected override void LoadContent()
            {
                //загрузка модели
                myModel = Content.Load<Model>("RoundShield");
                //Установка соотношения сторон окна вывода
                aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
                (float)graphics.GraphicsDevice.Viewport.Height;
    
                // ЗАДАЧА: используйте здесь this.Content для загрузки контента игры
            }
    
            /// <summary>
            /// UnloadContent будет вызываться в игре один раз; здесь выгружается
            /// весь контент.
            /// </summary>
            protected override void UnloadContent()
            {
                // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
            }
    
            /// <summary>
            /// Позволяет игре запускать логику обновления мира,
            /// проверки столкновений, получения ввода и воспроизведения звуков.
            /// </summary>
            /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
            protected override void Update(GameTime gameTime)
            {
                // Позволяет выйти из игры
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                    this.Exit();
    
                //увеличим угол поворота на 1
                angle++;
                //установим новую мировую матрицу
                //поворот по оси Y равен углу angle, поворот по X - 
                //angle, деленному на 5
                WorldMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(angle)) *
                    Matrix.CreateRotationX(MathHelper.ToRadians(angle / 5.0f));
                //Для каждой сети в модели
                foreach (ModelMesh mesh in myModel.Meshes)
                {
                    //Для каждого эффекта в сети
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        //Установить освещение
                        effect.AmbientLightColor = new Vector3(1.0f, 1.0f, 1.0f);
                        effect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
                        effect.LightingEnabled = true;
    
                        //установить матрицы
                        effect.World = WorldMatrix;
                        effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 120.0f), Vector3.Zero, Vector3.Up);
                        effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                             aspectRatio, 1.0f, 1000.0f);
                    }
                }
    
    
                base.Update(gameTime);
            }
    
            /// <summary>
            /// Вызывается, когда игра отрисовывается.
            /// </summary>
            /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
                // Вывод модели
                foreach (ModelMesh mesh in myModel.Meshes)
                {
                    mesh.Draw();
                }
                base.Draw(gameTime);
    
            }
        }
    }

    На рис. 23.3 вы можете видеть окно проекта.

    (рис 23.3) Окно проекта P16_2

    23.3. Выводы

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

    23.4. Задание

    Создайте или найдите в Интернете трехмерную модель, выведите её на экран, используя BasicEffect, освойте управление игровой сценой с помощью рассмотренных в лабораторной работе методов.

    23.5. Дополнительные материалы

    К данной лабораторной работе прилагается видеоурок

    и демонстрационный проект.

    Вернуться к учебному плану