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

Делаем треугольник трехмерным

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

Если вы помните, раньше при первом создании данных нашего треугольника мы использовали нечто, называемое преобразованные (transformed) координаты. Эти координаты, как вы уже знаете, относятся к пространству экрана и их легко определить. А что, если у нас будут координаты, которые еще не преобразованы? Такие непреобразованные координаты составляют большинство сцен в современных трехмерных играх.

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

Сначала мы изменим данные треугольника, чтобы использовать один из форматов непреобразованных вершин; в данном случае нас волнует местоположение вершин и их цвет, так что выберем CustomVertex.PositionColored. Измените код задания данных треугольника следующим образом:

CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[3];
verts[0].Position = new Vector3(0.0f, 1.0f, 1.0f);
verts[0].Color = System.Drawing.Color.Aqua.ToArgb();
verts[1].Position = new Vector3(-1.0f, -1.0f, 1.0f);
verts[1].Color = System.Drawing.Color.Black.ToArgb();
verts[2].Position = new Vector3(1.0f, -1.0f, 1.0f);
verts[2].Color = System.Drawing.Color.Purple.ToArgb();

Также соответственно измените свойство VertexFormat:

device.VertexFormat = CustomVertex.PositionColored.Format;

Что теперь? Если вы запустите приложение, то ничего не произойдет; вы увидите только окрашенное окно и ничего более. Перед тем, как выяснить, почему так происходит, давайте уделим момент описанию изменений. Как видите, теперь для наших данных используется структура PositionColored. Она содержит координаты вершины в мировом пространстве и ее цвет. Поскольку вершины не преобразованны, для указания их местоположения мы используем класс Vector3 вместо Vector4, применяемого для преобразованных вершин. Дело в том, что у непреобразованных вершин нет компонента rhw. Члены структуры Vector3 непосредственно соответствуют координатам X, Y и Z в мировом пространстве. Нам надо также гарантировать, что Direct3D знает об изменении типа рисуемых данных, поэтому мы меняем формат фиксированного конвейера функций для использования новых непреобразованных и окрашенных вершин, меняя значение свойства VertexFormat.

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

Камера управляется с помощью двух назначаемых устройству преобразований. Каждое преобразование описывается матрицей 4 × 4, передаваемой Direct3D. Преобразование проекции используется для описания того, как сцена проецируется на монитор. Один из простейших способов создания матрицы проекции — использование функции PerspectiveFovLH класса Matrix. Она позволяет создать матрицу перспективной проекции для левосторонней системы координат на основании параметров поля зрения.

Что же такое левосторонняя система координат и почему она имеет значение? В большинстве декартовых трехмерных систем координат ось X направлена вправо, а ось Y направлена вверх. Остается только ось Z. В левосторонней системе координат ось Z направлена от вас, а в правосторонней — к вам. В этом легко разобраться взяв руку и расположив ладонь вдоль положительного направления оси X. Затем согните пальцы, чтобы они были направлены вдоль положительного направления оси Y. Отогнутый большой палец будет указывать положительное направление оси Z (рис. 1.1).


Рис. 1.1. Трехмерные системы координат

Рис. 1.1. Трехмерные системы координат


Direct3D использует левостороннюю систему координат. Если вы портируете код из правосторонней системы координат, вам необходимо сделать две вещи. Во-первых, нужно изменить порядок вершин треугольников, чтобы при взгляде на лицевую сторону они располагались по часовой стрелке. Это необходимо для правильной работы механизма отбрасывания невидимых граней. Не волнуйтесь, об отбрасывании невидимых граней мы поговорим чуть позже. Во-вторых используйте матрицу вида с коэффициентом масштабирования по оси Z равным –1. Это делается путем смены знака членов M31, M32, M33 и M34 матрицы вида. Затем можете использовать версии *RH матричных функций для построения правосторонних матриц.

Сейчас, поскольку мы создаем новое приложение, будем использовать левостороннюю систему координат, которую предпочитает Direct3D. Вот прототип функции, создающей матрицу проекции:

public static Microsoft.DirectX.Matrix PerspectiveFovLH (System.Single fieldOfViewY,
     System.Single aspectRatio, System.Single znearPlane, System.Single zfarPlane )

