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

Введение в XNA

Голосов: 5

Книга адресована студентам и начинающим разработчикам, которые хотят использовать в своих проектах высокопроизводительную графику. Книга рассчитана на читателей, уже знакомых с основами C# и платформы .NET. Первая глава знакомит читателя с XNA Framework. Вторая глава посвящена визуализации базовых примитивов XNA Framework. В третьей главе рассматриваются более сложные вопросы визуализации: использование полноэкранного режима, плавная анимация примитивов и имитация прозрачности. В четвертой главе весь ранее изученный материал сводится воедино на примере создания полноценного хранителя экрана с дистрибутивом, при необходимости автоматически инсталлирующим на компьютер пользователя XNA Framework. В пятой, заключительной главе затрагивается тема программируемого графического конвейера, но уже на значительно более глубоком уровне; рассматриваются язык HLSL, основы ассемблеро-подобного языка Vertex Shader 1.1 и интегрированная среда разработки FX Composer 2.0, значительно облегчающая разработку и отладку шейдеров. Данная книга входит в состав <a target=_blank href="http://www.microsoft.com/Rus/Msdnaa/Curricula/">"Библиотеки учебных курсов"</a>, формирование которой ведется в рамках программы академического сотрудничества <a target=_blank href="http://www.microsoft.com/rus/msdnaa/">MSDN Academic Alliance (MSDN AA)</a>.

Приведенный ниже текст получен путем автоматического извлечения из оригинального PDF-документа и предназначен для предварительного просмотра.
Изображения (картинки, формулы, графики) отсутствуют.
    2.6.2. Веер треугольников (PrimitiveType.TriangleFan)
Следующий тип примитивов, PrimitiveType.TriangleFan, используется для рисования вееров
треугольников. Первые три вершины (0-я, 1-я и 2-я) задают первый треугольник. Второй треугольник
задаѐтся 0-й, 2-й и 3-й вершинами, третий – 0-й, 3-й и 4-й вершинами и т.д. (рисунок 2.29). Данный тип
примитивов идеально подходит для рисования эллипсов, окружностей, секторов окружностей и
аналогичных фигур.


                              v3
             v2
                                                    v4




                              v0


             v1                                v5

Рисунок 2.29. Веер треугольников, нарисованный с использованием примитивов PrimitiveType.TriangleFan

Чтобы опробовать этот тип примитива на практике, мы модифицируем пример Ex09, заставив его рисовать
на экране закрашенный круг вместо окружности (рисунки 1.31). Для этого придѐтся внести три небольших
изменения в обработчики событий Load и Paint:
22. Увеличить размер массива вершин на одну вершину.
23. Вставить в начало массива вершину с координатами центра окружности
24. Изменить тип примитива с LineList на TriangleFan
Так же мы добавим в программу возможность переключения между каркасным и закрашенным режимами
отображения треугольников при помощи клавиши пробел (Space). Эта функциональность, позволяющая
просматривать топологию сцены, неоценима при отладке приложения (рисунок 2.31).




Рисунок 2.30. Круг, нарисованный при помощи веера из 64-х треугольников (PrimitiveType.TriangleFan)


Рисунок 2.31. Круг (веер из 18-ти треугольников), визуализированный в каркасном режиме.

Основные фрагменты исходного кода полученного приложения (Ex15) приведены в листинге 2.25.

 Листинг 2.25.

// Количество сегментов в круге
const int slices = 64;
// Режим закраски круга
FillMode fillMode = FillMode.Solid;

private void MainForm_Load(object sender, EventArgs e)
{
...
// Создаѐм графический буфер
    vertices = new VertexPositionColor[slices + 2];

// Помещаем в начало графического буфера вершину, расположенную в центре экрана
    vertices[0] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f),
XnaGraphics.Color.White);

