Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Знакомство с XNA Framework и XNA Game Studio  »  XNA - Создание 3D-объектов

XNA Game Studio: Создание 3D-объектов в MSVC#2008




Здесь мы рассмотрим основные элементы трёхмерной графики и как именно XNA Framework представляет (exposes) различные типы данных и объекты, которые позволят нам с лёгкостью создавать 3D-миры.1 Мы создадим несколько приложений с 3D-демо, которые позволят наглядно увидеть изучаемый материал. Все объекты мы создадим посредством исходного кода, без применения пакетов 3D-моделирования (на первых порах). В конце статьи мы узнаем, как перемещать объекты в окне приложения.

Вершины (Vertices)

Абсолютно всё в 3D-игре представлено в виде 3D-точек (См. Рис.1). Существует два основных способа вывода 3D-объектов на экран: набросать (plot) точки самостоятельно, либо загрузить их из готового файла 3D-модели (в котором сохранены все точки, составляющие модель). В данной статье мы будем "вручную" (т.е. с помощью нашей IDE) создавать и размещать точки, называемые в 3D-программировании вершинами (vertices).
Мы определяем положение вершин в 3D-пространстве с помощью пространственных координат x, y и z.


Рис. 1 Вершины, рёбра и грани - основные элементы для создания 3D-объектов
Рис. 1 Вершины, рёбра и грани - основные элементы для создания 3D-объектов



Векторы (Vectors)

Из любой вершины можно провести вектор. И не один. XNA предлагает аж три структуры для хранения векторов:
  • Vector2 (имеет только координаты x, y);
  • Vector3 (имеет координаты x, y, z);
  • Vector4 (обычно применяется для хранения и передачи данных о цвете)
2D-векторы (Vector2) обычно применяются в двумерных играх, а также при работе с 3D-текстурами. Помимо вершин, векторы могут также хранить скорость (velocity).
Над векторами можно производить различные математические операции. В XNA встроено множество функций-помощников, выполняющие эти операции без необходимости глубокого копания в исходном коде.

Матрицы (Matrices)

В XNA матрицей нзывается таблица данных размером 4Х4. Это двумерный массив. Матрица идентичности (identity matrix), она же единичная матрица (unit matrix), схожа с числом 1. К примеру если мы перемножим любое другое число на 1, то в результате всегда получим исходное число (5 * 1 = 5).
Произведением произвольной матрицы и единичной матрицы является та же самая исходная произвольная матрица. Единичная матрица очень широко применяется как сама по себе, так и совмествно с другими матрицами. Её отдельные поля структурированы по строкам и столбцам вида 4х4. Это позволяет выполнять внутри одной матрицы множество различных преобразований (трансформаций, transformations). Для хранения матриц XNA предоставляет структуру Matrix.

Трансформации (Transformations)

Матрица данных как правило содержит в себе т.н. трансформацию. В игрокодинге существуют 3 основных вида трансформаций (преобразований):
  • Трансляция (Translation; изменение положения в пространстве);
  • Масштабирование (Scaling);
  • Вращение (Rotation).
Все они применяются для соответствующих трансформаций 3D-объектов.

Трансляция (Translation; изменение положения в пространстве)

Трансляция означает обычное перемещение объектов в 3D-пространстве. Мы транслируем объект из одной точки в другую путём корректного перемещения каждой точки объекта.

Масштабирование (Scaling)

Масштабирование объекта делает объекты больше или меньше. Это достигается за счёт изменения расстояний между точками объекта.

Вращение (Rotation)

Вращение объекта вращает объект по одной или нескольким осям. Перемещая точки объекта соответствующим образом в 3D-пространстве мы можем заставить наш объект крутиться словно волчок.

Закрыть
noteОбрати внимание

Трансляция является одной из трёх трансформаций. Наряду с масштабированием и вращением.


Трансформации (продолжение)

