Графика DirectX в Delphi - Михаил Краснов
Шрифт:
Интервал:
Закладка:
Работа с переменным числом вершин
Мы уже хорошо освоились в построениях фигур с помощью Direct3D и в этом небольшом разделе попробуем развить наши навыки и узнать некоторые новые для нас вещи.
Как выяснилось из многочисленных предыдущих примеров, при использовании FVF-флага DSDFVF_XYZRHW в своих построениях мы опираемся на систему координат, ассоциированную с окном. Теперь нам предстоит постичь смысл еще одного флага: D3DFVF__XYZ. При его применении система координат экрана воспроизведения выглядит так: центру окна, независимо от его размеров, соответствует точка с координатами (0, 0), правому верхнему углу окна - (1, 1), левому нижнему углу - (-1, -1).
Пока мы ограничимся такой трактовкой этого флага, а позже узнаем о его особенностях кое-что дополнительно. Сейчас же для нас важно, что при использовании этого FVF-флага мы не сможем окрашивать вершины так, как привыкли это делать, и временно можем использовать только черно-белые картинки.
На примере проекта каталога Ех05 попробуем закрепить знания об этом флаге и попутно решим еще одну задачу: научимся работать с переменным числом вершин.
Самое простое решение задачи состоит, конечно, в том, чтобы задавать размер буфера вершин максимальным. Но такой прием приводит к неэффективному расходу памяти.
Во время работы программы на экране появляются узоры, образуемые отрезками, соединяющими равномерно расположенные точки на окружности. Число узлов меняется с течением времени случайно (рис. 8.4).
Текущее значение переменной numpoints хранит число узлов. В периодически вызываемой функции initve не используется массив вершин, а применяется единственная переменная - указатель на структуру TCustomVertex. В этой структуре, в отличие от предыдущих примеров, отсутствует поле color, а описание формата данных вершины сократилось до одной константы:
const
D3DFVF CUSTOMVERTEX = D3DFVF XYZ;
Поскольку размер буфера постоянно изменяется, при каждом обращении к этой функции повторяются все действия инициализации буфера вершин:
function TfrmD3D.InitVB : HRESULT;
const
Pi2 = 2 * Pi; // Для сокращения числа операций
var
Vertices : ^TCustomVertex; // Указатель на запись вершины
i, j, k : Byte;
hRet : HRESULT;
begin
numPoints := random (7) + 3; // Генерация количества узлов
k := 0; // Подсчет количества отрезков, образующих узор
for i := 1 to numPoints do
for j := i + 1 to numPoints do begin Inc(k);
end;
numbines := k; // Используется в DrawPrimitive
// Создание буфера вершин нужного размера
hRet := FD3DDevice.CreateVertexBuffer(2 * k * SizeOf(TCustomVertex), 0,
D3DFVF__CUSTOMVERTEX, D3DPOOL_DEFAULT, FD3DVB) ;
if Failed(hRet) then begin
Result := hRet; Expend;
// Заполнение буфера
hRet := FD3DVB.Lock(0,2 * k * SizeOf(TCustomVertex), PByte(Vertices), 0);
if Failed(hRet) then begin
Result := hRet;
Exit;
end;
// Перебор точек узлов
for i := 1 to numPoints do
for j := i + 1 to numPoints do begin
// Начало отрезка, точка на окружности радиусом 0.5
Vertices.X := 0.5 * cos(Pi2 * i / numPoints);
Vertices.Y := 0.5 * sin(Pi2 * i / numPoints);
Vertices.Z := 0;
Inc(Vertices); // Сдвигаем указатель
// Конец отрезка
Vertices.X :=. 0.5 * cos(Pi2 * j / numPoints);
Vertices.Y := 0.5 * sin(Pi2 * j / numPoints);
Vertices.Z := 0; Inc(Vertices);
end;
hRet := FD3DVB.Unlock;
if Failed(hRet) then begin
Result := hRet;
Exit;
end;
// Заново устанавливаем поток
hRet := FDSDDevice.SetStreamSource(0, FD3DVB, SizeOf(TCUSTOMVERTEX));
if Failed (hRet) then begin
Result := hRet;
Exit;
end;
// Задаем вершинный шейдер
Result := FDSDDevice.SetVertexShader(D3DFVF_CUSTOMVERTEX);
end;
Последнее действие можно вынести из кода функции, чтобы выполнить его один раз, в начале работы приложения.
Беспрерывным созданием буфера вершин лучше не злоупотреблять и использовать только в случае крайней необходимости, иначе работа приложения может оказаться неустойчивой.
Теперь мы можем выяснить одну, очень важную для нас особенность использования флага D3DFVF_XYZ. Приведите код функции InitVB к следующему виду:
function TfrmDSD.InitVB : HRESULT;
var
Vertices : ^TCustomVertex;
hRet : HRESULT;
begin
hRet := FD3DDevice.CreateVertexBuffer(3 * SizeOf(TCustomVertex), 0,
D3DFVF_CUSTOMVERTEX,D3DPOOL_DEFAULT, FD3DVB);
if Failed(hRet) then begin
Result := hRet;
Exit;
end;
hRet := FD3DVB.Lock(0, 3 * SizeOf(TCustomVertex), PByte(Vertices), 0);
if Failed(hRet) then begin
Result := hRet;
Exit ;
end;
Vertices.X =0.0;
Vertices.Y = 0.0;
Vertices.Z = 0;
Inc(Vertices);
Vertices.X = 0.0;
Vertices.Y = 0.5;
Vertices.Z = 0;
Inc(Vertices) ;
Vertices.X =0.5;
Vertices.Y = 0.5;
Vertices.Z =0;
hRet := FD3DVB.Unlock;
if Failed(hRet) then begin
Result := hRet;
Exit;
end;
hRet := FD3DDevice.SetStreamSource(0, FD3DVB, SizeOf(TCUSTOMVERTEX));
if Failed (hRet) then begin
Result := hRet;
Exit;
end;
Result := FDSDDevice.SetVertexShader(D3DFVF_CUSTOMVERTEX);
end;
Таким образом, буфер вершин всегда заполняется данными о трех вершинах. Построим один треугольник. Для этого подправьте аргументы метода воспроизведения примитивов:
hRet := FD3DDevice.DrawPrimitive(D3DPT_TRIANGLELIST, О, 1);
Запустите программу и посмотрите результат: выводится один треугольник. Ничего особенного, но теперь поменяйте координаты первой и второй вершины треугольника и снова запустите программу. Экран станет чистым, ничего теперь воспроизводиться не будет. Ошибок нет. Просто для этого режима очень важен порядок перечисления вершин треугольников. Он задает сторону примитива, которую мы наблюдаем: лицевую или изнаночную. Для лицевой стороны вершины перечисляются по часовой стрелке. Поскольку в первом случае вершины треугольника задавались именно в таком порядке, зрителю видна передняя сторона треугольника. Когда мы переставили вершины, треугольник повернулся к нам своей тыльной стороной, а задние стороны треугольников по умолчанию не воспроизводятся. Поэтому мы не получили никакого результата.
Чтобы отключить режим отсечения задних сторон треугольников, можете вставить в код функции Render следующую строку:
FD3DDevice.SetRenderState(D3DRS CULLMODE, D3DCULL NONE);
То есть мы выключаем таким образом режим отсечения. Если вторым параметром использовать константу D3DCULL_CW, будут отсекаться примитивы, вершины которых перечисляются в поле зрения по часовой стрелке, а при значении, равным D3DCULL_CCW - против часовой стрелки. Именно это значение и установлено по умолчанию. В плоскостных построениях мы не станем менять установки этого режима, а будем следить за порядком перечисления вершин треугольников.
Текстура
Теперь нам предстоит изучить одну из важнейших тем - использование растровых образов. В Direct3D имеется несколько типов (стилей) текстур. Мы изучим текстуру, подобную наклеиваемым обоям.
Как всегда, для изучения нового понятия нам потребуется познакомиться с новыми типами объектов и интерфейсов. И как обычно для этой книги, знакомство осуществим на конкретном примере. Сейчас им послужит проект каталога Ех06. Работа примера очень проста: на экране выводится содержимое растрового файла - картинка с изображением дискеты (рис. 8.5).
Кратко смысл программы можно описать так: на два связанных треугольника, образующих квадрат, накладывается квадратная текстура.
В списке переменных добавилась еще одна, связанная с используемым СОМ-объектом:
FD3Texture : IDIRECT3DTEXTURE8;
В начале работы ее значением устанавливается nil, а при завершении работы перед окончательным освобождением памяти вызывается метод _Reiease этого объекта.
Формат данных вершины, помимо пространственных координат, содержит еще две, связанные с наложением текстуры:
type
TCUSTOMVERTEX = packed record
X, Y, Z : Single;
U, V : Single; // Новая пара координат в формате вершины
end;
const
D3DFVF_CUSTOMVERTEX = D3DFVF_XYZ or D3DFVF_TEX1; // Новая константа
Итак, для наложения текстуры на объект для каждой вершины должны указываться текстурные координаты. Сейчас мы используем двумерную текстуру. Она представляет собой прямоугольный массив данных. Для такой текстуры в формате вершин необходимо задавать две координаты, обычно называемые U и V. Первая из этих координат ассоциирована с горизонтальной осью текстуры, вторая, V-координата - с вертикальной. То есть для вершины, связываемой с левым нижним углом текстуры, оба эти значения должны быть нулевыми, а для вершины, к которой приклеивается правый верхний угол текстуры, оба эти значения должны быть единичными. Обращаю внимание, что эти координаты никак не связаны с пространственными координатами вершин и примитива, сам примитив не обязан иметь единичные размеры.
Константа DSDFVFJTEXI является указанием на то, что координаты текстуры задаются именно парой чисел, максимальным может быть девять координат.
При инициализации буфера вершин используем тот же прием, что и в предыдущем примере, т. е. обходимся без вспомогательного массива. Но инициализация буфера производится один раз, в самом начале работы приложения.
Квадрат располагаем в центре экрана:
function TfrmD3D.InitVB : HRESULT;
var
Vertices : ^TCustomVertex;
hRet : HRESULT;
begin
// Буфер вершин на четыре вершины квадрата
hRet := FD3DDevice.CreateVertexBuffer(4 * SizeOf(TCustomVerrex), 0,
D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, FD3DVB);
if Failed(hRet) then begin
Result := hRet;