netlib.narod.ru< Назад | Оглавление | Далее >

Рисование трехмерных осей в XNA

Чтобы проиллюстрировать изложенные в предыдущих разделах концепции, сейчас вы создадите код для рисования линий, изображающих каждую из трех осей системы координат и буквы X, Y и Z возле этих линий, чтобы вы могли самостоятельно видеть результаты создания трехмерных сцен и манипулирования ими.

Если подытожить, этапы создания и визуализации трехмерных объектов в XNA будут следующими:

  1. Определяем, какой тип вершин будем использовать (местоположение плюс цвет, текстура и т.д.).

  2. Создаем массив вершин и заполняем его данными вершин.

  3. Создаем буфер вершин и заполняем его ранее созданными вершинами.

  4. Определяем используемый эффект с матрицей проекции и матрицей вида и источники света, если они есть.

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

  6. Используя эффект, рисуем буфер вершин, указав требуемый тип примитивов.

Все, вовлеченные в эти этапы, концепции были уже ранее исследованы в этой главе, так что, если вам что-то не совсем ясно, вернитесь к предыдущим страницам, прежде чем переходить к коду.

Для лучшей организации вашего кода создайте новый класс с именем cls3DAxis. В этом классе будет несколько методов с теми же именами, что и у хорошо известного вам к настоящему моменту класса Game1, предоставляемого при создании нового решения XNA Windows Program: LoadContent, UnloadContent и Draw. Вы будете вызывать эти методы из их тезок в главном классе игры.

Создайте новый класс и включите код для трех закрытых свойств, — device, vertexBuffer и effect, — а также создайте конструктор класса с кодом для получения и сохранения объекта графического устройства. Графическое устройство потребуется вам для операций визуализации, и вы должны создать буфер вершин и эффект на уровне класса, чтобы их можно было создавать в методе LoadContent и освобождать в методе UnloadContent. Начальный код класса таков:

class cls3DAxis
{
    private GraphicsDevice device;
    private VertexBuffer vertexBuffer;
    private BasicEffect effect;

    public cls3DAxis(GraphicsDevice graphicsDevice)
    {
        device = graphicsDevice;
    }
}

Программирование вершин и буфера вершин

Сейчас мы запрограммируем закрытый вспомогательный метод класса, с именем Create3DAxis, который создает трехмерные оси и заполняет буфер вершин. Этим мы выполним первые три этапа из составленного нами ранее списка.

Следующий фрагмент кода показывает первую версию метода, которая просто создает три линии, представляющие оси трехмерной системы координат, простирающиеся от начала координат на axisLength единиц в положительном и отрицательном направлениях. Например, если axisLength равно 1, для оси X будет нарисована линия от (–1, 0, 0) до (1, 0, 0).

private void Create3DAxis()
{
    // Длина оси
    float axisLength = 1f;

    // Количество вершин
    int vertexCount = 6;

    VertexPositionColor[] vertices = new VertexPositionColor[vertexCount];

    // Ось X
    vertices[0] = new VertexPositionColor(new Vector3(-axisLength, 0.0f, 0.0f),
                                          Color.White);
    vertices[1] = new VertexPositionColor(new Vector3(axisLength, 0.0f, 0.0f),
                                          Color.White);
    // Ось Y
    vertices[2] = new VertexPositionColor(new Vector3(0.0f, -axisLength, 0.0f),
                                          Color.White);
    vertices[3] = new VertexPositionColor(new Vector3(0.0f, axisLength, 0.0f),
                                          Color.White);
    // Ось Z
    vertices[4] = new VertexPositionColor(new Vector3(0.0f, 0.0f, -axisLength),
                                          Color.White);
    vertices[5] = new VertexPositionColor(new Vector3(0.0f, 0.0f, axisLength),
                                          Color.White);

    // Заполняем вершинами буфер вершин
    vertexBuffer = new VertexBuffer(device,
                                    vertexCount * VertexPositionColor.SizeInBytes,
                                    BufferUsage.WriteOnly);
    vertexBuffer.SetData<VertexPositionColor>(vertices);
}

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

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