К объекту в данный момент времени может быть применена одна или несколько трансформаций одновременно. К примеру нам необходимо только транслировать (=переместить) объект, для чего достаточно обновить (update) мировую матрицу объекта (object's world matrix). Затем нам может понадобиться повернуть объект под одной из осей, для чего применим к нему трансформацию вращения. Обычным делом является масштабирование (изменение размера объекта) после его загрузки из 3D-редактора в игровой мир. Здесь применяется трансформация масштабирования. Часто все эти трансформации применяют одновременно. Иногда объект необходимо повернуть не более чем на 5 градусов. И матрица вращения позволяет это с лёгкостью сделать.
Мы может применять к объекту множество различных трансформаций путём перемножения (multiplaying) различных матриц друг на друга. Но здесь есть свои "подводные камни". Мы перемножаем матрицы друг на друга для достижения желаемого результата. Но в отличие от числовых множителей, когда от перестановки множителей произведение не изменяется, при перемножении матриц их порядок имеет ключевое значение. Т.е. Матрица A * Матрица B != (не равно) Матрица B * Матрица A.

Создание камеры

Хватит теории. Создадим камеру, через которую мы сможем увидеть наш 3D-мир. Писать код с нуля не будем (это не соответствует общей концепции XNA). Вместо этого возьмём за основу стандартный шаблон игрового проекта Windows Game (3.1) и дополним его своим кодом.


Рис. 2 Выбираем шаблон будущего игрового проекта.
Рис. 2 Выбираем шаблон будущего игрового проекта.


  • Стартуй MSVC#2008, если не сделал этого ранее. Создай новый проект.
  • В окне New Project выбери Windows Game (3.1) (См. Рис.2).
  • Введи имя XNADemo. Жми OK.
При этом созданное Решение автоматически сохраняется в папку по умолчанию. Также по умолчанию в каталоге Projects для каждого Решения создаётся отдельный подкаталог (=папка), имя которого повторяет имя Решения. В нашем случае путь к каталогу Решения следующий: C:\Users\<Имя пользователя>\Documents\Visual Studio 2008\Projects\XNADemo.
На всякий случай в конце статьи будет ссылка на архив с нашим доработанным Решением.

  • В открывшемся файле исходного кода Game1.cs найди следующий фрагмент:
Фрагмент файла Game1.cs
...
namespace XNADemo
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
...


  • Сразу после строки SpriteBatch spriteBatch; добавь ниже следующие строки, объявляющие закрытые члены игрового класса:
private Matrix projection;
private Matrix view;

  • Там же найди функцию Initialize, где инициализируются разные штуки:
Фрагмент файла Game1.cs
...
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }
...

  • ПЕРЕД строкой base.Initialize(); добавь объявление метода, инициализирующего нашу камеру:
InitializeCamera();
Как видим, данный метод без параметров и не возвращает значения. Чуть позднее мы его расширим и сделаем закрытым (private).

  • Сохрани Решение (File -> Save All).

Реализация метода InitializeCamera();


После добавления объявления метода InitializeCamera(), функция Initialize() выглядит так:
Фрагмент файла Game1.cs
...
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            InitializeCamera();

            base.Initialize();
        }
...


  • Сразу после функции Initialize() пишем реализацию метода InitializeCamera():
Фрагмент файла Game1.cs
...
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            InitializeCamera();

            base.Initialize();
        }

        private void InitializeCamera()
        {
            //Projection
            float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
                (float)graphics.GraphicsDevice.Viewport.Height;
            Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio,
                0.0001f, 1000.0f, out projection);

            //View
            Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget,
                ref cameraUpVector, out view);
        }
...


Проекция (Projection)

Первым делом мы вводим новую переменную aspectRatio для хранения соотношения сторон экрана. Если мы устанавливаем широкоэкранное соотношение сторон (16:9 или 16:10), то игроки на кинескопных мониторах с соотношением 4:3 увидят игру с чёрными полосами сверху и снизу. И наоборот, если мы установим соотношение 4:3, а у игрока современный монитор с соотношением 16:9, то игра автоматически растянет изображение, что может привести к нежелательному изменению пропорций 3D-объектов.
Во избежание обоих случаев мы предусмотрим обе ситуации и затем предложим игроку выбрать соотношение сторон его монитора, чтобы оно соответствовало соотношению сторон вьюпорта (=виртуальной камеры, отображающей содержимое игровой 3D-сцены). В XNA даже есть специальное свойство DisplayMode, запрашивающее текущие установки экрана после вызова метода Initialization. Конечно, при необходимости можно форсировать вывод изображения с определённым соотношением. Об этом читай раздел "How to: Restrict Graphics Devices to Widescreen Aspect Ratios in Full Screen" документации XNA Framework.


