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

Использование буфера индексов

Вспомните наше первое приложение, визуализирующее куб, где мы создавали данные для 36 вершин. Там было по два треугольника для каждой грани куба; 6 граней умноженные на 2 треугольника дают 12 примитивов. Поскольку в каждом примитиве три вершины, всего вершин 36. Однако, в реальности используется всего 8 различных вершин; по одной для каждого угла куба.

В таких небольших приложениях многократное хранение данных одной и той же вершины может казаться не заслуживающим внимания, но в крупномасштабных приложениях, где приходится хранить значительные объемы данных вершин, уменьшение требуемого объема памяти за счет исключения дублирования данных является хорошей идеей. К счастью, в Direct3D есть механизм для совместного использования данных вершин в примитивах, называемый буфер индексов.

Как говорится в названии, буфер индексов (index buffer) — это буфер, в котором хранятся индексы данных вершин. Хранящиеся в буфере индексы могут быть 32-разрядными (целые числа) или 16-разрядными (короткие целые).Если вам не требуется много индексов, используйте короткий формат, поскольку он занимает в два раза меньше места.

Когда для визуализации примитивов используется буфер индексов, каждый индекс в нем соответствует данным вершины, хранящимся в буфере вершин. Например, если вы визуализируете отдельный треугольник с индексами 0, 1, 6, он будет нарисован с использованием вершин, соответствующих этим индексам. Давайте изменим рисующее кубы приложение для использования индексов. Сначала модифицируем функцию создания данных вершин, как показано в листинге 4.3:

Листинг 4.3. Создание вершин для нашего куба

vb = new VertexBuffer(typeof(CustomVertex.PositionColored), 8,
                      device, Usage.Dynamic | Usage.WriteOnly,
                      CustomVertex.PositionColored.Format, Pool.Default);

CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[8];

// Вершины
verts[0] = new CustomVertex.PositionColored(-1.0f,  1.0f,  1.0f,
                                            Color.Purple.ToArgb());
verts[1] = new CustomVertex.PositionColored(-1.0f, -1.0f,  1.0f,
                                            Color.Red.ToArgb());
verts[2] = new CustomVertex.PositionColored( 1.0f,  1.0f,  1.0f,
                                            Color.Blue.ToArgb());
verts[3] = new CustomVertex.PositionColored( 1.0f, -1.0f,  1.0f,
                                            Color.Yellow.ToArgb());
verts[4] = new CustomVertex.PositionColored(-1.0f,  1.0f, -1.0f,
                                            Color.Gold.ToArgb());
verts[5] = new CustomVertex.PositionColored( 1.0f,  1.0f, -1.0f,
                                            Color.Green.ToArgb());
verts[6] = new CustomVertex.PositionColored(-1.0f, -1.0f, -1.0f,
                                            Color.Black.ToArgb());
verts[7] = new CustomVertex.PositionColored( 1.0f, -1.0f, -1.0f,
                                            Color.WhiteSmoke.ToArgb());
buffer.SetData(verts, 0, LockFlags.None);

Как видите, объем ранных вершин резко уменьшился; теперь хранятся только 8 вершин, являющихся углами куба. Мы по прежнему хотим нарисовать 36 вершин, составляя различные комбинации из этих восьми. Вершины заданы, и нам надо 36 индексов этих вершин, используемых чтобы мы смогли нарисовать наш куб. Взглянув на предыдущее приложение мы можем сравнить каждую из 36 используемых вершин и найти соответствующий ей индекс в новом списке. Добавьте список индексов, показанный в листинге 4.4, к разделу объявлений:

Листинг 4.4. Данные буфера индексов куба

private static readonly short[] indices = {
    0, 1, 2, // Передняя грань
    1, 3, 2, // Передняя грань
    4, 5, 6, // Задняя грань
    6, 5, 7, // Задняя грань
    0, 5, 4, // Верхняя грань
    0, 2, 5, // Верхняя грань
    1, 6, 7, // Нижняя грань
    1, 7, 3, // Нижняя грань
    0, 6, 1, // Левая грань
    4, 6, 0, // Левая грань
    2, 3, 7, // Правая грань
    5, 2, 7  // Правая грань
};

Для простоты восприятия список индексов разделен на группы по три, соответствующие каждому рисуемому треугольнику. Как видите, передняя грань образована двумя треугольниками. Первый треугольник использует вершины 0, 1 и 2, а второй треугольник использует вершины 1, 3 и 2. Аналогично, у правой грани первый треугольник использует вершины 2, 3 и 7, а второй треугольник использует вершины 5, 2 и 7. Правила отбрасывания невидимых граней точно так же действуют и при использовании индексов.

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

private IndexBuffer ib = null;