После создания буфера, в последней строке кода, вы заполняете его данными вершин, вызвав метод буфера вершин SetData, который получает созданный вами массив вершин и формат вершин (также называемый настраиваемый формат вершин, custom vertex format, или гибкий формат вершин, flexible vertex format).

Чтобы создать буквы на положительном конце каждой из осей вам надо создать новые сегменты линий, формирующие каждую из букв. В таком случае лучше всего нарисовать небольшой набросок, который позволит вам вычислить местоположение вершин для каждой линии каждой буквы. Взгляните на расстояния, показанные на рис. 7.14 и сравните их со следующим фрагментом кода, в котором представлен законченный вариант функции Create3DAxis. Убедитесь, что понимаете, как рисуются буквы X, Y и Z.


Рис. 7.14. Набросок, показывающий размеры для создания букв каждой оси

Рис. 7.14. Набросок, показывающий размеры для создания букв каждой оси


Если вы недоумеваете, как мы получили значения, представленные на рис. 7.14, ответ прост: методом проб и ошибок! Если вам не нравится, как выглядят буквы, просто подстройте значения, пока не достигнете желаемого эффекта.

private void Create3DAxis()
{
    // Длина оси
    float axisLength = 1f;

    // Количество вершин
    int vertexCount = 22;

    VertexPositionColor[] vertices = new VertexPositionColor[vertexCount];

    // Ось X
    vertices[0] = new VertexPositionColor(
            new Vector3(-axisLength, 0.0f, 0.0f), Color.White);
    vertices[1] = new VertexPositionColor(
            new Vector3(axisLength, 0.0f, 0.0f), Color.White);

    // Ось Y
    vertices[2] = new VertexPositionColor(
            new Vector3(0.0f, -axisLength, 0.0f), Color.White);
    vertices[3] = new VertexPositionColor(
            new Vector3(0.0f, axisLength, 0.0f), Color.White);

    // Ось Z
    vertices[4] = new VertexPositionColor(
            new Vector3(0.0f, 0.0f, -axisLength), Color.White);
    vertices[5] = new VertexPositionColor(
            new Vector3(0.0f, 0.0f, axisLength), Color.White);

    // Буква "X" около оси X
    vertices[6] = new VertexPositionColor(
            new Vector3(axisLength - 0.1f, 0.05f, 0.0f), Color.White);
    vertices[7] = new VertexPositionColor(
            new Vector3(axisLength - 0.05f, 0.2f, 0.0f), Color.White);
    vertices[8] = new VertexPositionColor(
            new Vector3(axisLength - 0.05f, 0.05f, 0.0f), Color.White);
    vertices[9] = new VertexPositionColor(
            new Vector3(axisLength - 0.1f, 0.2f, 0.0f), Color.White);

    // Буква "Y" около оси Y
    vertices[10] = new VertexPositionColor(
            new Vector3(0.075f, axisLength - 0.125f, 0.0f), Color.White);
    vertices[11] = new VertexPositionColor(
            new Vector3(0.075f, axisLength - 0.2f, 0.0f), Color.White);
    vertices[12] = new VertexPositionColor(
            new Vector3(0.075f, axisLength - 0.125f, 0.0f), Color.White);
    vertices[13] = new VertexPositionColor(
            new Vector3(0.1f, axisLength - 0.05f, 0.0f), Color.White);
    vertices[14] = new VertexPositionColor(
            new Vector3(0.075f, axisLength - 0.125f, 0.0f), Color.White);
    vertices[15] = new VertexPositionColor(
            new Vector3(0.05f, axisLength - 0.05f, 0.0f), Color.White);

    // Буква "Z" около оси Z
    vertices[16] = new VertexPositionColor(
            new Vector3(0.0f, 0.05f, axisLength - 0.1f), Color.White);
    vertices[17] = new VertexPositionColor(
            new Vector3(0.0f, 0.05f, axisLength - 0.05f), Color.White);
    vertices[18] = new VertexPositionColor(
            new Vector3(0.0f, 0.05f, axisLength - 0.1f), Color.White);
    vertices[19] = new VertexPositionColor(
            new Vector3(0.0f, 0.2f, axisLength - 0.05f), Color.White);
    vertices[20] = new VertexPositionColor(
            new Vector3(0.0f, 0.2f, axisLength - 0.1f), Color.White);
    vertices[21] = new VertexPositionColor(
            new Vector3(0.0f, 0.2f, axisLength - 0.05f), Color.White);

    // Заполняем вершинами буфер вершин
    vertexBuffer = new VertexBuffer(device,
            vertexCount * VertexPositionColor.SizeInBytes,
            ResourceUsage.WriteOnly,
            ResourceManagementMode.Automatic);
    vertexBuffer.SetData<VertexPositionColor>(vertices);
}