Рис.3 Усечённая пирамида видимого пространства (View Frustum)
Рис.3 Усечённая пирамида видимого пространства (View Frustum)



Далее создаём поле видимости (field of view) путём вызова метода CreatePerspectiveFieldOfView.
В первом параметре передаём значение MathHelper.PiOver4, что соответствует 45 градусам. Для наглядности можно использовать функцию MathHelper.ToRadians(45.0f), но делать это не обязательно, т.к. в классе MathHelper данной константе по умолчанию присвоено значение 45 градусов.
Второй параметр - переменная aspectRatio, который мы высчитали чуть выше.
Третий и четвёртый параметры - это наши ближняя (передняя) и дальняя (задняя) плоскости отсечения (clipping planes) (См. Рис.3). Указанные здесь значения определяют, насколько удалена каждая из плоскостей от объектива виртуальной камеры. Это необходимо для отсечения всех объектов, расположенных ближе передней плоскости и дальше задней. То есть рендериться будут только объекты, оказавшиеся между этими двумя плоскостями, образующими поле видимости (field of view, FOV).
В последнем параметре (out projection) указывается матрица проекции (projection matrix), над которой и производятся текущие преобразования.
Метод является перегруженным (overloaded). Вообще, мы могли бы применить его неперегруженную версию, возвращающую матрицу проекции. Но мы применили перегруз метода, который имеет ссылку (reference) и выходные параметры, что быстрее, т.к. при этом нет необходимости копировать текущие значения параметров.

Вид (View)

Теперь, когда мы установили матрицу проекции, мы можем установить матрицу вида (view matrix). В то время как проекцию (projection) можно представить в виде комбинации линз и фильтров объектива виртуальной камеры, вид (view) - это то, что эта камера "видит". Матрица вида содержит информацию о том, где у камеры верх, куда она направлена, а также её текущую позицию в 3D-пространстве. Для установки матрицы вида применим специальный вспомогательный метод из библиотеки XNA - Matrix.CreateLookAt, имеющий 3 параметра.
Вернёмся к началу Game1.cs, который после добавления двух новых объявлений выглядит так:
Фрагмент файла Game1.cs
...
namespace XNADemo
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

      private Matrix projection;
      private Matrix view;
...


  • Сразу после строки private Matrix view; добавь ниже следующие строки, объявляющие закрытые члены игрового класса:
private Vector3 cameraPosition = new Vector3(0.0f, 0.0f, 3.0f);
private Vector3 cameraTarget = Vector3.Zero;
private Vector3 cameraUpVector = Vector3.Up;

  • Сохрани Решение (File -> Save All).

Теперь мы можем вызывать метод CreateLookAt прямо из метода InitializeCamera. Рассмотрим реализацию метода CreateLookAt, расположенного внутри метода InitializeCamera:
Фрагмент файла Game1.cs
...
            //View
            Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget,
                ref cameraUpVector, out view);
        }
...

Первый параметр - положение камеры. Как следует из трёх последних объявлений в самом начале Game1.cs, мы передаём координаты (0,0,3) в качестве начальной позиции камеры. Это означает, что камера будет расположена в точке отсчёта координатных осей X и Y, но будет сдвинута от него на 3 единицы (units).
Второй параметр - цель (target), куда направлена камера. В нашем случае камера направлена в начало координат (Vector3.Zero).
Третий параметр - вектор "верха" камеры. В нашем случае верх камеры направлен в положительном направлении оси Y (Vector3.Up). Обрати внимание, что для трёх первых параметров мы создали переменные. И теперь можем передавать их значения в виде ссылок (ref). Метод CreateLookAt также является перегруженным (overloaded). И потому он достаточно быстр, т.к. мы передаём значения в виде ссылок, а не конкретных значений. К счастью, читабельность исходного кода при этом почти не пострадала.


Image


Мир (World)

На данном этапе Решение и входящий в него Проект XNADemo можно скомпилировать, нажав F5 на клавиатуре или кнопку с зелёным треугольником на панели управления. В результате на экране появится всё то же окно с голубым фоном. А всё из-за того, что мы пока не установили мировую матрицу (world matrix) и не разместили в этом мире каких-либо объектов. Сейчас мы это быстро исправим.
Как ты мог видеть, шаблоны XNA предлагают множество различных методов для решения самых разных задач. Один из таких методов - Draw, реализация которго размещена в самом конце листинга Game1.cs:
Фрагмент файла Game1.cs
...
        /// <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);

            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }

  • ПЕРЕД строкой base.Draw(gameTime); добавь следующую строку:
Matrix world = Matrix.Identity;

Здесь в качестве мировой матрицы устанавливается (присваивается) единичная (identity) матрица, что означает никакого масштабирования, вращения или трансляции (=перемещения). Единичная матрица имеет трансцляцию (0,0,0), что устанавливает нашу мировую матрицу в точке начала координат (origin) мира.
С этого момента у нас есть полностью настроенная (виртуальная) камера, но мы пока так ничего и не нарисовали.

Вершинные буферы (Vertex Buffers)

3D объекты состоят из треугольников (полигонов).
Закрыть
noteПолигоны и грани. В чём отличие?

Обрати внимание, что в классической геометрии грань фигуры может состоять из более чем трёх вершин (например гранью куба является квадрат, у которого 4 вершины). В терминологии 3D-моделирования принято приравнивать грань фигуры к полигону, который в большинстве систем состоит ровно из трёх вершин. Системы, оперирующие четырёхугольными полигонами встречаются нечасто. Одной из таких была игровая консоль Sega Saturn, увидевшая свет в 1995 г. Её создатели хотели как лучше, а на деле лишь прибавили головной боли разработчикам игр, которые ранее никогда не работали с четырёхугольными полигонами.
На Рис. 1 видно, что боковая грань куба на самом деле состоит из двух треугольных граней (полигонов). Мы также будем придерживаться этого правила, разбивая грани фигур, лежащие в одной плоскости и имеющие более чем 3 вершины, на полигональные треугольники.


Рис.4 Сфера состоит из треугольников
Рис.4 Сфера состоит из треугольников

Рис.5 Различия между 2-мя типами Картезианской системы координат
Рис.5 Различия между 2-мя типами Картезианской системы координат


Каждый объект представляет собой один или несколько треугольников. Например сфера состоит из треугольников. И чем их больше, тем более округлой она выглядит (См. Рис.4). Каждый треугольник состоии из трёх вершин. Для хранения этих вершин применяется вершинный буфер (vertex buffer), представляющий собой кусок памяти (=буфер), хранящий список вершин.
XNA использует праворучную (right-handed) систему координат (См. Рис.5). В ней положительный луч оси X исходит слева направо, ось Y устремляется снизу вверх, а ось Z направлена из точки начала координат к наблюдателю (камере).
XNA использует т.н. отсечение против часовой стрелки (counterclockwise culling) (См. Рис.4) Отсечение (culling, куллинг) представляет собой специальный приём, предотвращающий рендеринг объектов сцены, которые не повёнуты "лицом" (not facing) к камере. Это заметно повышает производительность 3D-рендеринга. В XNA есть 3 опции куллинга:
  • CullClockwiseFace,
  • CullCounterClockwiseFace (используется по умолчанию),
  • None.
Т.к. опция CullCounterClockwiseFace используется по умолчанию, то чтобы увидеть наш объект без лишних настроек, достаточно установить (set up) его вершины в порядке, противоположном движению часовой стрелки.
Закрыть
noteСовет

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

Разместим несколько точек в коде Проекта XNADemo. В идеале должен получиться квадрат. Мы знаем, что все 3D-объекты состоят из треугольников, а квадрат, в свою очередь, из двух треугольников (См. Рис.1).
Мы разместим первый треугольник на след. координатах: (-1,1,0); (1,-1,0); (-1,-1,0). Это означает, что первая точка (=вершина) с координатами (-1,1,0) будет размещена: по оси X левее начала координат на 1 единицу, приподнята по оси Y на 1 единицу.

Объявление структуры VertexPositionNormalTexture[]

  • В открытом Проекте XNADemo в самом начале листинга Game1.cs найди следующий фрагмент:
Фрагмент файла Game1.cs
...
namespace XNADemo
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private Matrix projection;
        private Matrix view;

        private Vector3 cameraPosition = new Vector3(0.0f, 0.0f, 3.0f);
        private Vector3 cameraTarget = Vector3.Zero;
        private Vector3 cameraUpVector = Vector3.Up;