Этот объект будет использоваться для хранения наших индексов и предоставления к ним доступа Direct3D. Объект очень похож на уже созданный нами объект буфера вершин, просто он будет хранить индексы. Давайте инициализируем этот объект и заполним его данными. Добавьте код из листинга 4.5 после кода инициализации буфера вершин:

Листинг 4.5. Создание буфера индексов

ib = new IndexBuffer(typeof(short), indices.Length, device,
                     Usage.WriteOnly, Pool.Default);
ib.Created += new EventHandler(this.OnIndexBufferCreate);
OnIndexBufferCreate(ib, null);

private void OnIndexBufferCreate(object sender, EventArgs e)
{
    IndexBuffer buffer = (IndexBuffer)sender;
    buffer.SetData(indices, 0, LockFlags.None);
}

Заметьте, что конструктор буфера индексов с зеркальной точностью повторяет конструктор буфера вершин. Единственное различие заключается в ограничениях на типы параметров. Как упоминалось ранее, для данных индексов можно использовать только типы short (System.Int16) и int (System.Int32). Мы также перехватываем событие создания буфера и вызываем функцию обработчика при первом запуске. В обработчике мы просто заполняем буфер индексов необходимыми данными.

ЭКОНОМИЯ ПАМЯТИ С БУФЕРАМИ ИНДЕКСОВ
Действительно ли применение буфера индексов экономит память нашего приложения? Давайте сравним два варианта относительно простого приложения визуализации куба — без буфера индексов и с ним. В первом сценарии мы создаем буфер вершин, хранящий 32 вершины типа CustomVertex.PositionColored. Эта структура занимает 16 байт (по 4 байта для координат x, y, z и цвета). Умножив размер на количество вершин вы увидите, что данные вершин занимают 576 байт.
Теперь сравним этот результат с методом, использующим буфер индексов. Пы используем только 8 вершин (того же самого типа), поэтому данные вершин занимают 128 байт. Однако, нам надо хранить так же и индексы, сколько места займут они? Мы используем короткие индексы (2 байта на каждый), а всего их 36. Так что данные индексов занимают 72 байта, что в совокупности с данными вершин дает нам общий размер в 200 байт.
Сравнив это с исходным размером в 576 байт, вы увидите 65-процентное сокращение объема используемой памяти. Экстраполировав эти значения на большие сцены, вы увидите, что экономия памяти при использовании буферов индексов может быть весьма существенной.

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

device.Indices = ib;

Теперь Direct3D знает о нашем буфере индексов, и нам надо изменить вызов функции рисования. Сейчас функция рисования пытается нарисовать 12 примитивов (36 вершин) из нашего буфера вершин, что, естественно, приведет к сбою, поскольку буфер содержит только 8 вершин. Добавьте в код функцию DrawBox:

private void DrawBox(float yaw, float pitch, float roll,
                     float x, float y, float z)
{
    angle += 0.01f;

    device.Transform.World = Matrix.RotationYawPitchRoll(yaw, pitch, roll) *
                             Matrix.Translation(x, y, z);
    device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0,
                                 indices.Length / 3);
}

Мы изменили функцию рисования с DrawPrimitives на DrawIndexedPrimitives. Давайте взглянем на прототип этой функции:

public void DrawIndexedPrimitives (Microsoft.DirectX.Direct3D.PrimitiveType primitiveType,
                                   System.Int32 baseVertex, System.Int32 minVertexIndex,
                                   System.Int32 numVertices, System.Int32 startIndex,
                                   System.Int32 primCount)

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

Итак, как видите, мы просто рисуем наши 8 вершин, используя буфер индексов для визуализации 12 примитивов нашего куба. Теперь давайте уберем вызовы DrawPrimitives и заменим их на вызовы нашего метода DrawBox, как показано в листинге 4.6.

Листинг 4.6. Рисование кубов

// Рисуем наши кубы
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f,
        angle / (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f,
        angle / (float)Math.PI * 4.0f, 5.0f, 0.0f, 0.0f);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.0f,
        angle / (float)Math.PI / 2.0f, -5.0f, 0.0f, 0.0f);

DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f,
        angle / (float)Math.PI / 4.0f, 0.0f, -5.0f, 0.0f);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f,
        angle / (float)Math.PI * 4.0f, 5.0f, -5.0f, 0.0f);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.0f,
        angle / (float)Math.PI / 2.0f, -5.0f, -5.0f, 0.0f);

DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f,
        angle / (float)Math.PI / 4.0f, 0.0f, 5.0f, 0.0f);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f,
        angle / (float)Math.PI * 4.0f, 5.0f, 5.0f, 0.0f);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.0f,
        angle / (float)Math.PI / 2.0f, -5.0f, 5.0f, 0.0f);

Запущенное приложение визуализирует красочные вращающиеся кубы.

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


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

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