Украина, Киев |
Более совершенные технологии рендеринга в 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()
// Определение индексного массива вершин треугольных примитивов 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. Синтаксис функции рисования
Здесь мы не сможем сделать каждую отдельную грань одноцветной, поскольку цвет примитива берется из параметров вершины, а у нас заданы цвета вершин куба, к которым примыкают сразу несколько примитивов разных граней. Но на данный момент так даже красивее!!!