...


  • Сразу после строки private Vector3 cameraUpVector = Vector3.Up; добавь ниже следующие строки:
private VertexPositionNormalTexture[] vertices;
private Texture2D texture;
Для хранения Текстурных координат в XNA применяют структуру типа Vector2. Вторая строка определяет текстуру в качестве объекта типа Texture2D. О текстурах читай ниже в этой статье.

Реализация функции InitializeVertices, размещающей 3 вершины в 3D-пространстве

  • В листинге Game1.cs найди след. фрагмент с реализацией функции InitializeCamera, где мы инициализировали камеру наблюдателя:
Фрагмент файла Game1.cs
...
        private void InitializeCamera()
        {
            //Projection
            float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
                (float)graphics.GraphicsDevice.Viewport.Height;
            Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio,
                0.0001f, 1000.0f, out projection);

            //View
            Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget,
                ref cameraUpVector, out view);
        }
...

  • Прямо под ней добавь реализацию функции InitializeVertices:
Фрагмент файла Game1.cs
...
        private void InitializeVertices()
        {
            Vector3 position;
            Vector2 textureCoordinates;

            vertices = new VertexPositionNormalTexture[4];

            //top left
            position = new Vector3(-1, 1, 0);
            textureCoordinates = new Vector2(0, 0);
            vertices[0] = new VertexPositionNormalTexture(position, Vector3.Forward,
                textureCoordinates);

            //bottom right
            position = new Vector3(1, -1, 0);
            textureCoordinates = new Vector2(1, 1);
            vertices[1] = new VertexPositionNormalTexture(position, Vector3.Forward,
                textureCoordinates);

            //bottom left
            position = new Vector3(-1, -1, 0);
            textureCoordinates = new Vector2(0, 1);
            vertices[2] = new VertexPositionNormalTexture(position, Vector3.Forward,
                textureCoordinates);

            //top right
            position = new Vector3(1, 1, 0);
            textureCoordinates = new Vector2(1, 0);
            vertices[3] = new VertexPositionNormalTexture(position, Vector3.Forward,
                textureCoordinates);

        }
...

Реализация функции InitializeVertices начинается с объявления двух внутренних переменных:
Vector3 position;
Vector2 textureCoordinates;
В XNA есть разные структуры, описывающие типы данных для хранения векторов. В большинстве случаев (в любой типичной 3D-игре) необходимо сохранять данные о положении (position), нормалях (normal) и текстурные координаты. Нормали мы обсудим позднее. Сейчас важно знать о них то, что они позволяют графическому устройству определять, как именно будет отражаться свет от поверхности (=граней) 3D-объектов. Самая важная инфа для любой вершины - это её положение (position) в 3D-пространстве. Напомним, что в XNA для этого обычно используют структуру (struct) Vector3. Мы можем просто назначить (set) значения переменных в конструкторе (как мы сделали это в данном примере), так и просто явно присвоить значения переменным X, Y и Z.

Ниже в коде мы вызываем данный метод. Самое подходящее место для этого - внутри метода LoadContent:
  • Найди в исходном коде Game1.cs следующий фрагмент:
Фрагмент файла Game1.cs
...
        /// <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);

            // TODO: use this.Content to load your game content here
        }
...

  • Сразу после комментария // TODO: use this.Content to load your game content here добавь следующую строку:
InitializeVertices();

На данном этапе Проект также успешно компилируется, но на экран по-прежнему выводится окно с голубым фоном. А всё из-за того, что мы до сих пор не дали команду на отрисовку треугольника на экране. Этим занимается метод Draw.

Изменения в методе Draw

  • Найди в исходном коде Game1.cs следующий фрагмент:
Фрагмент файла Game1.cs
...
        /// <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);

            // TODO: Add your drawing code here

            Matrix world = Matrix.Identity;

            base.Draw(gameTime);
        }
...

  • Выше строки Matrix world = Matrix.Identity; размести следующие строки:
graphics.GraphicsDevice.VertexDeclaration = new
VertexDeclaration(graphics.GraphicsDevice, VertexPositionNormalTexture.VertexElements);

BasicEffect effect = new BasicEffect(graphics.GraphicsDevice, null);

effect.Projection = projection;
effect.View = view;

