Графика DirectX в Delphi
Шрифт:
FD3DDevice.SetMaterial(Material);
// Инициализация направленного источника, белый свет
Light := InitDirectionalLight(DSDVector(0, 0, 1), 1, 1, 1, 0) ;
// Устанавливаем источник света
FDSDDevice.SetLight(0, Light);
// Включаем источник света
FD3DDevice.LightEnable(0, True);
end;
Материал и источник света являются записями (не СОМ-объекты) и имеют тип TD3DMateriais и TD3DLight8 соответственно. Пользовательская функция InitMaterial заполняет поля структуры материала и получает в качестве аргументов значения ARGB.
В примере материал задается желтым, для того, чтобы установить его. При этом используется метод SetMaterial объекта устройства.
Функция InitDirectionalLight заполняет поля структуры, описывающей направленный источник света. Первым аргументом передается вектор, задающий направление лучей света. Напоминаю, что мы наблюдаем сцену с отрицательной стороны оси Z. Чтобы лучи света были параллельны нашему взору, вектор направления задается (0, 0, 1). Следующие три аргумента описывают цветовой фильтр, накладываемый на источник света, обычно источник задается белым. Эти числа также вещественны. Значение последнего аргумента для направленного источника безразлично.
Метод setLight объекта устройства устанавливает источник света на сцене. Первый аргумент, целое число, основанное на нуле, является индексом, идентификатором источника света. Метод только задает источник света, включается же он с помощью отдельного метода, LightEnabie, первый аргумент которого - индекс нужного источника, второй аргумент - булево выражение.
Как я уже говорил, отключается воспроизведение задних сторон треугольников, т. е. тех, чьи вершины перечисляются против часовой стрелки:
SetRenderState(D3DRS__CULLMODE, D3DCULL_CCW);
Совсем не обязательно, чтобы вершины примитива перечислялись именно по часовой стрелке, можно использовать и противоположное направление. Просто желательно, чтобы существовал какой-нибудь определенный порядок перечисления, чтобы можно было отсекать воспроизведение задних сторон. Повторюсь, внутренние стороны кубика нам не видны в любом случае, поэтому и незачем тратить время на их воспроизведение. Также обращаю ваше внимание на то, что связанные треугольники приспособлены для перечисления вершин именно по часовой стрелке.
Есть и еще один важный аспект, который нам необходимо учитывать: DirectSD не может окрашивать примитивы с двух сторон. Замените последний аргумент метода Drawprimitive на 2 и установите значение для режима D3DRs_CULLMODE в D3DCULL_NONE. Теперь будет выводиться только одна сторона куба, отсечение задней стороны примитивов не производится. Обратите внимание, что когда квадрат поворачивается к зрителю задней стороной, он выводится черным, т. е. совершенно не окрашиваемым.
Кубик в нашем примере вращается вокруг двух осей одновременно:
SetRotateXMatrix(matRotateX, Angle);
SetRotateYMatrix(matRotateY, Angle);
FD3DDevice.SetTransform(D3DTS_WORLD, MatrixMul(matRotateX, matRotateY));
FD3DDevice.DrawPrimitive(D3DPT__TRIANGLELIST, 0, 12);
На рисунке куб
SetViewMatrixfmatView, D3DVector(0, 0, -2),
D3DVector(0, 0, 0), D3DVector(0, I, 0));
Есть и другой способ: действительно увеличить объект. Для этого в матрицу трансформаций надо добавить матрицу масштабирования, по главной диагонали которой стоят числа, отличные от единицы и равные масштабным множителям по трем осям отдельно. Попробуйте сейчас увеличить кубик в два раза:
procedure TfrmD3D.DrawScene;
var
matView, matProj : TD3DMatrix;
matRotateX, matRotateY : TD3DMatrix;
niatScale : TD3DMatrix; // Добавилась матрица масштабирования
begin
SetRotateXMatrix(matRotateX, Angle);
SetRotateYMatrix(matRotateY, Angle);
SetScaleMatrix(matScale, 2.0, 2.0, 2.0); // Увеличиваем в 2 раза
// Добавляем матрицу масштабирования
FD3DDevice.SetTransform(D3DTS_WORLD, MatrixMul(matScale,
MatrixMul(matRotateX, matRotateY)));
Обязательно это сделайте, чтобы увидеть, что куб действительно увеличился. Однако освещение его тоже изменилось. Связано это с тем, что векторы нормалей к вершинам вслед за масштабированием стали увеличенными, и требуется их нормализация. В таких случаях необходимо включить режим автоматической нормализации этих векторов:
SetRenderState(D3DRS NORMALIZENORMALS, DWORD (True));
Буфер глубины
Продолжим рассмотрение нашего примера с вращающимся кубом. В нем еще остались некоторые новые для нас вещи. Рисуемые примитивы накладываются друг на друга в том порядке, в котором они воспроизводятся: нарисованные позже лежат поверх созданных ранее. Это хорошо для двумерных построений, но при переходе в ЗD-пространство нам приходится беспокоиться о том, чтобы положения объектов передавались правильно: более удаленные от глаза наблюдателя объекты могут заслонять воспроизведенные позже, но располагающиеся ближе к камере. Графическая система предлагает решение в виде использования буфера глубины - вспомогательного экрана, предназначенного только для сортировки объектов, располагающихся в пространстве. При подключении этого буфера воспроизведение осуществляется дважды: первый раз в буфер записывается информация о значении расстояния от камеры до точки, второй раз в буфер кадра помещаются данные только о точках, действительно видимых и не заслоняемых другими точками.
Другое название буфера глубины - Z-буфер.
При инициализации Direct3D надо указать, что будет использоваться буфер глубины, и задать его формат. Обычно используется 16-битный формат:
with d3dpp do begin
Windowed := True;
SwapEffect := D3DSWAPEFFECT_DISCARD;
BackBufferFormat := dSddm.Format;
// Будет использоваться буфер глубины
EnableAutoDepthStencil := True;
AutoDepthStencilFormat := D3DFMT_D16; // 16-битный формат