Преобразование проекции используется для описания пирамиды видимого пространства (view frustum) сцены. Вы можете думать о ней, как о четырехгранной пирамиде с усеченной вершиной, внутренняя область которой определяет видимую часть вашей сцены. Два параметра этой пирамиды, дальний и ближний план (znearPlane и zfarPlane), определяют ее границы, причем дальний план является основанием пирамиды, а ближний план отсекает ее верхушку (рис. 1.2). Параметр поля зрения (fieldOfViewY) определяет угол у вершины пирамиды (рис. 1.3). О форматном соотношении (aspectRatio) можете думать как об отношении сторон экрана телевизора; например, в широкоэкранных телевизорах оно равно 1.85. Обычно вы вычисляете этот параметр простым делением ширины области показа на ее высоту. Direct3D рисует только объекты, находящиеся внутри пирамиды видимого пространства.


Рис. 1.2. Визуализация пирамиды видимого пространства

Рис. 1.2. Визуализация пирамиды видимого пространства



Рис. 1.3. Определение поля зрения

Рис. 1.3. Определение поля зрения


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

public static Microsoft.DirectX.Matrix LookAtLH(Microsoft.DirectX.Vector3 cameraPosition,
     Microsoft.DirectX.Vector3 cameraTarget , Microsoft.DirectX.Vector3 cameraUpVector )

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

Получив описание преобразования проекции и преобразования вида, Direct3D собрал достаточно информации, чтобы отобразить наш недавно созданный треугольник, так что давайте пойдем дальше и изменим код, чтобы что-нибудь отображалось. Мы добавим новую функцию SetupCamera, которая будет вызываться из нашей функции OnPaint сразу после выполнения очистки. Тело этой функции выглядит так:

private void SetupCamera()
{
    device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4,
         this.Width / this.Height, 1.0f, 100.0f);
    device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, 5.0f), new Vector3(),
         new Vector3(0,1,0));
}

Как видите, мы создаем матрицу проекции и устанавливаем ее как преобразование проекции устройства, чтобы Direct3D знал о нашей пирамиде видимого пространства. Затем мы создаем матрицу вида и устанавливаем ее в качестве преобразования вида устройства, чтобы Direct3D знал о нашей камере. Добавьте вызов этой функции в OnPaint сразу после вызова Clear и теперь мы готовы запустить наше приложение еще раз.

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

device.RenderState.Lighting = false;

 

ИСПОЛЬЗОВАНИЕ РЕЖИМОВ ВИЗУАЛИЗАЦИИ УСТРОЙСТВА
Существует много режимов визуализации, которые используются для управления различными этапами обработки в конвейере визуализации. Многие из них мы обсудим в последующих главах.

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

Итак теперь, когда мы поместили рисуемый треугольник в нашем мире, как заставить этот мир показать свою трехмерность? Проще всего вращать треугольник. Но как это сделать? К счастью, все очень просто; достаточно изменить мировое преобразование.

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

device.Transform.World = Matrix.RotationZ((float)Math.PI / 6.0f);

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

Замечательно, но все еще скучно; добавим остроты, сделав вращение непрерывным. Модифицируйте мировое преобразование:

device.Transform.World = Matrix.RotationZ((System.Environment.TickCount / 450.0f)
    / (float)Math.PI);

Запустив проект теперь вы должны увидеть наш треугольник, медленно вращающийся вокруг оси Z. Вращение немного дерганное, но это вызвано особенностью TickCount. Свойство TickCount класса System является оберткой метода GetTickCount из Win32 API, обеспечивающего разрешение примерно 15 миллисекунд. Это означает, что возвращаемое значение изменяется сразу на 15, что и вызывает подергивания. Можно легко сделать вращение более плавным, создав собственный увеличивающийся счетчик вместо использования TickCount. Добавьте новую переменную с именем angle типа float. Затем измените ваше мировое преобразование следующим образом:

device.Transform.World = Matrix.RotationZ(angle / (float)Math.PI);
angle += 0.1f;

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

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

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);

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

О, нет! Наш треугольник начинает вращаться, пропадает, затем вновь появляется, и так все время. Помните раньше упоминалось отбрасывание невидимых граней? Вот замечательный пример того, что оно делает. При визуализации треугольников, если Direct3D определяет, что грань не направлена на камеру, то она не рисуется. Этот процесс и называется отбрасыванием невидимых граней (back face culling). Но как во время выполнения выяснить, направлен ли конкретный примитив на камеру или нет? Взгляд на существующие в Direct3D параметры отбрасывания граней дает подсказку. Есть три варианта: без отбрасывания, по часовой стрелке и против часовой стрелки. При выборе последних двух вариантов те примитивы, чьи вершины идут в порядке обратном указаноому в режиме отбрасывания, не рисуются.

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


Рис. 1.4. Порядок обхода вершин

Рис. 1.4. Порядок обхода вершин


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

device.RenderState.CullMode = Cull.None;

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


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

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