effect.EnableDefaultLighting();

effect.TextureEnabled = true;
effect.Texture = texture;
  • Ниже строки Matrix world = Matrix.Identity; размести следующие строки:
effect.World = world;
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
graphics.GraphicsDevice.DrawUserPrimitives(
PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
pass.End();
}
effect.End();

base.Draw(gameTime);

  • Сохрани Решение (File -> Save All).

В итоге реализация метода Draw должна выглядеть так:
Фрагмент файла Game1.cs
...
        /// <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);

            // TODO: Add your drawing code here

            graphics.GraphicsDevice.VertexDeclaration = new
                 VertexDeclaration(graphics.GraphicsDevice, VertexPositionNormalTexture.VertexElements);
           
            BasicEffect effect = new BasicEffect(graphics.GraphicsDevice, null);
            
            effect.Projection = projection;
            effect.View = view;

            effect.EnableDefaultLighting();

            effect.TextureEnabled = true;
            effect.Texture = texture;

            Matrix world = Matrix.Identity;

            effect.World = world;
            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphics.GraphicsDevice.DrawUserPrimitives(
                    PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
                pass.End();
            }
            effect.End();

            base.Draw(gameTime);
        }
...


Обрати внимание на то, что метод base.Draw(gameTime); вызывается последним. И всё, что задумал выводить на экран, необходимо указывать ДО его вызова.
Вообще, для вывода трёх точек на экран кода явного многовато. Да, мы слегка усложнили задачу, добавив эффект.

Эффекты (Effects)

В XNA эффекты используются для вывода чего угодно на экран. Они управляют светом, текстурами и даже положением точек. По теме эффектов и языка шейдеров высокого уровня (High Level Shader Language, HLSL) написана не одна сотня книг. В нашем случае мы добавили в код простейший эффект (effect), предоставляемый специальным классом BasicEffect. Он позволяет нам пока не создавать файлы-листинги эффектов на языке HLSL, что заметно упрощает задачу.

Первым делом мы создаём для эффекта новую переменную:
Фрагмент файла Game1.cs
...
            BasicEffect effect = new BasicEffect(graphics.GraphicsDevice, null);
...

Здесь в первом параметре указываем графическое устройство (graphics device), во втором указывается т.н. пул эффектов (effect pool), который в нашем случае равен null. Пул эффектов используется для хранения и совместного использования нескольких эффектов.
Далее назначаем некоторые свойства:
Фрагмент файла Game1.cs
...
            effect.Projection = projection;
            effect.View = view;

            effect.EnableDefaultLighting();

            effect.TextureEnabled = true;
            effect.Texture = texture;

            Matrix world = Matrix.Identity;

            effect.World = world;
...

Здесь мы назначаем для только что созданного эффекта effect матрицы: проекции, вида и мировую, а также включаем освещение по умолчанию (effect.EnableDefaultLighting();). Теперь объект на экране будет освещён.

Закрыть
noteОбрати внимание

При работе с 3D-сценой полезно выставлять цвет фона в Color.CornflowerBlue или любой другой нечёрный цвет. В случае неверно настроенного освещения такой подход поможет предотвратить появление на сцене чёрного (неосвещённого) объекта на чёрном фоне. Ведь при таком раскладе может показаться, что на экране вовсе ничего нет, кроме фона.


Реализация нашего эффекта начинается с вызова метода Begin и оканчивается спецметодом End. Всё, что окажется между этими двумя командами, будет прорисовываться с данным эффектом.
Далее идёт цикл foreach, который итерируется (=проходит) через все вызовы нашего эффекта.
Каждый эффект имеет одну или несколько т.н. техник (technique). Каждая техника имеет один или несколько т.н. пасов (pass = "прохождение"). В нашем случае эффект имеет 1 технику и 1 пас:
Фрагмент файла Game1.cs
...
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphics.GraphicsDevice.DrawUserPrimitives(
                    PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
                pass.End();
            }
            effect.End();
...

Как раз внутри этого единственного паса и происходит отрисовка треугольника на экран с помощью метода DrawUserPrimitives. Вот его параметры:
Параметр Описание
PrimitiveType.TriangleList Тип примитива.
vertices Вершинный буфер, созданный выше методом InitializeVertices.
0 Смещение (offset) точки начала отрисовки (в нашем случае 0).
vertices.Length / 3 Число треугольников, выводимое на экран. Вычисляется путём деления количества вершин на число 3. В нашем случае получается ровно 1 треугольник.


  • Скомпилируй Проект (F5).