// Перебираем все вершины окружности
    for (int i = 0; i <= slices; i++)
    {
// Определяем координаты текущей вершины
        float angle = (float)i / (float)slices * 2.0f * (float)Math.PI;
        float x = 0.7f * (float)Math.Sin(angle);
        float y = 0.7f * (float)Math.Cos(angle);
// Вычисляем цвет вершины
        byte red = (byte)(255 * Math.Abs(Math.Sin(angle * 3)));
        byte green = (byte)(255 * Math.Abs(Math.Cos(angle * 2)));
// Помещаем информацию о вершине в массив вершин
        vertices[i + 1] = new VertexPositionColor(new Vector3(x, y, 0.0f),
            new XnaGraphics.Color(red, green, 0));
    };
...
}

private void MainForm_Paint(object sender, PaintEventArgs e)
{
...


// Задаем область визуализации размером во весь экран. Для вычисления параметров видового
// преобразования используется метод FullScreenViewport нашего вспомогательного класса Helper
        device.Viewport = Helper.FullScreenViewport(ClientSize);
// Закрашиваем поверхность формы
        device.Clear(XnaGraphics.Color.CornflowerBlue);

// Задаем квадратную область закраски максимально возможного размера
        device.Viewport = Helper.SquareViewport(ClientSize);

// Выключаем отсечение треугольников (см. предыдущий раздел)
        device.RenderState.CullMode = CullMode.None;
// Задаѐм режим визуализации треугольников
        device.RenderState.FillMode = fillMode;

        device.VertexDeclaration = decl;;

// Рисуем круг
        effect.Begin();
        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Begin();
            device.DrawUserPrimitives(PrimitiveType.TriangleFan, vertices, 0, vertices.Length
- 2);
            pass.End();
        }
        effect.End();

// Переключаем вспомогательные буферы
        device.Present();
}

// Обработчик события нажатия клавиш
private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
// Если нажата клавиша пробел
    if (e.KeyCode == Keys.Space)
    {
// Меняем режим отображения
        if (fillMode == FillMode.Solid)
            fillMode = FillMode.WireFrame;
        else
            fillMode = FillMode.Solid;

// Обновляем изображение
        Invalidate();
    }
}



2.6.3.Полоса из связанных треугольников (PrimitiveType.TriangleStrip)
Последний тип примитивов, PrimitiveType.TriangleStrip, предназначен для рисования полосы из
связанных треугольников. При этом первый треугольник проходит через 0-ю, 1-ю 2-ю вершины, второй
треугольник – через 3-ю, 2-ю и 1-ю вершины, третий треугольник – через 2-ю, 3-ю и 4-ю вершины,
четвѐртый через 5-ю, 4-ю и 3-ю, и т.д (рисунок 2.32). Обратите внимание на порядок перечисления вершин в
треугольниках: все треугольники в полосе имеют одинаковую ориентацию относительно часовой стрелки –
например, если вершины первого треугольника располагаются по часовой стрелке, то и вершины других
треугольников так же будут перечисляться по часовой стрелке. Эта особенность используется при отсечении
невидимых треугольников с использованием поля RenderState.CullMode (см. раздел x.x).


                               v3                 v5                                  v9               v11
                                                                      V7
            v1




                                                   v4
                                                                                       v8              v10
            v0                v2                                     v6
Рисунок 2.32. Визуализация полосы связанных треугольников

Данный тип примитивов очень удобно использовать для визуализации ломаных линий шириной больше
одного пикселя, то есть в качестве продвинутой версии примитива PrimitiveType.LineStrip. В
частности, на рисунке 2.32 в качестве иллюстрации приведена ломаная линия переменной ширины,
состоящая из пяти сегментов.

Визуализация графика функции y=cos(x)
Чтобы       попрактиковаться          в     рисовании        ломаных        линий       при       помощи        примитива
PrimitiveType.TriangleStrip, мы напишем приложение, визуализирующее график косинуса в
интервале с 0º…720º (0…4·π в радианах) использованием ломаной толщиной 10 пикселей. Хотя на первый
взгляд эта задача не намного сложнее практический упражнений №2.1 и №2.2, она всѐ же имеет несколько
подвохов.
Для начала сформулируем задачу более чѐтко. Нам необходимо построить полосу из связанных
треугольников, аппроксимирующую график косинуса, центр которой совпадает с графиком косинуса
(рисунок 2.33).




