Практикум прикладного программирования на C# в среде VS.NET 2005

Введение в DirectX

Показывать лекцию целиком

Файлы к лабораторной работе Вы можете скачать здесь.

Библиотека классов DirectX является расширением базовой библиотеки .NET Framework и представляет собой новую поддержку современной технологии программирования, разработанной корпорацией Microsoft. Сама технология основывается на трех положениях:

  • Производители аппаратного обеспечения поддерживают технологию DirectX
  • Операционная система Windows поддерживает технологию DirectX
  • Пакет разработчика DirectX 9.0 SDK Update ( Summer 2003 ) от Microsoft содержит большой набор готовых интерфейсов, классов, функций, макросов, структур и констант, значительно упрощающих технологию разработки компьютерных игр для операционной системы Windows
  • DirectX 9.0 предоставляет возможность программисту разрабатывать приложения, осуществляющие работу со звуком, сетью, трехмерной графикой, видео и построен в виде набора компонентов. Все компоненты пакета DirectX распределены по пространствам имен

    Пространства имен управляемого DirectX
    Пространство имен Пояснения
    Microsoft.DirectX Корневой каталог, содержащий все общие коды.
    Microsoft.DirectX.Direct3D DirectDraw3D API объемная графика, также как вспомогательная библиотека D3DX.
    Microsoft.DirectX.DirectDraw Графика DirectDraw API. DirectDraw позволяет рисовать на экране двухмерную графику, однако этот компонент давно не обновлялся и его текущая версия числится под номером 7 (DirectDraw 7). В настоящее время Microsoft заморозила его поддержку и рекомендует пользоваться Direct3D, который тоже может рисовать двухмерную графику.
    Microsoft.DirectX.DirectPlay Обеспечивает работу приложения с сетью и основан на своем протоколе обмена информацией.
    Microsoft.DirectX.DirectSound Отвечают за музыку и звук, делая возможным воспроизведение MIDI- и WAV-файлов. Текущая версия числится под номером 8.
    Microsoft.DirectX.DirectInput Пользовательский вход в DirectInput API. DirectInput обеспечивает взаимодействие программы с устройствами ввода, а именно с клавиатурой, мышью, джойстиком и рулем. Он обеспечивает максимально возможную скорость в играх, работая напрямую с аппаратным обеспечением компьютера в обход сервисов операционной системы. Стандартные средства Win32 не в силах обеспечить такую скорость. Корпорация Microsoft приняла решение пока не обновлять DirectInput, поскольку заложенного в него потенциала хватит на несколько ближайших лет.
    Microsoft.DirectX.AudioVideoPlayback API простого звукового и видео воспроизведения.
    Microsoft.DirectX.Diagnostics Простая диагностика API.
    Microsoft.DirectX.Security Контроль доступа на глубокие уровни DirectX.
    Microsoft.DirectX.Security.Permissions Классы разрешения для защиты доступа.

    Установка Microsoft R DirectX R 9.0 SDK Update (Summer 2003)

    Устанавливаемый пакет является расширением среды разработчика (IDE) Visual Studio .NET 2005, поэтому прежде всего на вашем компьютере должна быть эта среда.

  • Вставте диск с пакетом DirectX и запустите программу Install.exe
  • Выполните пункт Install DirectX 9.0 SDK
  • Следуйте инструкциям мастера установки
  • Пакет установки возьмите у преподавателя или купите на рынке.

    Создание заготовки приложения

    Далее мы будем создавать приложение на основе управляемого DirectX ( Managed DirectX ).

  • Выберите папку размещения файлов проекта и создайте проект C# с именем SimpleMedia
  • Удалите ссылки на ненужные нам на данном этапе библиотеки
  • Используя меню Project, выполните команду Add Reference и добавьте к проекту нужные нам ссылки на сборки пакета DirectX
  • Откройте файл Form1.cs в режиме View Code, добавьте в начало файла ссылки на пространства имен только что добавленных библиотек и закомментируйте ссылки на System.Data и System.Text
  • //using System.Data;
    //using System.Text;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows.Forms;
    using Microsoft.DirectX;
    using Microsoft.DirectX.Direct3D;
        
    namespace SimpleMedia
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
        }
    }

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

    Пространство имен Microsoft.DirectX.Direct3D

    Для рисования графики на форме мы будем использовать компоненты, расположенные в пространстве имен Microsoft.DirectX.Direct3D

    Непосредственное выполнение всех графических операций осуществляется классом Device (устройство). Условно можно представить, что для программиста этот класс аналогичен графическому устройству и графической карте, установленной на компьютере.

    Существуют три перегруженных версии конструктора класса устройства Microsoft.DirectX.Direct3D.Device

  • public Device ( System.IntPtr lp )
  • public Device ( System.Int32 adapter, Microsoft.DirectX.Direct3D.DeviceType deviceType, System.Windows.Forms.Control renderWindow, Microsoft.DirectX.Direct3D.CreateFlags behaviorFlags, Microsoft.DirectX.Direct3D.PresentParameters presentationParameters )
  • public Device ( System.Int32 adapter , Microsoft.DirectX.Direct3D.DeviceType deviceType , System.IntPtr renderWindowHandle , Microsoft.DirectX.Direct3D.CreateFlags behaviorFlags , Microsoft.DirectX.Direct3D.PresentParameters presentationParameters )
  • Пока мы будем использовать только вторую версию конструктора. Рассмотрим параметры этого конструктора:

  • adapter - относится к тому физическому устройству, которое мы хотим классифицировать. Каждое устройство в компьютере имеет идентификатор адаптера (от 0 до числа, на единицу меньшего, чем число имеющихся устройств). Нулевое значение для параметра adapter подразумевает установку устройства по умолчанию.
  • deviceType (тип устройства) - сообщает приложению, какой тип устройства мы хотим создать. Значением этого параметра является одно из полей структуры Microsoft.DirectX.Direct3D.DeviceType
  • Microsoft.DirectX.Direct3D.DeviceType.Hardware
  • Microsoft.DirectX.Direct3D.DeviceType.Reference
  • Microsoft.DirectX.Direct3D.DeviceType.Software
  • Обычно применяется опция DeviceType.Hardware, подразумевающая использование аппаратного устройства. Опция DeviceType.Reference позволяет использовать дополнительное или эмулированное устройство растеризации (rasterizer), реализующее все возможности Direct3D Realtime, но работающее чрезвычайно медленно. Эту опцию целесообразно использовать для отладки программ или для проверки параметров приложений, не поддерживаемых видеокартой разработчика. Опция DeviceType.Software позволяет использовать программный rasterizer.
  • renderWindow связывает окно с созданным устройством, назначая его в качестве окна рендеринга (выдачи результата). Для этого параметра можно использовать форму, панель или любой другой производный от класса Control класс. На данном этапе мы будем использовать только формы.
  • behaviorFlags (флажки режимов) - используется для управления режимами устройства после его создания. Может комбинироваться операцией поразрядного сложения "|", что позволяет установить множественные режимы работы устройства. Некоторые из этих флажков являются взаимоисключающими. Значения параметра определяются перечислением Microsoft.DirectX.Direct3D. CreateFlags:
  • Microsoft.DirectX.Direct3D.CreateFlags.AdapterGroupDevice
  • Microsoft.DirectX.Direct3D.CreateFlags.DisableDriverManagement
  • Microsoft.DirectX.Direct3D.CreateFlags.FpuPreserve
  • Microsoft.DirectX.Direct3D.CreateFlags.HardwareVertexProcessing
  • Microsoft.DirectX.Direct3D.CreateFlags.MixedVertexProcessing
  • Microsoft.DirectX.Direct3D.CreateFlags.MultiThreaded
  • Microsoft.DirectX.Direct3D.CreateFlags.PureDevice
  • Microsoft.DirectX.Direct3D.CreateFlags.SoftwareVertexProcessing
  • Пока мы будем использовать флажок SoftwareVertexProcessing, передающий всю обработку вершин центральному процессору. Несмотря на то, что таким образом операции выполняются медленнее, чем при использовании графического процессора, надежнее будет использовать именно такой подход, поскольку не всегда известно наверняка, поддерживает ли графическая плата пользователя режим графического процессора.
  • presentationParameters - управляет представлением данных на экране. Посредством этого параметра можно управлять любыми параметрами отображаемых данных. Параметр представляет собой экземпляр класса Microsoft.DirectX.Direct3D.PresentParameters, имеющего, в частности, свойства
  • Microsoft.DirectX.Direct3D.PresentParameters.Windowed - является логической переменной, определяющей, работает ли устройство в полноэкранном режиме (значение false ) или в оконном режиме (значение true )
  • Microsoft.DirectX.Direct3D.PresentParameters.SwapEffect - используется для описания режима буферной подкачки
  • SwapEffect.Discard - сбрасывает содержимое буфера, если он не готов к представлению
  • SwapEffect.Copy - сходно со значением Flip, но требует от программиста установки значения 1 для числа вторичных буферов
  • SwapEffect.Flip - будет создан вторичный буфер и произойдет копирование в него из первичного буфера независимо от состояния последнего
  • Далее мы воспользуемся опцией SwapEffect.Discard.

    Создание устройства

    Теперь у нас достаточно информации для создания устройства в нашем проекте.

  • В конец класса формы включите объявление ссылки на экземпляр класса Device
  • namespace SimpleMedia
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
        
            private Device device = null;
        }
    }
  • Добавьте новую функцию-член в конец класса Form1 с именем InitializeGraphics(), которую наполните следующим кодом
  • public void InitializeGraphics()
    {
      // Создание объекта и настройка параметров представления
      // Создать объект параметров представления
      PresentParameters presentParams = new PresentParameters();
      // Установить оконный режим
      presentParams.Windowed = true;
      // Сбрасывать содержимое буфера, если он не готов к представлению
      presentParams.SwapEffect = SwapEffect.Discard;
      // Создать объект устройства и сохранить ссылку на него
      device = new Device(0, DeviceType.Hardware, this,
        CreateFlags.SoftwareVertexProcessing, presentParams);
    }

    Нулевое значение параметра adapter подразумевает установку устройства по умолчанию. В результате мы создали фактическое аппаратное устройство, в противоположность устройству, эмулированному программными средствами. В коде использовано ключевое слово this, подразумевающее, что окном представления ( рендеринга ) будет текущее окно формы. Мы возложили вершинную обработку на наш центральный процессор.

  • Откройте созданный мастером файл Program.cs и перепишите функцию Main() ) так
  • static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
        
                using (Form1 frm = new Form1())
                {
                    // Показать форму перед созданием движка
                    frm.Show();
                    // Создать и инициализировать графический движок
                    // нашей функцией
                    frm.InitializeGraphics();
                    // Войти в цикл сообщений
                    Application.Run(frm);
                }
            }

    Здесь сначала мы добавили директиву using для создания формы. Это гарантирует вызов сборщика мусора при завершении приложения. Команда Show() визуализирует форму прежде, чем мы попытаемся создать наше графическое устройство.

  • Постройте проект и убедитесь в отсутствии синтаксических ошибок. Результатом вывода пока будет обычное окно формы пустого проекта, будто-бы мы ничего не сделали. Дальше наша задача - что нибудь отобразить на экране средствами DirectX.
  • Замещение метода OnPaint() формы

    Форма наследует от класса System.Windows.Forms.Control метод OnPaint(), который можно переопределять в форме. Этот метод диспетчеризует событие Paint, генерируемое операционной системой в тот момент, когда нужно перерисовывать окно формы. Добавим этот метод в наш код рендеринга (отображения). Нет смысла делать сейчас какие-то сложные вещи, просто очистим окно и заполним его однородным цветом.

  • В конце класса Form1 начните набирать ключевое слово override и в подсказчике кода выберите метод OnPaint(), который заполните следующим кодом
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Перерисовать
                device.Present();
        
                // Вызвать обработчики, подписавшиеся на событие Paint
                // base.OnPaint(e);
            }

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

  • Постройте приложение и убедитесь, что окно заливается васильковым цветом
  • Но пока что мы не использовали мощные возможности DirectX. То же самое можно было сделать, установив цвет фона формы через ее свойство BackColor в конструкторе программы. Теперь пришла пора что-нибудь нарисовать.

    Рисование плоского треугольника

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

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

  • Стадия тесселяции - происходит отбор граней объекта, т.е. разбиение модели на элементарные примитивы, такие как полигоны.
  • Стадия геометрических преобразований - включает в себя большой комплекс по преобразованию вершин с помощью матриц, установке материала, расчета освещения объекта. При этом может быть использован фиксированный или программируемый графический конвейер. Фиксированный конвейер использует массу повторяющихся операций по преобразованию объекта. Программируемый конвейер построен на работе с вершинными шейдерами.
  • Стадия растеризации - производит представление объекта на пиксельном уровне. Здесь могут применяться пиксельные шейдеры.
  • После прохождения объектом всех трех стадий готовая модель помещается в буфер кадра и выводится на экран.

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

    Управляемый DirectX имеет встроенную конструкцию для обработки треугольников. Существует класс вершин Microsoft.DirectX.Direct3D. CustomVertex, который размещает используемые в Direct3D различные сборки вершин в формате CustomVertex. Структура вершинного формата содержит данные в понятном для Direct3D виде.

    На данном этапе будем использовать для нашего треугольника структуру Microsoft.DirectX.Direct3D.CustomVertex. TransformedColored - преобразование цветов. Эта структура сообщает Direct3D, что наш треугольник не требует никаких преобразований, например, вращения и перемещения, поскольку мы будем устанавливать его положение посредством экранных координат. Данная структура включает цветовые параметры для каждой вершины треугольника.

  • Добавьте к методу OnPaint() после вызова Clear() следующий код
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Содать массив структур преобразованных координат
                CustomVertex.TransformedColored[] verts = new
                    CustomVertex.TransformedColored[3];
        
                // Задать параметры треугольника
                verts[0].SetPosition(new Vector4(this.ClientSize.Width / 2.0F,
                    50.0F, 0.5F, 1.0F));
                verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
                verts[1].SetPosition(new Vector4(this.ClientSize.Width - 
                    this.ClientSize.Width / 5.0F,
                    this.ClientSize.Height - this.ClientSize.Height / 5.0F,
                    0.5F, 1.0F));
                verts[1].Color = System.Drawing.Color.Black.ToArgb();
                verts[2].SetPosition(new Vector4(this.ClientSize.Width / 5.0F,
                    this.ClientSize.Height - this.ClientSize.Height / 5.0F,
                    0.5F, 1.0F));
                verts[2].Color = System.Drawing.Color.Purple.ToArgb();
        
                // Сформировать сцену
                device.BeginScene();
                device.VertexFormat = CustomVertex.TransformedColored.Format;
                device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);
                device.EndScene();
        
                // Перерисовать
                device.Present();
        
                // Вызвать обработчики, подписавшиеся на событие Paint
                // base.OnPaint(e);
            }

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

    Затем мы применяем методы формирования сцены и рендеринг. Команда BeginScene() уведомляет Direct3D о том, что мы собираемся что-то нарисовать и определяет его готовность к этому. Далее мы сообщаем, что именно хотим нарисовать. Свойство VertexFormat созданного устройства устанавливает режим непрограммируемого конвейера в преобразованном цветном вершинном формате.

    Метод DrawUserPrimitives() определяет место, где размещается рисунок. Его парметры означают следующее:

  • Первый параметр - это тип примитива, который мы планируем нарисовать. Существуют различные примитивы, которые нам доступны, но сейчас мы используем набор треугольников TriangleList.
  • Второй параметр - число примитивов (треугольников), которые мы хотим нарисовать. Оно равно общему числу вершин объекта, разделенному на число вершин примитива. В данном случае один объект с тремя вершинами отображается треугольным примитивом (объект треугольник аппроксимируется примитивом треугольник).
  • Третий параметр определяет данные, описывающие отображаемый объект (в данном случае координаты и цвет вершин треугольника).
  • Последняя команда EndScene() сообщает приложению Direct3D об окончании процедуры рисования. Эта команда должна присутствовать всегда, если использовалась команда BeginScene().

  • Постройте приложение и должен получиться такой результат
  • Поэкспериментируйте с заданием новых цветов вершин фигуры. Direct3D автоматически меняет градиенты цвета, позволяющие обеспечить цветовой плавный переход от одной вершины к другой.
  • Если поизменять размеры формы, то можно заметить, что при уменьшении величины окна изображение не перерисовывается. Причина в том, что Windows не рассматиривает уменьшение размера окна как случай, когда необходимо перерисовать целое окно. Она считает, что просто закрылась часть информации, которая была отображена, а остальная часть осталась целой. Для того, чтобы заставить окно перерисовываться непрерывно, даже если оно не разрушено, надо в метод OnPaint() добавить оператор принудительной перерисовки.

  • Добавьте в самый конец метода OnPaint() вызов метода
    this.Invalidate();
  • наследуемого формой от класса System.Windows.Forms. Control

  • Запустите приложение и посокрушайтесь увиденным
  • Может показаться, что мы сломали приложение, поскольку теперь мы видим главным образом пустой экран и мерцающий на нем треугольник. После каждого нового принудительного перерисовывания окна вызывается новая его перерисовка. То есть сообщение о разрушении окна генерируется непрерывно. Ну и пусть перерисовывается, лишь бы не мерцало. Устраним эффект мерцания изменением стиля окна. Для этого

  • Добавьте в конструктор формы следующий код
  • public Form1()
            {
                InitializeComponent();
        
                // Устраняем мерцание окна при бесконечной перерисовке
                this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                    ControlStyles.Opaque, true);
            }

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

  • Запустите приложение и убедитесь, что мерцание прекратилось, хотя окно при этом продолжает бесконечно перерисовываться
  • Поизменяйте размеры окна
  • Мы видим, что при изменении размеров окна треугольник деформируется, поскольку привязан к параметрам окна. Ниже мы построим треугольник в мировых координатах, без привязки к окну, и он будет сохранять свои пропорции при изменении окна.

    Создание трехмерного треугольника

    Созданное нами приложение не является трехмерным. Оно отображает только плоский цветной треугольник, нарисованный внутри окна. Его можно было бы создать гораздо проще с помощью графического интерфейса GDI (Graphics Device Interface). Но мы хотим создать реалистичный объект в трехмерном пространстве, который с помощью GDI создать невозможно.

    Когда мы формировали данные нашего треугольника, мы использовали структуру CustomVertex. TransformedColored, называемую преобразованными координатами (transformed coordinates). Эти координаты лежат в пространстве экрана и легко определяются из свойств формы. Но можно использовать непреобразованные координаты (структура CustomVertex. PositionColored ), которые и описывают наибольшую часть сцены и используются в современных 3D -играх.

    Для применения непреобразованных координат нужно позиционировать каждую вершину объекта не в экранных координатах, а в абсолютном пространстве мировых координат ( внешних координат). Можно представить внешнее пространство, как бесконечное трехмерное Декартово пространство, в любом месте которого мы можем размещать наши объекты.

    Задание положения треугольника в мировых координатах

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

    protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                /*
                // Содать массив структур преобразованных координат
                CustomVertex.TransformedColored[] verts = new
                    CustomVertex.TransformedColored[3];
        
                // Задать параметры треугольника
                verts[0].SetPosition(new Vector4(this.ClientSize.Width / 2.0F,
                    50.0F, 0.5F, 1.0F));
                verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
                verts[1].SetPosition(new Vector4(this.ClientSize.Width - 
                    this.ClientSize.Width / 5.0F,
                    this.ClientSize.Height - this.ClientSize.Height / 5.0F,
                    0.5F, 1.0F));
                verts[1].Color = System.Drawing.Color.Black.ToArgb();
                verts[2].SetPosition(new Vector4(this.ClientSize.Width / 5.0F,
                    this.ClientSize.Height - this.ClientSize.Height / 5.0F,
                    0.5F, 1.0F));
                verts[2].Color = System.Drawing.Color.Purple.ToArgb();
        
                // Сформировать сцену
                device.BeginScene();
                device.VertexFormat = CustomVertex.TransformedColored.Format;
                device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);
                device.EndScene();
                */
        
                // Содать массив структур непреобразованных координат
                CustomVertex.PositionColored[] verts = new
                    CustomVertex.PositionColored[3];
        
                // Задать параметры треугольника
                verts[0].SetPosition(new Vector3(0.0F, 1.0F, 1.0F));
                verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
                verts[1].SetPosition(new Vector3(-1.0F, -1.0F, 1.0F));
                verts[1].Color = System.Drawing.Color.Black.ToArgb();
                verts[2].SetPosition(new Vector3(1.0F, -1.0F, 1.0F));
                verts[2].Color = System.Drawing.Color.Purple.ToArgb();
        
                // Сформировать сцену
                device.BeginScene();
                device.VertexFormat = CustomVertex.PositionColored.Format;
                device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);
                device.EndScene();
        
                // Перерисовать
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
        
                // Вызвать обработчики, подписавшиеся на событие Paint
                // base.OnPaint(e);
            }

    Мы сменили тип массива структур для хранения цвета и координат вершин треугольника на внешние (мировые) координаты. Поскольку вершины не являются преобразованными, мы используем структуру Microsoft.DirectX. Vector3 вместо Microsoft.DirectX. Vector4. Элементы Microsoft.DirectX. Vector3 структурируют видеокарту непосредственно в координатах мирового пространства X, Y, Z. Присваивая при формировании сцены свойству device. VertexFormat значение CustomVertex. PositionColored.Format, мы информируем непрограммируемый конвейер о том, что вершины отображаемого объекта заданы в мировых координатах.

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

    Камера управляется с помощью двух различных линейных преобразований. Каждое преобразование определяется матрицей размером 4x4, которую можно переслать в Direct3D. Преобразование проекции используется для определения того, как сцена должна проецироваться на экран. Один из самых простых способов создания матрицы проекции состоит в использовании функции Microsoft.DirectX.Matrix. PerspectiveFovLH() структуры Matrix, которая создает матрицу проекции левой системы координат (для правой системы координат служит метод Microsoft.DirectX.Matrix.PerspectiveFovRH). Левая система координат моделирует угол зрения пользователя, когда он смотрит на экран монитора.

    Создание перспективы

    Приложения Direct3D используют левую ситему координат естественным образом. Для смены системы координат нужны дополнительные действия.

    Функция PerspectiveFovLH() имеет следующий синтаксис

    public static Microsoft.DirectX.Matrix.PerspectiveFovLH(
      System.Single fieldOfViewY, // Угол зрения камеры
      System.Single aspectRatio,  // Форматное соотношение сторон
      System.Single znearPlane,   // Ближний план (по Z-координате)
      System.Single zfarPlane)    // Дальний план (по Z-координате)

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

    Заметим, что у структуры System. Single в языке C# имеется псевдоним - ключевое слово float. Два параметра функции преобразования перспективы определяют ближнюю и дальнюю плоскости усеченной пирамиды. Параметр поля зрения определяется углом при вершине пирамиды. Форматное соотношение сторон любого из сечений пирамиды аналогично форматам телевидения, например, широкоформатное телевидение имеет соотношение 1.85. Можно понимать это соотношение как отношение ширины изображения к высоте. Direct3D прорисовывает только те объекты, которые в мировых координатах попадают внутрь усеченной пирамиды.

    Добавление камеры

    Кроме задания области визуализации в проект нужно добавить саму камеру. имеет вид

    public static Microsoft.DirectX.Matrix.LookAtLH(
      Microsoft.DirectX.Vector3 cameraPosition, // Положение камеры
      Microsoft.DirectX.Vector3 cameraTarget,   // Положение объекта
      Microsoft.DirectX.Vector3 cameraUpVector) // Направление камеры

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

  • Добавьте в класс Form1 новый метод SetupCamera() и запакуйте в него следующий код
  • // Установка камеры в сцену
            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));      // Направление камеры
            }
  • Поместите в методе OnPaint() класса Form1 сразу после вызова метода очистки Clear() вызов функции SetupCamera()
  • protected override void OnPaint(PaintEventArgs e)
        {
          // Очистить цветом клиентскую область формы
          device.Clear(ClearFlags.Target, 
            System.Drawing.Color.CornflowerBlue,
            1.0F, 0);
      
          // Вызов нашей функции установки камеры
          SetupCamera();
          ...............................................
        }
  • Запустите приложение и должен получиться следующий результат
  • Отключение потребности в освещении сцены

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

  • Добавьте в конец функции SetupCamera() код отключения освещения
  • // Установка камеры в сцену
            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 для преобразования объектов, нарисованных в пространстве моделей, где каждая вершина определена относительно модели, расположенной во внешнем пространстве. Преобразованием пространства может быть любая комбинация перемещения, вращения и масштабирования.

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

  • Добавьте в конец функции SetupCamera() код, поворачивающий треугольник на небольшой положительный (против часовой стрелки) угол
  • // Установка камеры в сцену
            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;
        
                // Преобразование пространства для поворота треугольника относительно оси Z
                device.Transform.World = Matrix.RotationZ((float)Math.PI / 6.0F);
            }

    Добавленный код сообщает Direct3D о типе используемого преобразования для каждого рисуемого объекта. В данном случае это вращение объекта относительно оси Z. Угол поворота задается строго в радианах. В библиотеке функций Direct3DX, которую мы добавим позже к нашему проекту, существует вспомогательная функция Geometry.DegreeToRadians(), преобразующая привычные нам градусы в радианы.

  • Постройте приложение. Экранный вывод для постоянного поворота будет таким
  • Мы повернули объект на постоянный произвольный угол и он непрерывно перерисовывается в этом неизменном положении в окне формы, потому что мы непрерывно генерируем событие разрушение окна вызовом Invalidate() в методе OnPaint(). Если менять угол в методе Matrix. RotationZ(), то треугольник будет непрерывно вращаться.

  • Замените вызов функции Matrix. RotationZ() на следующий код
  • // Установка камеры в сцену
            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;
        
                /*
                // Преобразование пространства для поворота треугольника относительно оси Z
                device.Transform.World = Matrix.RotationZ((float)Math.PI / 6.0F);
                */
        
                // Преобразование пространства для непрерывного вращения треугольника
                device.Transform.World = Matrix.RotationZ(
                    (System.Environment.TickCount / 450.0F) / (float)Math.PI);
            }
  • Запустите приложение и получите вращающийся треугольник
  • Вращение происходит из-за непрерывно увеличивающегося встроенного в библиотеку счетчика System.Environment. TickCount. Каждые 15 миллисекунд значение счетчика увеличивается, тем самым периодически увеличивая угол вращения для новых перерисовок. Учитывая, что клиентская область формы непрерывно перерисовывается, мы можем сами организовать вращение объекта заданием своего счетчика увеличения угла.

  • Добавьте в класс Form1 поле angle для хранения значений угла и измените вызов Matrix. RotationZ() в функции SetupCamera()
  • 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;
        
                /*
                // Преобразование пространства для поворота треугольника относительно оси Z
                device.Transform.World = Matrix.RotationZ((float)Math.PI / 6.0F);
        
                // Преобразование пространства для непрерывного вращения треугольника
                device.Transform.World = Matrix.RotationZ(
                    (System.Environment.TickCount / 450.0F) / (float)Math.PI);
                */
        
                // Вращение с собственным счетчиком
                device.Transform.World = Matrix.RotationZ(angle / (float)Math.PI);
                angle += 0.1F;
            }
  • Постройте приложение и убедитесь, что треугольник вращается
  • Последний способ, который мы применили для приращения угла поворота, не слишком хороший. Мы привязали изменение угла к быстродействию компьютера, с которым он перерисовывает окно, а не к стабильному временному источнику. Поэтому на разных компьютерах будет разная скорость вращения объекта. Позже мы рассмотрим эффективный способ привязки нашего кода к таймеру.

    Вращение треугольника вокруг произвольной оси

    Организуем вращение треугольника вокруг произвольно заданной оси.

  • Измените процедуру преобразования пространства, чтобы код функции SetupCamera() стал таким
  • // Установка камеры в сцену
            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;
        
                /*
                // Преобразование пространства для поворота треугольника относительно оси Z
                device.Transform.World = Matrix.RotationZ((float)Math.PI / 6.0F);
        
                // Преобразование пространства для непрерывного вращения треугольника
                device.Transform.World = Matrix.RotationZ(
                    (System.Environment.TickCount / 450.0F) / (float)Math.PI);
        
                // Вращение с собственным счетчиком
                device.Transform.World = Matrix.RotationZ(angle / (float)Math.PI);
                */
        
                // Преобразование пространства для произвольных поворотов
                device.Transform.World = Matrix.RotationAxis(
                    new Vector3(angle / ((float)Math.PI * 2.0F),
                                angle / ((float)Math.PI * 4.0F),
                                angle / ((float)Math.PI * 6.0F)),
                    angle / (float)Math.PI);
                angle += 0.1F;
            }

    В вызове функции Matrix. RotationAxis() первый аргумент конструктора Vector3 устанавливает ориентацию оси вращения треугольника, второй параметр - угол поворота вокруг этой оси. Угол поворота мы задаем непрерывным увеличением счетчика при каждом обновлении экрана, чем вызываем вращение треугольника.

  • Запустите приложение с новым изменением кода и проверьте экранный вывод
  • Треугольник вращается вокруг утановленной оси, но постоянно исчезает, когда должен поворачиваться обратной стороной. Это связано с порядком освещения невидимых поверхностей. В данном случае Direct3D по умолчанию считает, что отображается объемная фигура, следовательно наш плоский треугольник является только передней ее гранью, а обратная сторона должна быть закрыта другой гранью, которой нет. Такой процесс называется Back face culling (не отображать обратное).

    Порядок отображения поверхностей определяется опциями параметра состояния рендера device.RenderState. CullMode, которые имеют значения перечисления

  • Microsoft.DirectX.Direct3D.Cull. Clockwise - для вращения по часовой стрелке
  • Microsoft.DirectX.Direct3D.Cull. CounterClockwise - для вращения против часовой стрелки
  • Microsoft.DirectX.Direct3D.Cull. None - отключить механизм отсечения
  • Отключите в состоянии рендера механизм отсечения, добавив в настройки состояния устройства соответствующий код
  • // Установка камеры в сцену
            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.CullMode = Cull.None;
        
                /*
                // Преобразование пространства для поворота треугольника относительно оси Z
                device.Transform.World = Matrix.RotationZ((float)Math.PI / 6.0F);
        
                // Преобразование пространства для непрерывного вращения треугольника
                device.Transform.World = Matrix.RotationZ(
                    (System.Environment.TickCount / 450.0F) / (float)Math.PI);
        
                // Вращение с собственным счетчиком
                device.Transform.World = Matrix.RotationZ(angle / (float)Math.PI);
                */
        
                // Преобразование пространства для произвольных поворотов
                device.Transform.World = Matrix.RotationAxis(
                    new Vector3(angle / ((float)Math.PI * 2.0F),
                                angle / ((float)Math.PI * 4.0F),
                                angle / ((float)Math.PI * 6.0F)),
                    angle / (float)Math.PI);
                angle += 0.1F;
            }
  • Запустите приложение и убедитесь, что обратная сторона вращающегося треугольника теперь не исчезает
  • Обработка события изменения размеров окна

    Если попробовать в запущенном на данном этапе приложении изменять размер окна формы, то в какие-то моменты треугольник исчезает и не хочет прорисовываться, либо начинает изменять размеры пропорционально размерам окна (попробуйте это!). Приложение Direct3D пытается сброситься или продолжить рендеринг с тем же самым пространственным разрешением. В результате изображение будет скопировано и вытянуто, чтобы заполнить измененное окно.

    Управляемый DirectX способен контролировать процедуру изменения окна формы и автоматически перезапускать устройство при изменении окна. Для этого устройство имеет событие Microsoft.DirectX.Direct3D.Device. DeviceResizing, в обработчике которого можно сформировать код автоматического сброса устройства.

  • Добавьте в конец класса Form1 код регистрации и обработки события DeviceResizing
  • public void InitializeGraphics()
        {
          // Создание объекта и настройка параметров представления
          // Создать объект параметров представления
          PresentParameters presentParams = new PresentParameters();
          // Установить оконный режим
          presentParams.Windowed = true;
          // Сбрасывать содержимое буфера, если он не готов к представлению
          presentParams.SwapEffect = SwapEffect.Discard;
          // Создать объект устройства и сохранить ссылку на него
          device = new Device(0, DeviceType.Hardware, this,
            CreateFlags.SoftwareVertexProcessing, presentParams);
      
          // Перехват события изменения формы для сброса устройства
          device.DeviceResizing +=new CancelEventHandler(device_DeviceResizing);
        }
      
        // Обработчик события изменения формы для сброса устройства
        private void device_DeviceResizing(object sender, CancelEventArgs e)
        {
          e.Cancel = true;
        }
  • Запустите приложение и максимизируйте окно
  • Мы видим, что у граней треугольника появились неровные края, что портит картину.

  • Уберите только что добавленный код, потому что DirectX сам отрабатывает по умолчанию процедуру изменения размеров окна оптимальным образом
  • Создание освещения сцены

    Ранее, после того, как мы перевели модель треугольника в непреобразованные координаты, он стал черным и нам пришлось временно отключить освещение. Теперь мы займемся настройками источника света. Класс устройства имеет прикрепленный световой массив, каждый член которого определяет различные свойства света. Первое, что нужно сделать - включить освещение. Для этого необходимо либо явно установить флаг device.RenderState. Lighting в состояние true, либо просто убрать этот код из функции SetupCamera(), учитывая, что устройство device по умолчанию настроено с включенным освещением.

  • Включите свет, удалив или закомментировав соответствующую строку в функции SetupCamera() (или просто установите этот флаг device.RenderState. Lighting в значение true )
  • Если сейчас запустить приложение, то треугольник опять станет черным. Настроим освещение в нашей сцене.

  • Добавьте в метод OnPaint() формы перед формированием сцены следующий код
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Вызов нашей функции установки камеры
                SetupCamera();
        
                // Содать массив структур непреобразованных координат
                CustomVertex.PositionColored[] verts = new
                    CustomVertex.PositionColored[3];
        
                // Задать параметры треугольника
                verts[0].SetPosition(new Vector3(0.0F, 1.0F, 1.0F));
                verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
                verts[1].SetPosition(new Vector3(-1.0F, -1.0F, 1.0F));
                verts[1].Color = System.Drawing.Color.Black.ToArgb();
                verts[2].SetPosition(new Vector3(1.0F, -1.0F, 1.0F));
                verts[2].Color = System.Drawing.Color.Purple.ToArgb();
        
                // Настроить параметры света источника с номером 0
                device.Lights[0].Type = LightType.Point;    // Точечный источник
                // По умолчанию (0,0,0) - источник в начале координат
                device.Lights[0].Position = new Vector3();  
                // Свет диффузионный белый
                device.Lights[0].Diffuse = System.Drawing.Color.White;
                // Затухание света при удалении
                device.Lights[0].Attenuation0 = 0.2f;
                // Дальность освещения, поддерживаемая устройством
                device.Lights[0].Range = 1000.0f;
                // Принять настройки
                device.Lights[0].Commit();
                // Установить активным 
                device.Lights[0].Enabled = true;
        
                // Сформировать сцену
                device.BeginScene();
                device.VertexFormat = CustomVertex.PositionColored.Format;
                device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);
                device.EndScene();
        
                // Перерисовать
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
        
                // Вызвать обработчики, подписавшиеся на событие Paint
                // base.OnPaint(e);
            }

    Вначале определяется, какой тип освещения мы хотим отобразить. Мы выбрали точечный источник света (Point), который излучает во всех направлениях одинаково. Существуют также направленное освещение, параллельное освещение, и световое пятно.

    Мы поместили по умолчанию источник в центр треугольника с координатами ( 0,0,0 ) с помощью конструктора Vector3() класса параметров. Мы установили диффузионный компонент осветителя в режим получения рассеянного белого света, чтобы поверхность нормально освещалась. Мы установили параметр затухания ( Attenuation0 ), определяющий изменение интенсивности света при удалении. Диапазон освещения ( Range ) - это максимальное расстояние, в пределах которого распространяется свет.

  • Запустите приложение и убедитесь, что треугольник по прежнему остается черным
  • Причина в том, что Direct3D не может освещать объект, не имея нормали к каждому примитиву его поверхности. Нормаль представляет собой вектор, перпендикулярный к передней стороне каждой вершины примитива, описывающего поверхность освещаемого объекта.

  • Добавьте нормали к каждой вершине примитива-треугольника, а также новый формат построения сцены с учетом введенных дополнительных параметров нормали, заменив старый код на следующий
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Вызов нашей функции установки камеры
                SetupCamera();
        
                /*
                // Создать массив структур непреобразованных координат
                CustomVertex.PositionColored[] verts = new
                    CustomVertex.PositionColored[3];
                */
        
                // Создать массив структур непреобразованных координат
                // треугольника с возможностью определения нормали к нему
                CustomVertex.PositionNormalColored[] verts = new
                    CustomVertex.PositionNormalColored[3];
        
                // Задать параметры треугольника
                verts[0].SetPosition(new Vector3(0.0F, 1.0F, 1.0F));
                verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
                verts[1].SetPosition(new Vector3(-1.0F, -1.0F, 1.0F));
                verts[1].Color = System.Drawing.Color.Black.ToArgb();
                verts[2].SetPosition(new Vector3(1.0F, -1.0F, 1.0F));
                verts[2].Color = System.Drawing.Color.Purple.ToArgb();
        
                // Дополнить вершины параметрами нормалей
                verts[0].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[1].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[2].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
        
                // Настроить параметры света источника с номером 0            
                device.Lights[0].Type = LightType.Point;    // Точечный источник
                // По умолчанию (0,0,0) - источник в начале координат
                device.Lights[0].Position = new Vector3();  
                // Свет диффузионный белый
                device.Lights[0].Diffuse = System.Drawing.Color.White;
                // Затухание света при удалении
                device.Lights[0].Attenuation0 = 0.2f;
                // Дальность освещения, поддерживаемая устройством 
                device.Lights[0].Range = 1000.0f;
                // Принять настройки
                device.Lights[0].Commit();
                // Установить активным 
                device.Lights[0].Enabled = true;
        
                // Сформировать сцену с учетом параметров 
                // "положение-нормаль-цвет"
                device.BeginScene();
                // Установить формат обработки вершин при отображении
                // device.VertexFormat = CustomVertex.PositionColored.Format;
                device.VertexFormat = CustomVertex.PositionNormalColored.Format;
                // Нарисовать в буфере кадра треугольник по вершинам 
                device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);
                device.EndScene();
        
                // Показать буфер кадра 
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
        
                // Вызвать обработчики, подписавшиеся на событие Paint
                // base.OnPaint(e);
            }

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

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

  • Постройте проект и полюбуйтесь на наше простое мультимедиа
  • Поизменяйте размеры окна и убедитесь, что пропорции треугольника сохраняются
  • Хранение треугольника в вершинном буфере

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

    Класс Microsoft.DirectX.Direct3D. VertexBuffer имеет три перегрузки конструктора, которые можно использовать для создания вершинного буфера

    public VertexBuffer(System.IntPtr lp, 
                        Microsoft.DirectX.Direct3D.Device device,
                        Microsoft.DirectX.Direct3D.Usage usage, 
                        Microsoft.DirectX.Direct3D.VertexFormats vertexFormat,
                        Microsoft.DirectX.Direct3D.Pool pool)
                        
        public VertexBuffer(System.Type typeVertexType,
                        System.Int32 numVerts, 
                        Microsoft.DirectX.Direct3D.Device device, 
                        Microsoft.DirectX.Direct3D.Usage usage, 
                        Microsoft.DirectX.Direct3D.VertexFormats vertexFormat, 
                        Microsoft.DirectX.Direct3D.Pool pool)
                        
        public VertexBuffer(Microsoft.DirectX.Direct3D.Device device, 
                        System.Int32 sizeOfBufferInBytes, 
                        Microsoft.DirectX.Direct3D.Usage usage, 
                        Microsoft.DirectX.Direct3D.VertexFormats vertexFormat, 
                        Microsoft.DirectX.Direct3D.Pool pool)

    Опишем каждый параметр:

  • lp - принимает указатель на неуправляемый интерфейс IDirect3DvertexBuffer9 для совместимости с вершинным буфером, созданным с помощью неуправляемого внешнего источника. Управляемые приложения не должны использовать эту перегрузку конструктора.
  • device - устройство, которое используется в Direct3D для создания вершинного буфера.
  • sizeOfBufferInBytes - задаваемый параметр вершинного буфера в байтах. С таким параметром буфер способен поддерживать любой тип вершин.
  • typeVertexType - задается, если мы хотим, чтобы буфер включал только один тип вершин. Это может быть тип одной из встроенных структур вершин в классе CustomVertex, либо тип вершин, определяемый пользователем. Данное значение не может быть пустым.
  • numVerts - максимальное количество вершин, которые мы хотим сохранить в буфере. Это значение должно быть большим нуля.
  • usage - определяет, как этот вершинный буфер может использоваться. При создании буфера могут использоваться все значения этого параметра, за исключением следующих значений перечисления:
  • DoNotClip - используется для того, чтобы исключить режим clipping (отсечение) для этого буфера. Используя этот флажок, необходимо при использовании рендеринга из вершинного буфера установить параметр clipping состояния рендера в значение false.
  • Dynamic - используется для сообщения приложению, что вершинный буфер требует использования динамической памяти. Если этот флажок не определен, вершинный буфер является статическим. Статические вершины буфера обычно хранятся в видеопамяти, в то время как динамические буферы хранятся в памяти AGR. Выбор данной опции необходим для драйверов, чтобы определить, где хранить данные.
  • NPatches - сообщает, что данный буфер используется для рисования патчей N-patches.
  • Points - сообщает, что данный буфер используется для рисования точек.
  • RTPatches - сообщает, что данный буфер используется для рисования примитивов более высокого порядка.
  • SoftwareProcessing - сообщает, что обработка вершин должна проводиться программным обеспечением, в противном случае обработка ведется аппаратными средствами.
  • WriteOnly - сообщает, что вершинный буфер не будет считываться в случае, когда нет необходимости считывать данные вершинного буфера. Данная опция позволяет разгрузить видеопамять.
  • vertexFormat - перечисление определяет формат вершин, которые будут сохранены в этом буфере. Можно выбрать опцию Microsoft.DirectX.Direct3D.VertexFormats. None, если требуется зарезервировать этот буфер.
  • pool - определяет пул памяти, куда будет размещен вершинный буфер. Определяется значениями перечислений
  • Microsoft.DirectX.Direct3D.Pool. Managed - ресурсы копируются автоматически в доступное устройство памяти. При сбросе устройства ресурсы автоматически в нем не восстанавливаются.
  • Microsoft.DirectX.Direct3D.Pool. Default - данные располагаются либо в памяти AGP, либо в видеопамяти. Вершинные буферы, созданные в этом пуле памяти, определяются автоматически перед сбросом устройства.
  • Microsoft.DirectX.Direct3D.Pool. SystemMemory - данные вершинного буфера помещаются в системную память, где являются недоступными для устройства.
  • Microsoft.DirectX.Direct3D.Pool. Scratch - системный пул памяти, не связанный с устройством и не используемый им. Удобен при управлении данными, при этом не привязан к специфическому формату устройства.
  • Далее мы воспользуемся второй из описанных перегрузок конструктора создания вершинного буфера (выделена).

  • Добавьте к классу Form1 закрытое поле-ссылку на вершинный буфер в дополнение к объявленной ранее переменной-ссылке устройства
  • private Device device = null;
        private VertexBuffer vb = null;

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

  • Перенесите код создания треугольника из OnPaint() в функцию InitializeGraphics() и разместите его сразу после кода создания устройства, после чего функция InitializeGraphics() должна стать такой
  • 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.PositionNormalColored),
                                      3,
                                      device,
                                      Usage.Dynamic | Usage.WriteOnly,
                                      CustomVertex.PositionNormalColored.Format,
                                      Pool.Default);
        
                // Создать массив структур непреобразованных координат
                // треугольника с возможностью определения нормали к нему
                CustomVertex.PositionNormalColored[] verts = new
                    CustomVertex.PositionNormalColored[3];
        
                // Задать параметры треугольника
                verts[0].SetPosition(new Vector3(0.0F, 1.0F, 1.0F));
                verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
                verts[1].SetPosition(new Vector3(-1.0F, -1.0F, 1.0F));
                verts[1].Color = System.Drawing.Color.Black.ToArgb();
                verts[2].SetPosition(new Vector3(1.0F, -1.0F, 1.0F));
                verts[2].Color = System.Drawing.Color.Purple.ToArgb();
        
                // Дополнить вершины параметрами нормалей
                verts[0].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[1].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[2].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
        
                // Заполнить вершинный буфер данными треугольника
                vb.SetData(verts, 0, LockFlags.None);
            }

    В добавленном коде мы сначала создаем вершинный буфер для хранения трех элементов массива структур verts. Мы настраиваем вершинный буфер только на запись, определяем его как динамический для хранения в заданном по умолчанию пуле памяти. Затем задаем параметры треугольника в перенесенном из функции OnPaint() коде.

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

    После записи данных о треугольнике в вершинный буфер мы не можем в функции OnPaint() рисовать треугольник как прежде функцией DrawUserPrimitives(). Для этого существует другая функция - DrawPrimitives().

  • Измените в функции OnPaint() код формирования сцены, размещенный сразу после настройки источника света, на следующий
  • // Сформировать сцену с учетом параметров 
                // "положение-нормаль-цвет"
                device.BeginScene();
                // Установить формат обработки вершин при отображении
                device.VertexFormat = CustomVertex.PositionNormalColored.Format;
                // Нарисовать треугольник по данным из буфера
                device.SetStreamSource(0, vb, 0);
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
                device.EndScene();

    С помощью функции SetStreamSource() мы подключаем к устройству вершинный буфер. Первый параметр этой функции определяет номер потока, который будет использовать данные. Мы используем один поток с номером 0. Второй параметр - вершинный буфер с данными рисования. Третий параметр - смещение в байтах, с которого начнется рисование. Мы будем рисовать все данные, поэтому задали нулевое смещение.

    Далее мы рисуем данные из созданного потока с помощью функции DrawPrimitives(). Первый параметр определяет примитив, с помощью которого будет рисоваться фигура - у нас это единственный треугольник будет аппроксимироваться треугольным примитивом. Второй параметр - начальная вершина в потоке. Последний параметр - число примитивов, которые мы будем рисовать.

  • Постройте приложение и убедитесь, что треугольник строится и вращается как прежде
  • Но здесь есть проблема: если попытаться изменить размер окна формы, то треугольник исчезает, сразу и навсегда.

    Причина в том, что при изменении размеров окна графическое устройство device автоматически сбрасывается. Это означает, что пул при сбрасывании устройства автоматически устанавливается в нулевое состояние. То есть там после сброса не будет никаких данных, поэтому ничего не отобразится на экране. До введения вершинного буфера данные определялись в функции OnPaint() при каждой перерисовке, поэтому такого эффекта не наблюдалось, хотя сбрасывание при изменении размеров окна имело место.

    Нужно каким-то образом вновь заполнить вершинный буфер после сброса. Вершинный буфер имеет событие Microsoft.DirectX.Direct3D.VertexBuffer. Created, которое отслеживает момент восстановления буфера и готовность его к заполнению. Нам нужно перехватить это событие в приложении и создать для него обработчик, в котором разместить код создания треугольника и заполнения вершинного буфера.

  • В функции InitializeGraphics() сразу после кода создания вершинного буфера поместите код регистрации события Created и начальный вызов обработчика для только что созданной формы
  • // Создать вершинный буфер
                vb = new VertexBuffer(typeof(CustomVertex.PositionNormalColored),
                                      3,
                                      device,
                                      Usage.Dynamic | Usage.WriteOnly,
                                      CustomVertex.PositionNormalColored.Format,
                                      Pool.Default);
        
                // Регистрация события Created вершинного буфера
                vb.Created += new EventHandler(vb_Created);
        
                // Принудительный вызов создания треугольника 
                // и заполнения вершинного буфера при первом 
                // создании формы
                vb_Created(vb, null);

    Нулевая ссылка на месте второго параметра в принудительном вызове обработчика vb_Created() является заглушкой для объекта типа EventArgs параметров события, поскольку никаких параметров внутри обработчика мы не используем.

  • Перенесите всю оставшуюся часть функции InitializeGraphics() (код создания вершин треугольника и заполнения буфера) в созданный обработчик, который станет таким
  • void vb_Created(object sender, EventArgs e)
            {
                // Создать массив структур непреобразованных координат
                // треугольника с возможностью определения нормали к нему
                CustomVertex.PositionNormalColored[] verts = new
                    CustomVertex.PositionNormalColored[3];
        
                // Задать параметры треугольника
                verts[0].SetPosition(new Vector3(0.0F, 1.0F, 1.0F));
                verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
                verts[1].SetPosition(new Vector3(-1.0F, -1.0F, 1.0F));
                verts[1].Color = System.Drawing.Color.Black.ToArgb();
                verts[2].SetPosition(new Vector3(1.0F, -1.0F, 1.0F));
                verts[2].Color = System.Drawing.Color.Purple.ToArgb();
        
                // Дополнить вершины параметрами нормалей
                verts[0].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[1].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[2].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
        
                // Заполнить вершинный буфер данными треугольника
                vb.SetData(verts, 0, LockFlags.None);
            }
  • Запустите приложение и попробуйте изменить размер окна
  • Теперь треугольник восстанавливается в вершинном буфере и прорисовывается всякий раз после сброса устройства.

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

    Вернуться к учебному плану