Основы разработки компьютерных игр в XNA Game Studio

Вывод трехмерных объектов на экран

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

Примеры к лабораторной работе

Задачи работы

  • Научиться рисовать трехмерные объекты средствами XNA
  • Научиться выводить трехмерные модели
  • Научиться текстурировать трехмерные модели
  • Рисование трехмерных объектов средствами XNA

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

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

    В этом примере мы выведем на экран следующие объекты:

  • Треугольник
  • Два прямоугольника
  • Прямую линию
  • 1000 точек со случайными координатами
  • 200 треугольников со случайными координатами, одна из вершин каждого из которых расположена в одной точке.
  • В листинге 19.1. вы можете найти код класса Game1 проекта P14_1. Код подробно прокомментирован.

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

    Установить мировую, проекционную и видовую матрицы.

    Создать и настроить объект типа BasicEffect для вывода изображений.

    Создать наборы вершин, которые мы будем использовать при выводе

    Создать и заполнить вершинный буфер, который нужно будет загрузить в один из элементов коллекции Vertices объекта, используемого для вывода изображений

    Вывести изображение из буфера, при необходимости вывести изображения, сгенерированные на основе массивов вершин, не внесенных в вершинный буфер.

    Массив вершин может быть интерпретирован по-разному. Например, при интерпретации его в качестве PointList массив выводится в виде списка точек, при интерпретации в качестве TriangleList – как набор треугольников, при интерпретации в качестве TriangleFan – как "веер" из треугольников, одна из вершин которых совпадает, при интерпретации как LineList – в качестве набора линий.

    При выводе изображения в методе Draw необходимо очищать экран от предыдущего вывода – иначе он покроется копиями изображений.

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

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P14_1
    {
        
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            //Вывод изображений
            BasicEffect basicEffect;
            //Мировая матрица
            Matrix worldMatrix;
            //Матрица вида
            Matrix viewMatrix;
            //Проекционная матрица
            Matrix projectionMatrix;
            //Вершинный буфер
            VertexBuffer vertBuffer;
            //Массивы для хранения координат вершин
            //Которые используются для вывода изображения
            //без использования вершинного буфера
            VertexPositionColor[] vert1;
            VertexPositionColor[] vert2;
            VertexPositionColor[] vert3;
            VertexPositionColor[] vert4;
            VertexPositionColor[] vert5;
            //Переменная для хранения текущего значения
            //поворота мировой матрицы
            float wM = 0;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
            }
    
            protected override void Initialize()
            {
    
                base.Initialize();
            }
    
            protected override void LoadContent()
            {
               //Настройка матриц
               SetMatrix();
               //Настройка эффектов вывода
               TuneUpEff();
               //Создание объектов для вывода
               CreateFigures();
            }
            void SetMatrix()
            {
                //мировая матрица, содержащая 1 по диагонали
                //она не влияет на состояние объекта
                worldMatrix = Matrix.Identity;
    
                //матрица вида
                //При ее создании задаем следующие параметры
                //1 - положение камеры
                //2 - направление камеры
                //3 - что считать "верхом" для камеры
                viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 10.0f), Vector3.Zero, Vector3.Up);
                //Находим соотношение сторон пикселей для корректного вывода
                //изображений на экран
                float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width / (float)graphics.GraphicsDevice.Viewport.Height;
    
                //Матрица проекции
                //При ее создании задаем следующие параметры:
                //1 - угол зрения в радианах
                //Соотношение сторон пикселей экрана
                //Ближний план пространства
                //Дальний план пространства
                projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), aspectRatio, 1.0f, 19.0f);
            }
            void TuneUpEff()
            {
                //Создаем экземпляр класса BasicEffect
                //Он используется для вывода изображений - это напоминает
                //SpriteBatch при работе с двумерной графикой
                basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
                //Настраиваем параметры basicEffect
                //Освещение
                basicEffect.LightingEnabled = true;
                basicEffect.EnableDefaultLighting();
               
                //Установка матриц
                basicEffect.World = worldMatrix;
                basicEffect.View = viewMatrix;
                basicEffect.Projection = projectionMatrix;
            }
            void CreateFigures()
            {
                //Массив вершин для построения треугольника
                VertexPositionColor [] vert = new VertexPositionColor[3];
                //Массив для построения линий, расположенных в виде прямоугольника
                vert1 = new VertexPositionColor[5];
                //Массив для построения второго прямоугольника, расположенного выше, чем
                //первый
                vert2 = new VertexPositionColor[5];
                //Массив для построения линии
                vert3 = new VertexPositionColor[2];
                //Массив для построения точек со случайными координатами
                vert4 = new VertexPositionColor[1000];
                //Массив для построения группы треугольников со случайными координатами
                vert5 = new VertexPositionColor[200];
                //Генераратор случайных чисел
                Random rnd = new Random();
                //Задаем параметры для построения треугольника
                vert[0] = new VertexPositionColor(new Vector3(1.0f, 2.0f, -5.0f), Color.White);
                vert[1] = new VertexPositionColor(new Vector3(2.0f, 0.0f, -5.0f), Color.White);
                vert[2] = new VertexPositionColor(new Vector3(1.0f, -2.0f, -5.0f), Color.White);
                //Задаем параметры для построения прямоугольника
                //его центр расположен в начале координат
                vert1[0] = new VertexPositionColor(new Vector3(-1.0f, -1.0f, 0.0f), Color.White);
                vert1[1] = new VertexPositionColor(new Vector3(-1.0f, 1.0f, 0.0f), Color.White);
                vert1[2] = new VertexPositionColor(new Vector3(1.0f, 1.0f, 0.0f), Color.White);
                vert1[3] = new VertexPositionColor(new Vector3(1.0f, -1.0f, 0.0f), Color.White);
                vert1[4] = new VertexPositionColor(new Vector3(-1.0f, -1.0f, 0.0f), Color.White);
                //Задаем параметры для второго прямоугольника - он перпендикулярен первому
                //и смещен вверх
                vert2[0] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 1.0f), Color.White);
                vert2[1] = new VertexPositionColor(new Vector3(0.0f, 0.0f, -1.0f), Color.White);
                vert2[2] = new VertexPositionColor(new Vector3(0.0f, 2.0f, -1.0f), Color.White);
                vert2[3] = new VertexPositionColor(new Vector3(0.0f, 2.0f, 1.0f), Color.White);
                vert2[4] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 1.0f), Color.White);
                //Параметры для построения линии
                vert3[0] = new VertexPositionColor(new Vector3(2.0f, 2.0f, 2.0f), Color.White);
                vert3[1] = new VertexPositionColor(new Vector3(-2.0f, -2.0f, -2.0f), Color.White);
                //Генерируем случайные координаты для построения точек
                for (int i = 0; i < 1000; i++)
                {
                    //Диапазон координат от -3 до 3
                    float x = (float)(rnd.NextDouble() - rnd.NextDouble()) * 3;
                    float y = (float)(rnd.NextDouble() - rnd.NextDouble()) * 3;
                    float z = (float)(rnd.NextDouble() - rnd.NextDouble()) * 3;
    
                    vert4[i] = new VertexPositionColor(new Vector3(x, y, z), Color.White);
                }
                //Генерируем случайные координаты для построения треугольников
                for (int i = 0; i < 200; i++)
                {
                    //Координаты изменяются в пределах от -1 до 1
                    
                    float x = (float)(rnd.NextDouble() - rnd.NextDouble());
                    float y = (float)(rnd.NextDouble() - rnd.NextDouble());
                    //группа треугольников сдвинута на 3 по оси Z
                    float z = (float)(rnd.NextDouble() - rnd.NextDouble())-3;
                    vert5[i] = new VertexPositionColor(new Vector3(x, y, z), Color.White);
                }
    
                //Создаем вершинный буфер
                //При создании указываем графическое устройство, размер буфера и способ работы с буфером
                vertBuffer = new VertexBuffer(graphics.GraphicsDevice, 3 * VertexPositionColor.SizeInBytes,BufferUsage.WriteOnly);
                //Загружаем в буфер массив вершин vert
                vertBuffer.SetData<VertexPositionColor>(vert);
            }
    
            protected override void UnloadContent()
            {
                if (vertBuffer != null)
                {
                    vertBuffer.Dispose();
                    vertBuffer = null;
                }
    
                if (basicEffect != null)
                {
                    basicEffect.Dispose();
                    basicEffect = null;
                }
            }
    
            
            protected override void Update(GameTime gameTime)
            {
                //Уменьшаем на 1 градус значение поворота матрицы
                wM = wM - 1f;
                //Модифицируем мировую матрицу
                worldMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(wM));
                //Меняем мировую матрицу
                basicEffect.World = worldMatrix;
                base.Update(gameTime);
            }
    
            protected override void Draw(GameTime gameTime)
            {
               //Очищаем окно вывода
               graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
               //Устанавиливаем объект VertexDeclaration - он используется для вывода изображения
               graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration
        (graphics.GraphicsDevice, VertexPositionColor.VertexElements);
               //Устанавливаем в качестве источника для вывода ранее созданный вершинный буфер
               graphics.GraphicsDevice.Vertices[0].SetSource(vertBuffer, 0, VertexPositionColor.SizeInBytes);
               //Начинаем вывод изображения
                basicEffect.Begin();
                //Для каждого прохода эффекта в коллекции примененных эффектов 
                //выведем изображение. В нашем случае вывод осуществляется в 1 проход
                foreach (EffectPass CurrentPass in basicEffect.CurrentTechnique.Passes)
                {
                    //Начинаем вывод для текущего прохода
                    CurrentPass.Begin();
                        //Выводим треугольник, пользуясь параметрами созданного вершинного буфера
                        //Тип графического примитива - список треугольников, отсчет от 0 элемента буфера
                        //Количество треугольников - 1. По умолчанию задняя часть объекта не видна
                        graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
                        //Выводим набор линий для рисования первого прямоугольника, используя 
                        //массив вершин vert1
                        graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, vert1, 0, 4);
                        //Выводим второй прямоугольник
                        graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, vert2, 0, 4);
                        //Выводим линию - она проходит через начало координат
                        graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, vert3, 0, 1);
                        //Выводим группу точек
                        graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList, vert4, 0, 1000);
                        //Выводим набор треугольников, одна из вершин которых совпадает с начальной точкой
                        //Количество треугольников на 2 меньше, чем количество вершин, заданных в массиве Vert5
                        graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleFan, vert5, 0, 198);
                        //Окончание текущего прохода
                    CurrentPass.End();
                }
                //Окончание вывода изображений
                basicEffect.End();
                base.Draw(gameTime);
            }
        }
    }

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

    (рис 19.1) Окно проекта P14_1

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

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

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

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

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

    Создадим новый проект – P14_2 для демонстрации техники вывода трехмерной модели. На рис. 19.2. вы можете видеть окно Project Explorer этого проекта и окно Properties для текстуры, предназначенной для наложения на модель. Трехмерные модели, которые предусматривают использование текстур, содержат в своих файлах указание на то, каким образом накладывать текстуру на объект, а так же – на файл текстуры. В нашем случае адрес файла текстуры задан в относительном выражении – то есть система будет искать этот файл в той же папке, в которой расположен файл трехмерной модели. Текстура подвергается преобразованиям в соответствии с командами, записанными в файле трехмерной модели, поэтому мы отключили параметр Build Action в свойствах текстуры.

    (рис 19.2) Окна Solution Explorer и Properties

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

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P14_2
    {
        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";
            }
    
            protected override void Initialize()
            {
                // TODO: Add your initialization logic here
    
                base.Initialize();
            }
    
            protected override void LoadContent()
            {
                //загрузка модели
                myModel = Content.Load<Model>("RoundShield");
                //Установка соотношения сторон окна вывода
                aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
                (float)graphics.GraphicsDevice.Viewport.Height;
    
            }
    
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
    
            protected override void Update(GameTime gameTime)
            {
                //увеличим угол поворота на 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.LightingEnabled = true;
                        effect.EnableDefaultLighting();
                       //установить матрицы
                        effect.World = WorldMatrix;
                        effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 119.0f), Vector3.Zero, Vector3.Up);
                        effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                             aspectRatio, 1.0f, 1000.0f);
                    }
                }
                
                base.Update(gameTime);
            }
    
            protected override void Draw(GameTime gameTime)
            {
                graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
                // Вывод модели
                foreach (ModelMesh mesh in myModel.Meshes)
                {
                    mesh.Draw();
                }
                base.Draw(gameTime);
            }
        }
    }

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

    (рис 19.3) Окно проекта P14_2
    Вернуться к учебному плану