Рисунок 2.33. Полоса из связанных треугольников, аппроксимирующая график косинуса (тонкая линию, проходящая по центру
полосы).

Для построения синусоиды мы будем перебирать точки графика косинуса с определѐнным шагом. На
рисунке 2.30 эти точки обозначены как p0, p1, p2 и т.д. Отступив симметрично по обе стороны от точки p0 на
некоторое расстояние, например, на 0.05 единиц, мы получим две вершины v0 и v1, расстояние межу
которыми равно 10 пикселей. Проделав аналогичную операцию над остальными точками, мы получим пары
вершин (v2–v3, v4–v5, v6–v7, …, v2·n–v2·n+1), расстояние между которыми равно 0.1 единиц. И, наконец,
построив полосу из треугольников, опирающуюся на вершины v0, v1, v2, v3, мы получим ломаную линию
толщиной 0.1 пикселей, точно аппроксимирующую график косинуса (рисунок 2.34).


Рисунок 2.34. Построение полосы из треугольников

По ширине график косинуса будет вписан в клиентскую область окна, а по высоте наш график будет
немного меньше высоты окна. Таким образом, в действительности наше приложение будет визуализировать
не сам график y=cos(x), а несколько другую функцию, полученную путем масштабирования графика
y=cos(x) вдоль осей X и Y:
y  0.6  cos(2    x)                                                                       (2.9)
где
     cos – функция вычисляющая значение косинуса в радианах
     x – аргумент функции, лежащий в диапазоне [-1, +1]. Фактически это координата x графика,
      пробегающая с определѐнным шагом значения от левого до правого краѐв экрана, то есть от -1 до +1.
      Соответственно аргумент функции косинуса пробегает значения от 0 до 4   (0º...720º).
      y – координата y графика функции, лежащая в диапазоне от [-0.7, +0.7].
Перебирая с определѐнным шагом значения координаты x от -1 до +1 и подставляя их в выражение (2.9),
мы получим координаты набора точек p0, p1, …, pn (рисунок 2.34). Как говорилось выше, для получения
координат вершин полосы треугольников v0, v1, …, v2·n, v2·n+1 необходимо симметрично отупить от точек p0
… pn на 0.05 единиц.
Вроде бы всѐ просто и понятно, если не считать одной мелочи: мы пока не ещѐ определились, каким
образом должны быть сориентированы отрезки v0–v1, v2–v3, …, v2·n–v2·n+1 относительно точек p0, p1, …, pn.
Не мудрствуя лукаво, мы сделаем эти отрезки параллельными оси Y и посмотрим, что из этого выйдет:
v0 = p0 - 0.05                                                                                 (2.10)
v1 = p0 + 0.05
и т.д.
Основные фрагменты приложения (Ex16) приведены в листинге 2.26.

    Листинг 2.26.

public partial class MainForm : Form
{
// Количество сегментов в ломаной линии, аппроксимирующей график косинуса
    const int QuadStrips = 100;
// Число треугольников в ломанной линии
    const int TriStrips = QuadStrips * 2;

...
// Массив вершин
    VertexPositionColor[] vertices = null;
// Режим закраски треугольников
    FillMode fillMode = FillMode.Solid;

...


      private void MainForm_Load(object sender, EventArgs e)
      {
...
// Создаѐм массив вершин для хранения вершин полоски из треугольников
        vertices = new VertexPositionColor[TriStrips+2];

// Перебираем вершины полоски из треугольников
        for (int i = 0; i <= QuadStrips; i++)
        {
// Определяем текущее значение координаты x вершины
            float x = -1.0f + 2.0f * (float) i / (float) QuadStrips;
// Вычисляем значение косинуса, соответствующее координате x
            float angle = 2.0f * (float)Math.PI * x;
            float cos = (float)Math.Cos(angle);
// Вычисляем значение координаты y вершины по формуле 2.9
            float y = 0.6f * cos;
// Вычисляем красную и зелѐную составляющую цвета по формулам 2.3 (см. практическое
// упражнение 2.2)
            byte green = (byte)(Math.Pow(0.5f + cos * 0.5f, 0.3f) * 255.0f);
            byte red = (byte)(Math.Pow(0.5f - cos * 0.5f, 0.3f) * 255.0f);

// Заносим в массив координаты вершины v[i*2] (см. выражение 2.10)
            vertices[i * 2] = new VertexPositionColor(new Vector3(x, y - 0.05f, 0.0f),
                new XnaGraphics.Color(red, green, 0));
// Заносим в массив координаты вершины v[i*2+1]
            vertices[i * 2 + 1] = new VertexPositionColor(new Vector3(x, y + 0.05f, 0.0f),
                new XnaGraphics.Color(red, green, 0));
        };
...
    }

