Более совершенные технологии рендеринга в DirectX
Рендеринг различных типов примитивов
В качестве основы для дальнейших изменений мы взяли из лабораторной работы №12 код построения одного куба без текстурирования сторон, размещенный в файле Form1.cs. Мы уже присоединили этот файл к новому проекту (оболочка физически скопировала этот файл в каталог размещения текущего проекта). Теперь мы усовершенствуем файл Form1.cs, чтобы продемонстрировать отображение одного и того же набора вершин с помощью различных примитивов. Испытания примитивов не предполагают вращения рисуемых объектов, поэтому в функции SetupCamera() необходимо удалить вращение вершин и угол поворота.
- В функции SetupCamera() удалите две последних секции кода, отвечающих за повороты объекта и приращение угла поворота, а также объявление самой переменной-поля angle
private float angle = 0;// Закрытый член класса // Установка камеры в сцену private void SetupCamera() { //Создание перспективы device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI / 4, // Угол зрения равен 45 градусов // Форматное соотношение сторон (float)this.ClientSize.Width / (float)this.ClientSize.Height, 1.0F, // Ближний план 100.0F); // Дальний план //Добавление камеры device.Transform.View = Matrix.LookAtLH( new Vector3(0, 0, 5.0F), // Положение камеры new Vector3(), // Положение объекта текущее new Vector3(0, 1, 0)); // Направление камеры // Освещение device.RenderState.Lighting = false; device.Transform.World = Matrix.RotationYawPitchRoll( angle / (float)Math.PI, angle / (float)Math.PI * 2.0F, angle / (float)Math.PI); if (flagRotate) angle += 0.1F; }Листинг 13.1. Удаления вращений в коде функции SetupCamera()
- Удалите из класса Form1 сначала подписку обработчика на событие Click формы, затем обработчик Form1_Click(), а также описание ненужного теперь флага flagRotate
public Form1() { InitializeComponent(); // Устраняем мерцание окна при бесконечной перерисовке this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); this.Click += new EventHandler(Form1_Click); } bool flagRotate = true; void Form1_Click(object sender, EventArgs e) { flagRotate = !flagRotate; }Листинг 13.2. Удаление обработчика остановки вращения и его регистрации
- Добавьте в класс объявление поля-ссылки NumberItems на количество вершин случайно генерируемого объекта, который мы будем отображать с применением разных примитивов
private Device device = null; // Графический конвейер private VertexBuffer vb = null; // Вершинный буфер private const int NumberItems = 12; // Количество вершин объектаЛистинг 13.3. Константная переменная класса Form1 для задания количества вершин
Мы выбрали количество вершин 12 потому, что это число четное, делится на 3 (что подходит для примитивов LineList и TriangleList ) и не настолько большое, чтобы загромождать рисунок. Объявление переменной как константы поручает компилятору следить за тем, чтобы мы случайно где-нибудь в коде не попытались ее изменить.
- Измените в функции InitializeGraphics() размерность вершинного буфера, заменив 36 на NumberItems
public void InitializeGraphics() { .................................. // Создать вершинный буфер vb = new VertexBuffer(typeof(CustomVertex.PositionColored), NumberItems, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default); .................................. }Листинг 13.4. Новая размерность вершинного буфера в функции InitializeGraphics()
Теперь изменим алгоритм задания координат вершин и заполнения вершинного буфера в функции vb_Created(). Мы применим средства случайного формирования координат вершин и их цветов.
- Объявите в составе класса Form1 ссылочную переменную-поле на генератор случайных чисел по равномерному закону распределения, а в конструкторе Form1() разместите код создания и инициализации этого генератора случайных чисел
public Form1() { InitializeComponent(); // Устраняем мерцание окна при бесконечной перерисовке this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); // Инициализация генератора случайных чисел rand = new System.Random(); } private Device device = null; // Графический конвейер private VertexBuffer vb = null; // Вершинный буфер private const int NumberItems = 12; // Количество вершин объекта System.Random rand; // Поле для генератора случайных чиселЛистинг 13.5. Добавление генератора случайных чисел в файле Form1.cs
- Измените код создания вершин объекта в функции vb_Created()
void vb_Created(object sender, EventArgs e) { // Определить внутреннюю ссылку на вершинный буфер VertexBuffer buffer = (VertexBuffer)sender; // Явное приведение типов // Создать локальный массив ссылок для вершин объекта CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[NumberItems]; // Сгенерировать случайно координаты и цвета вершин double factor = 2.5d;// Весовой коэффициент for (int i = 0; i < NumberItems; i++) { float xPos = (float)((rand.NextDouble() - rand.NextDouble()) * factor); float yPos = (float)((rand.NextDouble() - rand.NextDouble()) * factor); float zPos = (float)((rand.NextDouble() - rand.NextDouble()) * factor); verts[i].SetPosition(new Vector3(xPos, yPos, zPos)); verts[i].Color = System.Drawing.Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256)).ToArgb(); } // Заполнить вершинный буфер данными треугольников buffer.SetData(verts, 0, LockFlags.None); }Листинг 13.6. Создание вершин со случайными параметрами в функции vb_Created()
Организовать просмотр работы различных примитивов можно последовательным их перебором с достаточной задержкой. Саму задержку можно измерять как разницу во временных тактах процессора между текущим временем и временем запуска приложения и на это время отображать объект одним и тем же примитивом. После завершения очередного цикла перебора всех примитивов необходимо сгенерировать новые параметры вершинного буфера, чтобы случайно изменить рисуемый объект.
- Добавьте в класс Form1 объявление двух новых полей, которые для читабельности удобнее расположить перед функцией OnPaint()
// Флаг регенерации вершин перед началом // каждого нового цикла обхода всех примитивов. // Первое заполнение вершинного буфера выполнено // в InitializeGraphics() при запуске приложения private bool needRecreate = false; // Захват стартового времени в тактах private static readonly int beginTickCount = System.Environment.TickCount; protected override void OnPaint(PaintEventArgs e) { ........................................ }Листинг 13.7. Добавление новых полей в класс Form1
Переменная beginTickCount объявлена как статическая для того, чтобы ее данные хранились в объекте-типе, а не объекте-экземпляре, на случай, если будет создано несколько экземпляров класса Form1. Ключевое слово readonly делает ее доступной "только для чтения". В отличие от константных переменных, требующих немедленной инициализации при объявлении, переменные "только для чтения" допускают отложенную инициализацию в конструкторе класса.
- В функции OnPaint() замените вызов библиотечной функции DrawPrimitives() в секции кода формирования сцены и цвет фона формы выделенным ниже блоком кода, чтобы окончательно функция выглядела так
// Флаг регенерации вершин перед началом // каждого нового цикла обхода всех примитивов. // Первое заполнение вершинного буфера выполнено // в InitializeGraphics() при запуске приложения private bool needRecreate = false; // Захват стартового времени в тактах private static readonly int beginTickCount = System.Environment.TickCount; protected override void OnPaint(PaintEventArgs e) { // Очистить цветом клиентскую область формы device.Clear(ClearFlags.Target, System.Drawing.Color.White, 1.0F, 0); // Вызов нашей функции установки камеры SetupCamera(); // Сформировать сцену с учетом параметров // "положение-нормаль-цвет" device.BeginScene(); // Установить формат обработки вершин при отображении device.VertexFormat = CustomVertex.PositionColored.Format; // Нарисовать треугольник по данным из буфера device.SetStreamSource(0, vb, 0); const int countTime = 5 * 1000; // Держать изображение 5 секунд const int countIndex = 6; // Количество вариантов показа int index = ((System.Environment.TickCount - beginTickCount) / countTime) % countIndex; switch (index) { case 0: // PointList device.DrawPrimitives(PrimitiveType.PointList, 0, NumberItems); if (needRecreate)// Выполнить только при очередном index == 0 { vb_Created(vb, null);// Сгенерировать новый вершинный буфер needRecreate = false;// Сбросить флаг регенерации вершин } this.Text = "Несоединенные точки вершин"; break; case 1: // LineList device.DrawPrimitives(PrimitiveType.LineList, 0, NumberItems / 2); needRecreate = true;// Приготовить флаг для регенерации вершин при index == 0 this.Text = "Соединие пар вершин линиями"; break; case 2: // LineStrip device.DrawPrimitives(PrimitiveType.LineStrip, 0, NumberItems - 1); this.Text = "Соединение вершин ломаной"; break; case 3: // TriangleList device.DrawPrimitives(PrimitiveType.TriangleList, 0, NumberItems / 3); this.Text = "Отдельные треугольники"; break; case 4: // TriangleStrip device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, NumberItems - 2); this.Text = "Непрерывная ферма треугольников"; break; case 5: // TriangleFan device.DrawPrimitives(PrimitiveType.TriangleFan, 0, NumberItems - 2); this.Text = "Треугольный веер из точки"; break; } device.EndScene(); // Показать буфер кадра device.Present(); // Принудительно перерисовать this.Invalidate(); }Листинг 13.8. Рендеринг различных типов примитивов в функции OnPaint()
- Добавьте в конец функции SetupCamera() код изменения состояния рендера для увеличения размера отображаемых точек вершин, чтобы их можно было различить в нулевом режиме PointList
// Установка камеры в сцену private void SetupCamera() { //Создание перспективы device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI / 4, // Угол зрения равен 45 градусов // Форматное соотношение сторон (float)this.ClientSize.Width / (float)this.ClientSize.Height, 1.0F, // Ближний план 100.0F); // Дальний план //Добавление камеры device.Transform.View = Matrix.LookAtLH( new Vector3(0, 0, 5.0F), // Положение камеры new Vector3(), // Положение объекта текущее new Vector3(0, 1, 0)); // Направление камеры // Освещение device.RenderState.Lighting = false; // Увеличение масштаба отображения точек вершин объекта device.RenderState.PointSize = 5.0f; }Листинг 13.9. Увеличение масштаба отображения точек в функции SetupCamera()
- Постройте приложение и убедитесь, что форма Form1 периодически генерирует новые объекты и отображает их разными примитивами