Вам также надо создать в классе cls3DAxis код в методе LoadContent, вызывающий Create3DAxis, и освобождающий буфер вершин код в UnloadContent, как показано в следующем фрагменте кода.

public void LoadContent()
{
    // Создание трехмерных осей
    Create3DAxis();
}

public void UnloadContent()
{
    if (vertexBuffer != null)
    {
        vertexBuffer.Dispose();
        vertexBuffer = null;
    }
}

Это завершает код для создания и освобождения (удаления) из памяти ресурсов вершин осей трехмерной системы координат. Однако вы еще не можете запустить программу: вам необходимо добавить код для основного эффекта, определяющий, как должна выполняться визуализация, и вызовы класса cls3DAxis из главного класса программы, Game1.

В следующем разделе вы закончите работу над классом cls3DAxis, установив свойство effect, необходимое для отображения осей.

Программирование эффекта и визуализация трехмерной сцены

Ранее в этой главе вы видели, что BasicEffect — это класс XNA предоставленный чтобы помочь вам создавать эффекты для визуализации трехмерных сцен, и включающий много свойств, позволяющих вам, например, определять местоположение камеры, используемую проекцию и применяемые источники света.

В следующем фрагменте кода показан полный код метода LoadContent, включая создание и конфигурирование простого базового эффекта, соответствующего требованиям ваших программ. Все показанные функции и свойства уже обсуждались; можете вернуться к разделу «Свет, камера... эффект!» чтобы освежить в памяти детали, если почувствуете, что потерялись:

public void LoadContent()
{
    // Создание эффекта, который будет использоваться
    // для рисования осей
    effect = new BasicEffect(device, null);

    // Вычисление форматного соотношения эффекта,
    // матрицы проекции и матрицы вида 
    float aspectRatio =
        (float)device.Viewport.Width / device.Viewport.Height;
    effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 2.0f, 2.0f),
                                      Vector3.Zero, Vector3.Up);
    effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                                        MathHelper.ToRadians(45.0f),
                                        aspectRatio, 1.0f, 10.0f);
    effect.LightingEnabled = false;

    // Создание трехмерных осей
    Create3DAxis();
}

Стоит особо отметить назначение некоторых параметров в показанных функциях. В методе CreateLookAt вы создаете камеру, расположенную в двух единицах вверх (по оси Y) от точки (0, 0, 0) и в двух единицах вне экрана (по оси Z, отрицательные значения Z находятся внутри экрана, являясь видимыми, а положительные расположены вне экрана) «направленную на» вектор Zero (0 0, 0), и устанавливаете ось Y как «верх» с помощью Vector3.Up.

Затем вы создаете матрицу перспективной проекции с углом поля зрения равным 45 градусам. Визуализироваться будут объекты, расположенные от 1 до 10 единиц от экрана (значения Z от –1 до –10).

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

Метод UnloadContent также нуждается в завершении, путем включения кода для освобождения объекта эффекта, как показано в следующем фрагменте кода:

public void UnloadContent()
{
    if (vertexBuffer != null)
    {
        vertexBuffer.Dispose();
        vertexBuffer = null;
    }

    if (effect != null)
    {
        effect.Dispose();
        effect = null;
    }
}

Теперь, когда установлены буфер вершин и эффект, вам необходимо запрограммировать метод Draw класса cls3DAxis, который будет использовать эффект для рисования сцены, следуя шаблону кода, представленному ранее в разделе «Свет, камера... эффект!».