      private void MainForm_Paint(object sender, PaintEventArgs e)
      {
...
              device.Clear(XnaGraphics.Color.DarkSlateGray);

// Выключаем отсечение невидимых треугольников
            device.RenderState.CullMode = CullMode.None;
// Задаѐм режим показа треугольников
            device.RenderState.FillMode = fillMode;

              device.VertexDeclaration = decl;

// Визуализируем полоску из треугольников, аппроксимирующую график косинуса
            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                device.DrawUserPrimitives(PrimitiveType.TriangleStrip, vertices, 0,
vertices.Length - 2);
                pass.End();
            }
            effect.End();

              device.Present();
          }
      }


Скомпилируйте и запустите приложение на выполнение. Как и ожидалось, на экране появится график
функции y=cos(x), однако толщина графика будет переменной, причем максимальная толщина графика
будет достигаться в окрестностях точек, в которых функция cos(x) принимает значения -1 или 1. (рисунок
2.35).
       П р им еч а н ие
       Следующий материал этого раздела содержит довольно много математических выкладок, поэтому если вы не в
       ладах с математикой, можете смело пропустить оставшуюся часть раздела 2.6.3.




Рисунок 2.35. График функции y=cos(x) переменной толщины


График y=cos(x) постоянной толщины
И так, попытка использования отрезков v0–v1, …, v2·n–v2·n+1 параллельных оси Y закончилась неудачей. Ну
что ж, отрицательный результат, это тоже результат. Попробуем поэкспериментировать ориентацией
отрезков v0–v1, …, v2·n–v2·n+1, например, развернув их под углом 45° (рисунок 2.36):
// Вычисляем вектор смещения вершин v[2*i+1] относительно p[n]
float nx = (float) (5.0 / Math.Sqrt(2.0));
float ny = nx;

// Симметрично смещаем вершины на 5 пикселей в направлении векторов (-1, -1) и (+1, +1)
vertices[i * 2] = new VertexPositionColor(new Vector3(x - nx, y - ny, 0.0f),
    new XnaGraphics.Color(red, green, 0));
vertices[i * 2 + 1] = new VertexPositionColor(new Vector3(x + nx, y + ny, 0.0f),
    new XnaGraphics.Color(red, green, 0));


Рисунок 2.36. График функции y=cos(x) переменной толщины. Вершины смещаются в направлении векторов (-1, -1) и (+1, +1).

Проведя несколько экспериментов, мы придѐм к выводу, что график косинуса имеет необходимую толщину
только в там, где отрезки v2·i–v2·i+1 перпендикулярны графику косинуса. Следовательно, чтобы график
функции имел постоянную толщину 0.1 единиц, все отрезки v2·i–v2·i+1 должны быть перпендикулярны
графику косинуса.
Для нахождения координат вершин отрезка v2·i–v2·i+1 длиной 0.1 единиц, проходящего через точку pi,
перпендикулярно графику функции необходимо выполнить следующие действия:
25. Найти вектор, s перпендикулярный графику функции в точке pi.
26. Найти вектор, параллельный вектору n длиной пять единиц, параллельный вектору s .
27. Для нахождения координат вершин v2·i и v2·i+1 необходимо отложить от точки pi вектора                   n и n .
Рассмотрим эти шаги более подробно. Как вы знаете из курса аналитической геометрии, вектор,
перпендикулярного графику функции, определяются по формуле:
       df ( x, y) df ( x, y )
