Опубликован: 13.07.2010 | Доступ: свободный | Студентов: 889 / 20 | Оценка: 4.40 / 4.20 | Длительность: 77:34:00
Самостоятельная работа 13:

Более совершенные технологии рендеринга в DirectX

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

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


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

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

Массив с индексами опорных точек объекта, записанных в вершинном буфере, называется индексным буфером. Индексы, сохраняемые в таком буфере, могут быть 16- или 32-разрядными целыми числами. Давайте используем индексный буфер для моделирования вращающегося треугольника.

  • Добавьте к текущему проекту форму Form1 из лабораторной работы №12. Для этого временно переименуйте через панель Solution Explorer файл Form1.cs текущего проекта и в меню Project (при выделенном узле проекта в панели Solution Explorer ) выполните команду Add Existing Item

  • После копирования файла Form1.cs в текущий каталог проекта присвойте ему через панель Solution Explorer имя Form2.cs, а временно переименованному файлу верните первоначальное имя Form1.cs
  • Откройте файл Form2.cs в режиме кода и замените в нем все вхождения Form1 на Form2, вызвав в редакторе кода окно замены комбинацией клавиш Ctrl-H


  • Не закрывая окна Find and Replace откройте файл Form2.designer.cs и тоже замените в нем все вхождения Form1 на Form2
  • Не закрывая окна Find and Replace установите в нем опцию Look in в значение Current Project и замените все вхождения пространства имен RenderCube на RenderBest, выполнив команду Replace All


  • Проследите за тем, чтобы в обеих файлах было использовано одно и то же пространство имен, равное имени текущего проекта ( namespace RenderBest )

Теперь нужно установить форму Form2 стартовой для возможности ее начального запуска. Для этого:

  • В панели Solution Explorer выделите узел проекта и щелкните на пиктограмме Properties, чтобы вызвать окно свойств проекта


  • Во вкладке Application окна свойств проекта установите опцию Startup object в значение RenderBest.Form2

  • Запустите проект на выполнение, чтобы проверить начальную работоспособность Form2

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

  • Откройте файл Form2.cs в режиме View Code и через раскрывающийся список Members правой верхней части окна редактора позиционируйтесь на функцию vb_Created() класса Form2
  • Введите в тело функции код определения вершин куба (у нашего куба 8 вершин) в соответствии с рисунком


void vb_Created(object sender, EventArgs e)
        {
            // Определить внутреннюю ссылку на вершинный буфер
            VertexBuffer buffer = (VertexBuffer)sender; // Явное приведение типов
    
            // Создать локальный массив структур непреобразованных координат
            CustomVertex.PositionColored[] verts = 
                new CustomVertex.PositionColored[8]; // Задать размерность массива на 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);
        }
Листинг 13.10. Тело функции vb_Created() класса Form2

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

  • Найдите в функции InitializeGraphics() секцию создания вершинного буфера и измените его размерность с 36 на 8 (у нашего куба 8 вершин)
// Создать вершинный буфер
      vb = new VertexBuffer(typeof(CustomVertex.PositionColored),
                          8, 
                          device, 
                          Usage.Dynamic | Usage.WriteOnly,
                          CustomVertex.PositionColored.Format,
                          Pool.Default);
Листинг 13.11. Новая размерность вершинного буфера в функции InitializeGraphics()
  • Добавьте в класс Form2 поле для индексного массива 16-разрядных целых чисел (тип short или Int16 )
// Определение индексного массива вершин треугольных примитивов
        private static readonly short[] indices = {
            2,0,1,// Задняя грань:    треугольник 2-0-1 (невидимая сторона - обход левый)
            2,1,3,// Задняя грань:    треугольник 2-1-3 (невидимая сторона - обход левый)
            5,6,4,// Передняя грань:  треугольник 5-6-4 (лицевая сторона - обход правый)
            5,7,6,// Передняя грань:  треугольник 5-7-6 (лицевая сторона - обход правый)
            0,5,4,// Верхняя грань:   треугольник 0-5-4 (лицевая сторона - обход правый)
            0,2,5,// Верхняя грань:   треугольник 0-2-5 (лицевая сторона - обход правый)
            1,6,7,// Нижняя грань:    треугольник 1-6-7 (невидимая сторона - обход левый)
            1,7,3,// Нижняя грань:    треугольник 1-7-3 (невидимая сторона - обход левый)
            0,6,1,// Левая грань:     треугольник 0-6-1 (невидимая сторона - обход левый)
            0,4,6,// Левая грань:     треугольник 0-4-6 (невидимая сторона - обход левый)
            2,3,7,// Правая грань:    треугольник 2-3-7 (лицевая сторона - обход правый)
            2,7,5 // Правая грань:    треугольник 2-7-5 (лицевая сторона - обход правый)
    };