В следующем фрагменте кода вы конфигурируете устройство для использования формата вершин, который вы применяете (вершины определяются по их местоположению и цвету). Затем вы устанавливаете в качестве потокового источника вершин устройства ваш буфер вершин, определяя стартовую точку этого потока (начало чтения с первой вершины в буфере) и размер каждой вершины. Когда устройство сконфигурировано, вы входите в цикл рисования и вызываете device.DrawPrimitives для каждого прохода текущей техники эффекта (как объяснялось ранее в этой главе), объявляя, что вы рисуете 11 линий (составленных из 22 вершин).

public void Draw()
{
    // Создаем объявление вершин, которое будет использовано
    // при рисовании вершин
    device.VertexDeclaration = new VertexDeclaration(device,
                              VertexPositionColor.VertexElements);
    // Устанавливаем источник вершин
    device.Vertices[0].SetSource(vertexBuffer, 0,
                              VertexPositionColor.SizeInBytes);

    // Рисуем трехмерные оси
    effect.Begin();
    foreach(EffectPass CurrentPass in effect.CurrentTechnique.Passes)
    {
        CurrentPass.Begin();

        // Мы рисуем 22 вершины, сгруппированные в 11 линий 
        device.DrawPrimitives(PrimitiveType.LineList, 0, 11);

        CurrentPass.End();
    }
    effect.End();
}

Этот код завершает класс cls3DAxis. Теперь вам надо только вызвать методы этого класса из главного класса Game1, и вы сможете увидеть оси трехмерной системы координат. Вы сделаете это в следующем разделе.

Программирование главного класса программы

В предыдущем разделе вы создали класс cls3DAxis, который предоставляет методы с теми же именами, что и у главного класса программ XNA: LoadContent, UnloadContent и Draw.

Чтобы воспользоваться этим классом давайте теперь создадим новый пустой проект XNA Windows Game. Для вас будет автоматически сгенерирован класс Game1. Вам необходимо определить объект класса cls3DAxis, инициализировать его и вызывать соответствующие методы из класса Game1. Код обновленных методов таков:

GraphicsDeviceManager graphics;

// Трехмерный объект
cls3DAxis my3DAxis;

protected override void Initialize()
{
    my3DAxis = new cls3DAxis(graphics.GraphicsDevice);

    base.Initialize();
}

protected override void LoadContent()
{
    // Создание трехмерных осей
    my3DAxis.LoadGraphicsContent();
}

protected override void UnloadContent()
{
    // Освобождение ресурсов, выделенных для трехмерного рисования
    my3DAxis.UnloadContent();
}

protected override void Draw(GameTime gameTime)
{
    graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

    // Рисование трехмерных осей
    my3DAxis.Draw();

    base.Draw(gameTime);
}

На рис. 7.15 показан результат выполнения этого кода.


Рис. 7.15. Оси трехмерной системы координат

Рис. 7.15. Оси трехмерной системы координат


Результат, представленный на рис. 7.15, выглядит не совсем так, как ожидалось: вы можете видеть только оси X и Y и нет ничего, похожего на трехмерность... Это вызвано тем, что камера выровнена по оси Z и эта ось скрыта за осью Y, а буква Z не рисуется потому что находится позади камеры.

Вы можете просто настроить местоположение камеры в классе cls3DAxis, но мы поступим немного лучше, исследовав новое понятие: мировая матрица.

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

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

  1. Создайте новое свойство в классе cls3DAxis для хранения текущей мировой матрицы, которая по умолчанию будет единичной матрицей (матрицей, которая не выполняет никаких преобразований):

    public Matrix worldMatrix = Matrix.Identity;
    
  2. Добавьте новую строку в метод Draw этого класса для обновления свойства World эффекта значениями из этой матрицы, чтобы эффект получал обновленную матрицу и мог использовать ее для преобразования рисуемых осей:

    effect.World = worldMatrix;
    
  3. Добавьте новую строку в метод Update класса Game1 для обновления свойства worldMatrix класса cls3DAxis, увеличивающего мировой угол поворота при каждом обновлении:

    my3DAxis.worldMatrix *= Matrix.CreateRotationY(0.01f) *
                                       Matrix.CreateRotationX(0.01f);
    

Запустите вашу программу снова, и вы увидите прелестный результат в виде вращающейся трехмерной системы координат, как показано на рис. 7.16.


Рис. 7.15. Вращающаяся трехмерная система координат

Рис. 7.15. Вращающаяся трехмерная система координат



netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz