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

Устройство Direct3D

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

Есть три конструктора, которые можно использовать для создания устройства. Сейчас мы используем один из них, а с другими познакомимся в следующих главах. Тот, на котором мы сосредоточим внимание, получает следующие аргументы:

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)

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

ПЕРЕГРУЖЕННЫЕ КОНСТРУКТОРЫ УСТРОЙСТВ
Второй перегруженный конструктор устройства идентичен первому за исключением параметра renderWindow, который получает значение типа IntPtr для неуправляемого (не относящегося к Windows Forms) окна. Последний перегруженный конструктор получает единственное значение типа IntPtr, являющееся неуправляемым COM-указателем на интерфейс IDirect3DDevice9. Они могут использоваться, если вам необходимо работать с неуправляемыми приложениями из вашего управляемого кода.

Следующий аргумент, DeviceType, сообщает Direct3D какой тип устройства вы хотите создать. Обычно здесь указывается значение DeviceType.Hardware, обозначающее, что вы хотите использовать аппаратное устройство. Другое значение, DeviceType.Reference, позволяет вам использовать вспомогательный растеризатор, который программно эмулирует все возможности Direct3D, но работает исключительно медленно. Этот вариант используется главным образом для оладки, или для проверки тех возможностей приложения, которые не поддерживает установленная видеокарта.

ПРОГРАММНАЯ ЭМУЛЯЦИЯ УСТРОЙСТВ
Обратите внимание, что вспомогательный растеризатор поставляется только с DirectX SDK, а библиотеки времени выполнения DirectX не предоставляют этой возможности. Последнее значение, DeviceType.Software, означает, что вы используете собственный программный растеризатор. Если вы не знаете о том, есть ли у вас программный растеризатор, игнорируйте этот вариант.

Аргумент renderWindow привязывает окно к данному устройству. Поскольку классы элементов управления Windows Forms содержат дескриптор окна, достаточно просто использовать наследуемый класс в качестве нашего окна визуализации. В этом параметре вы можете указать форму, панель или любой другой класс, наследуемый от элемента управления. Сейчас мы будем использовать формы.

Следующий параметр, behaviorFlags, используется для управления поведением устройства после его создания. Большинство членов перечисления CreateFlags можно комбинировать, что позволяет сразу задавать несколько вариантов поведения. Некоторые флаги являются взаимоисключающими, однако об этом я расскажу позже. Сейчас мы будем использовать только флаг SoftwareVertexProcessing. Он указывает, что вся обработка вершин должна выполняться центральным процессором. Хотя это значительно медленнее, чем обработка вершин видеокартой, мы сейчас не можем гарантировать, что ваша видеокарта поддерживает эту возможность. Безопаснее будет пока возложить задачу обработки на центральный процессор.

Последний параметр конструктора, presentationParameters, управляет тем, как устройство отображает данные на экране. Через этот класс можно управлять любыми параметрами отображения устройства. Позже мы более подробно поговорим об этой структуре, а сейчас уделим винмание только членам Windowed и SwapEffect.

Член Windowed является логической переменной, определяющей работает ли устройство в полноэкранном режиме (false) или в оконном режиме (true).

Член SwapEffect используется для управления поведением механизма переключения буферов. Если вы выберете SwapEffect.Flip, во время выполнения будет создан дополнительный вторичный буфер, который во время показа будет меняться местами с первичным буфером. SwapEffect.Copy похож на Flip, но требует, чтобы вы установили количество вторичных буферов равным 1. Сейчас мы выберем вариант SwapEffect.Discard, который после показа просто делает недействительным содержимое вторичного буфера.

Теперь, обладая этой информацией, мы можем создать устройство. Давайте вернемся к коду и сделаем это. Сперва нам потребуется объект устройства, который мы будем использовать в нашем приложении. Мы добавим новый закрытый член device. Вставьте следующую строку в определение класса:

private Device device = null;

Теперь добавим к нашему классу новую функцию с именем InitializeGraphics, где мы сможем использовать конструктор, который только что обсудили. Добавьте к вашему классу следующий код:

/// <summary>
/// Здесь мы инициализируем наше графическое устройство
/// </summary>
public void InitializeGraphics()
{
    // Устанавливаем наши параметры показа
    PresentParameters presentParams = new PresentParameters();

    presentParams.Windowed   = true;
    presentParams.SwapEffect = SwapEffect.Discard;

    // Создаем устройство
    device = new Device(0, DeviceType.Hardware, this,
                        CreateFlags.SoftwareVertexProcessing,
                        presentParams);
}

Как видите, мы создаем аргумент с параметрами показа, устанавливаем упомянутые выше члены (Windowed и SwapEffect), а затем создаем устройство. В качестве идентификатора адаптера мы указываем 0, что означает используемый по умолчанию адаптер. Мы создаем аппаратное устройство, а не вспомогательный или программный растеризатор. Обратите внимание на использование ключевого слова this в качестве окна визуализации. Поскольку наше приложение и, в частности, данный класс, является формой Windows, мы просто используем ее. Кроме того, как упоминалось ранее, мы возлагаем обработку вершин на центральный процессор. Все замечательно, но сейчас этот код никогда не вызывается, так что давайте изменим функцию Main нашего класса, чтобы она обращалась к этому методу. Внесите в созданный по умолчанию статический метод Main следующие изменения:

static void Main()
{
    using (Form1 frm = new Form1())
    {
        // Отображаем форму и инициализируем графическую систему
        frm.Show();
        frm.InitializeGraphics();
        Application.Run(frm);
    }
}

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

Ладно, следует признать, что приложение исключительно скучное. Оно создает устройство, но ничего не делает с ним, так что посмотрев на запущенное приложение вы не заметите никаких отличий от созданного в самом начале «пустого» проекта C#. Давайте изменим это, и что-нибудь визуализируем на экране.

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

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    device.Clear(ClearFlags.Target,
                 System.Drawing.Color.CornflowerBlue,
                 1.0f, 0);
    device.Present();
}

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

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

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

К счастью, в DirectX для управляемого кода уже есть конструкция для хранения данных треугольников. Это класс CustomVertex в пространстве имен Direct3D, приютивший многие общеупотребительные конструкции формата вершин, используемые в Direct3D. Структура формата вершин хранит данные в формате, который Direct3D понимает и может использовать. Позже мы более подробно обсудим эти структуры, а сейчас для нашего треугольника выберем структуру TransformedColored. Она сообщает Direct3D, что наш треугольник не надо преобразовывать (то есть вращать или перемещать), поскольку мы указываем его местоположение в экранных координатах. Также для каждой точки (вершины) треугольника включена цветовая компонента. Вернемся к нашему переопределенному методу OnPaint, и добавим следующий код после вызова очистки устройства:

CustomVertex.TransformedColored[] verts = new CustomVertex.TransformedColored[3];
verts[0].Position = new Vector4(this.Width / 2.0f,
                                 50.0f, 0.5f, 1.0f);
verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
verts[1].Position = new Vector4(this.Width - (this.Width / 5.0f),
                                 this.Height - (this.Height / 5.0f), 0.5f, 1.0f);
verts[1].Color = System.Drawing.Color.Black.ToArgb();
verts[2].Position = new Vector4(this.Width / 5.0f,
                                 this.Height - (this.Height / 5.0f), 0.5f, 1.0f);
verts[2].Color = System.Drawing.Color.Purple.ToArgb();

Каждый член созданного массива представляет одну точку нашего треугольника, так что необходимо три элемента. Затем для каждого члена массива мы присваиваем свойству Position структуру Vector4. Местоположение преобразованной вершины включает координаты X и Y в пространстве экрана (относительно начала координат окна), а также координату Z и значение rhw (обратное или однородное w). Последние два члена в данном примере игнорируются. Структура Vector4 очень удобна для хранения подобной информации. Затем мы устанавливаем цвета каждой из вершин. Обратите внимание, что при использовании стандартных цветов необходимо вызывать метод ToArgb. Direct3D ожидает, что цвета будут представлены в виде 32-разрядных целых чисел, и данный метод преобразует имеющийся цвет в этот формат.

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

Теперь у нас есть данные и надо сообщить Direct3D о том, что мы хотим нарисовать наш треугольник, и как надо его рисовать. Это делается путем добавления к перегруженному методу OnPaint следующего кода:

device.BeginScene();
device.VertexFormat = CustomVertex.TransformedColored.Format;
device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);
device.EndScene();

Что, черт возьми, все это значит? Не волнуйтесь, все относительно просто. Метод BeginScene позволяет Direct3D узнать, что мы собираемся что-то рисовать, и подготовиться к этому. Теперь, когда мы сказали Direct3D, что собираемся что-то нарисовать, нам надо сообщить что именно мы будем рисовать. Для этого предназначено свойство VertexFormat. Оно сообщает Direct3D, какой формат фиксированного конвейера функций мы хотим использовать. В нашем случае мы используем конвейер для преобразованных и окрашенных вершин. Не беспокойтесь, если вам пока непонятны слова «фиксированный конвейер функций», мы поговорим об этом позже.

В функции DrawUserPrimitives происходит собственно рисование. Так что означают ее параметры? Первый параметр — это тип примитивов, которые мы планируем рисовать. Доступно несколько различных типов, но сейчас мы хотим просто нарисовать список треугольников, так что выберем тип TriangleList. Второй параметр — это сколько треугольников мы собираемся рисовать; для списка треугольников это число всегда должно быть равно количеству вершин в массиве, деленому на три. Поскольку мы рисуем один треугольник, естественно, здесь укажем 1. Последний параметр функции — это данные, которые Direct3D будет использовать для рисования треугольника. Мы уже создали массив данных, так что осталось указать его здесь. Последний метод, EndScene, просто сообщает Direct3D, что мы не будем больше рисовать. После каждого вызова BeginScene вы должны вызывать EndScene.

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

Поэкспериментировав с приложением вы заметите несколько вещей. Например, если уменьшить размеры окна, то его содержимое не обновляется — ничего не происходит. Причина в том, что Windows не считает уменьшение окна случаем, требующим его перерисовки. Ведь вы просто убрали часть отображаемых данных, а те, которые осталсь, никак не изменились. К счатью, эту проблему легко обойти. Достаточно сообщить Windows, что нам всегда нужна перерисовка окна. Это достигается простым объявлением окна недействительным в конце переопределенного метода OnPaint:

this.Invalidate();

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

this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

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


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

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