Листинг 13.12. Определение индексного массива-поля класса Form2
  • В классе Form2 объявите поле типа Microsoft.DirectX.Direct3D. IndexBuffer с именем ib для хранения ссылки на индексный буфер. Ее удобнее разместить рядом со ссылкой на вершинный буфер
private Device device = null;
    private VertexBuffer vb = null;
    private IndexBuffer ib = null;
  
    public void InitializeGraphics()
    {
    .....................
    }
Листинг 13.13. Объявление ссылочной переменной на индексный буфер

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

  • Разместите в самом конце функции InitializeGraphics() (после кода создания вершинного буфера) код создания индексного буфера и подписку на событие Created. Подписку на событие Created выполните вручную, чтобы оболочка правильно создала заготовку обработчика
public void InitializeGraphics()
    {
      // Создание объекта и настройка параметров представления
      // Создать объект параметров представления
      PresentParameters presentParams = new PresentParameters();
      // Установить оконный режим
      presentParams.Windowed = true;
      // Сбрасывать содержимое буфера, если он не готов к представлению
      presentParams.SwapEffect = SwapEffect.Discard;
      // Создать объект устройства и сохранить ссылку на него
      device = new Device(0, DeviceType.Hardware, this,
        CreateFlags.SoftwareVertexProcessing, presentParams);
    
      // Создать вершинный буфер
      vb = new VertexBuffer(typeof(CustomVertex.PositionColored),
                          8, 
                          device, 
                          Usage.Dynamic | Usage.WriteOnly,
                          CustomVertex.PositionColored.Format,
                          Pool.Default);
  
      // Регистрация события Created вершинного буфера
      vb.Created += new EventHandler(vb_Created);
  
      // Принудительный вызов создания треугольника 
      // и заполнения вершинного буфера при первом 
      // создании формы
      vb_Created(vb, null);
  
      // Создать индексный буфер для координат вершин куба
      ib = new IndexBuffer(typeof(short),
                         indices.Length,
                         device,
                         Usage.WriteOnly,
                         Pool.Default);
  
      // Регистрация события Created индексного буфера
      // Код написать вручную (для создания обработчика)!!!
      ib.Created += new EventHandler(ib_Created);
  
      // Принудительный вызов обработчика при первом запуске
      ib_Created(ib, null);
    }
Листинг 13.14. Создание индексного буфера в функции InitializeGraphics()
  • Код обработчик заполнения индексного буфера после сброса будет таким
// Обработчик события повторного заполнения индексного буфера
        void ib_Created(object sender, EventArgs e)
        {
            IndexBuffer buffer = (IndexBuffer)sender;
            buffer.SetData(indices, 0, LockFlags.None);
        }
Листинг 13.15. Обработчик повторного заполнения индексного буфера после сброса

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

  • Установите в функции SetupCamera() новую величину приращения угла, определяющую меньшую скорость вращения куба
if (flagRotate)
                angle += 0.02F;
Листинг 13.16. Приращение угла поворота куба в SetupCamera()
  • В функции OnPaint() найдите секцию кода "Сформировать сцену" и внесите в нее регистрацию индексного буфера в устройстве и вызов функции DrawIndexedPrimitives(), которая при рисовании использует индексный буфер
// Сформировать сцену с учетом параметров 
            // "положение-нормаль-цвет"
            device.BeginScene();
            // Установить формат обработки вершин при отображении
            device.VertexFormat = CustomVertex.PositionColored.Format;
            // Нарисовать треугольник по данным из буфера
            device.SetStreamSource(0, vb, 0);
            device.Indices = ib;
            device.DrawIndexedPrimitives(
                PrimitiveType.TriangleList, 0, 0, 8, 0, indices.Length / 3);
            device.EndScene();
Листинг 13.17. Регистрация индексного буфера в устройстве

Функция рисования, которую мы использовали, имеет следующий синтаксис

public void DrawIndexedPrimitives(
  Microsoft.DirectX.Direct3D.PrimitiveType primitiveType, // Чем рисуем
  System.Int32 baseVertex, // Смещение от начало буфера до индекса первой вершины
  System.Int32 minVertexIndex, // Минимальный индекс вершины в текущем вызове
  System.Int32 numVertices, // Число вершин, используемых в текущем вызове
  System.Int32 startIndex, // Стартовый индекс для считывания и отображения
  System.Int32 primCount) // Число отображаемых примитивов
Листинг 13.18. Синтаксис функции рисования
  • Постройте приложение и получите вращающийся куб с красочными градиентами цветов на гранях


Здесь мы не сможем сделать каждую отдельную грань одноцветной, поскольку цвет примитива берется из параметров вершины, а у нас заданы цвета вершин куба, к которым примыкают сразу несколько примитивов разных граней. Но на данный момент так даже красивее!!!