s (             ,            )                                                                                    (2.11)
          dx         dy
где
     s – вектор, перпендикулярный графику функции
     f ( x, y) – функция, заданная в неявной форме (f(x, y)=0)
      df ( x, y ) df ( x, y )
                ,            – частные производные по x и y
         dx          dy
Чтобы определить значение вектора          s для нашей функции (2.9), перепишем еѐ в неявной форме f(x, y)=0:
y  0.6  cos(2    x)  0                                                                                       (2.12)


Теперь найдѐм частные производные, являющиеся координатами вектора                    s:
s  (sx , s y )
       d ( y  0.6  cos(2    x))
sx                                   1.2    sin(2    x )                                                   (2.13)
                     dx


        d ( y  0.6  cos(2    x))
sy                                   1
                     dy

Нахождение производных в среде MathCAD
Если вы немного подзабыли высшую математику, не огорчайтесь. Для нахождения производных можно
воспользоваться, к примеру, математическим пакетом MathCAD.
Для вычисления значения производной средствами символьной математики пакета MatCAD просто
наберите выражение производной, которую выходите вычислить. Затем введите специальный символ →
(Ctrl + .) и нажмите Enter, после чего справа от выражения появится вычисленное значение производной
(рисунок 2.37). Подробную информацию о среде MathCAD можно найти, к примеру, в [К.22].




Рисунок 2.37. Математический пакет MathCAD.

Зная вектор    s можно легко найти вектор n заданной длины, параллельный вектору s :
          s
n  a                                                                                        (2.14)
          s
где
     n – вектор, параллельный вектору s и имеющий длину a
     a – необходимая длина вектора (в нашем случае 0.05)
     s – длина вектора s
После этого определить координаты точек v2·i и v2·i+1 не составит труда:
v2i  pi  n                                                                                 (2.15)

v2i1  pi  n


Имея под рукой формулы 2.13, 2.14 и 2.15 мы можем легко исправить ошибку в примере Ex16 (визуализация
графика функций переменной толщины вместо постоянной) путем небольшой модификации фрагмента
обработчика события Paint (листинг 2.27).

 Листинг 2.27.

// Полная версия приложения находится на CD диске книги в каталоге Ex02\Ex17.
for (int i = 0; i <= QuadStrips; i++)
{
    float x = -1.0f + 2.0f * (float) i / (float) QuadStrips;
    float angle = 2.0f * (float)Math.PI * x;
    float cos = (float)Math.Cos(angle);
    float y = 0.6f * cos;
    byte green = (byte)(Math.Pow(0.5f + cos * 0.5f, 0.3f) * 255.0f);
    byte red = (byte)(Math.Pow(0.5f - cos * 0.5f, 0.3f) * 255.0f);

// Вычисляем вектор s
    float sx = (float)(1.2 * Math.PI * Math.Sin(2.0 * Math.PI * x));
    float sy = 1.0f;
// Вычисляем длину вектора s
    float length = (float)Math.Sqrt(sx * sx + sy * sy);
// Вычисляем вектор nx
    float nx = sx / length * 0.05f;
    float ny = sy / length * 0.05f;

// Заносим в графический буфер координаты вершин v[i*2] и v[i*2+1] (вычисляются по формуле
// 2.15)
    vertices[i * 2] = new VertexPositionColor(new Vector3(x - nx, y - ny, 0.0f),
        new XnaGraphics.Color(red, green, 0));
    vertices[i * 2 + 1] = new VertexPositionColor(new Vector3(x + nx, y + ny, 0.0f),
        new XnaGraphics.Color(red, green, 0));
};
Все изменения обработчика события Paint сводятся к добавлению пяти новых строк кода и косметической
правке двух строк. Как говорится, дело в количестве строк, а в математических формулах, которые
заложены в эти строки. Результат работы приложения приведен на рисунке 2.38.




Рисунок 2.38. График функции y=cos(x) постоянной толщины



    
Яндекс цитирования Яндекс.Метрика