На экране появится окно приложения с чёрным треугольником внутри. Да, он страшный (т.к. без текстуры), но это не что иное, как треугольник. Начало положено!
Помимо TriangleList, энумерация PrimitiveType содержит несколько других типов примитивов:
Примитив Описание
LineList Рендерит вершины в виде изолированных прямолинейных (streight-line) сегментов.
LineStrip Рендерит вершины в виде единой полилинии (ломаной).
PointList Рендерит вершины в виде набора изолированных точек. Неприменимо для индексированных (Indexed) примитивов.
TriangleFan Рендерит вершины в виде треугольного цветка (вентилятора).
TriangleList Рендерит указанные вершины в виде подборки (sequence) изолированных треугольников. Каждая группа из трёх точек пределяет отдельный треугольник. Отсечение обратной (невидимой) грани (Back-face culling) производится текущим рендер-стейтом (render-state).
TriangleStrip Рендерит вершины в виде треугольной полосы, похожей на детскую головоломку "змейка". Флаг отсечения обратных граней (back-face culling flag) переключается автоматически на чётных треугольниках.

  • "Поиграй" с исходным кодом, подставляя различные значения первого параметра метода DrawUserPrimitives и отправляя код на перекомпиляцию.
В большинстве случаев на экране будет всё тот же треугольник. Ведь он у нас всего один.

Текстуры (Textures)


Рис. 6 Ветка Content в Обозревателе Решения.
Рис. 6 Ветка Content в Обозревателе Решения.


Мы вывели на экран треугольник. Но выглядит он невзрачно. Мы это быстро исправим, добавив к нему текстуру.
Напомним, что наше Решение (в нашем случае) сохранено по адресу C:\Users\<Имя пользователя>\Documents\Visual Studio 2008\Projects\XNADemo. В нём содержится пара других подкаталогов, включая папку Content (Содержимое).

Добавим файл текстуры в Проект:
  • Найди в Интернете (или создай в любом графическом редакторе) изображение с расширением.jpg, размером 256х256 пикселей.
  • Задай ему имя texture.jpg.
  • В Обозревателе Решения (Solution Explorer) щёлкни правой кнопкой мыши по ветке Content и во всплывающем меню выбери: Add -> Existing Item... (Добавить -> Существующий объект). Укажи путь к файлу текстуры и нажми Add (см. Рис.6).
Добавленная текстура скопируется в подкаталог Content нашего Решения и появится в ветке Content Обозревателя Решения MSVC#.

Выше мы объявили объект текстуры в качестве объекта типа Texture2D (предоставляемого XNA):
Фрагмент файла Game1.cs
...
        private Texture2D texture;
...

Класс Texture2D наследуется от класса Texture, позволяющего использовать текстуры в качестве ресурсов.
  • Найди в исходном коде Game1.cs реализацию функции LoadContent:
Фрагмент файла Game1.cs
...
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            InitializeVertices();
        }
...

  • После строки InitializeVertices(); добавь строку:
texture = Content.Load<Texture2D>("texture");

Здесь мы загружаем файл текстуры в движок и присваиваем его имя внутренней переменной texture, объявленной в начале листинга. Теперь её необходимо ассоциировать с эффектом (effect), который используется для отрисовки треугольника.
Выше мы дополняли метод Draw следующими строками:
Фрагмент файла Game1.cs
...
            effect.TextureEnabled = true;
            effect.Texture = texture;
...

Здесь мы просто "включаем" текстуры и присваиваем загруженную текстуру текущему эффекту.

  • Сохрани Решение (File -> Save All).

Всё, можно компилировать!

  • Скомпилируй Проект (F5).

На экране появится треугольник с текстурой.


Источники:


1. Chad Carter. Microsoft XNA Game Studio 3.0 Unleashed. - Sams Publishing. 2009


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Знакомство с XNA Framework и XNA Game Studio  »  XNA - Создание 3D-объектов

Contributors to this page: slymentat .
Последнее изменение страницы Воскресенье 16 / Июнь, 2019 16:44:20 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108