Графика DirectX в Delphi - Михаил Краснов
Шрифт:
Интервал:
Закладка:
type
TCUSTOMVERTEX = packed record
X, Y, Z, RHW : Single;
Color : DWORD; // Добавлено новое поле
end;
Поскольку FVF-флаг задается в нескольких местах кода, вводим пользовательскую константу, хранящую нужную нам комбинацию:
const
D3DFVF_COSTOMVERTEX = D3DFVF_XYZRHW or D3DFVF_DIFFUSE;
Порядок, в котором перечисляются эти константы, безразличен, но порядок перечисления полей в записи формата вершин предопределен графической системой. При считывании данных из потока будет подразумеваться, что первыми идут координаты вершин, за ними следует цвет (диффузная составляющая).
Итак, теперь прибавляется дополнительная константа, которую будем трактовать пока как включение режима окрашивания вершин примитивов.
При инициализации массива вершин поле цвета заполняется случайным значением:
for i := 0 to MAXPOINTS - 1 do
with VPoints [i] do begin
Z := 0.0;
RHW := 0.0;
Color := D3DCOLOR_XRGB(random (256), random (256), random (256));
end;
В остальном код примера не содержит ничего для нас нового, поэтому разбирать его здесь не будем.
В следующем примере (проект каталога Ех14) окрашивание вершин используется для создания черно-белого изображения. Пример весьма занятный: из облака хаотически располагающихся точек выстраивается упорядоченный образ (рис. 7.7).
Изображение формируют 20 898 отдельных примитивов. Первоначально координаты их задаются хаотически, для каждой точки вычисляется шаг смещения. Текстовый файл содержит координаты окончательного положения точки. За 100 шагов каждая точка должна достичь финишного положения:
type
TStep = packed record // Тип для хранения скорости точки по осям
StepX, StepY : Single;
end;
var
Steps : Array [0..MAXPOINTS - 1] of TStep; // Шаги для каждой точки
function TfrmD3D.InitPoints : HRESULT;
var
pVertices : PByte;
hRet : HRESULT;
i : Integer;
t : TextFile;
wrkX, wrkY : Integer;
begin
AssignFile (t, 'points.txt');
Reset (t);
for i := 0 to MAXPOINTS - 1 do begin
ReadLn (t, wrkX, wrkY);
with VPoints [i] do begin
X := random (240);
Y := random (289) ;
// Каждая точка должна достичь своего положения за 100 шагов
Steps [i].StepX := (wrkX - X) / 100;
Steps [i].StepY := (wrkY - Y) / 100;
Z := 0.0;
RHW := 0.0;
Color := 0;
end;
end;
CloseFile (t);
...
Переменная Pointsize управляет текущим размером точки, первоначально ее значение установлено в 5.0. При перемещении точки размер ее последовательно уменьшается и через 100 шагов должен стать единичным:
function TfrmD3D.MovePoints : HRESULT;
var
pVertices : PByte; hRet : HRESULT;
i : Integer;
begin
PointSize := PointSize - 0.04; // Уменьшение размера точки.
FD3DDevice.SetRenderState( D3DRS_POINTSIZE, PDWORD(@PointSize)");
for i := 0 to MAXPOINTS - 1 do begin
with VPoints [i] do begin
X := X +- Steps [i].StepX; // Перемещение точки
Y := Y + Steps [i].StepY;
end;
end;
В цикле ожидания сообщения подсчитывается количество обновлений положения точек. Для их первой сотни вызывается функция MovePoints. Шоу можно повторить нажатием пробела:
procedure TfrmDSD.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_ESCAPE then Close else
if Key = VK_SPACE then begin
InitPoints; // Заново разбрасываем точки
PointSize := 5.0; // Размер точек снова пятикратный
Count := 0; // Очередная сотня кадров
end;
end;
Следующий пример, проект из каталога Ех15, построен по аналогичной схеме, но примитивы на секунду покрывают всю клиентскую часть окна, чтобы затем снова разлететься (рис. 7.8).
Для задания образа используется растровое изображение размером 200x146 пикселов, цвет каждого примитива определяется цветом пиксела растра:
const
MAXPOINTS = 200 * 146;
function Tf гтаОЗО.InitPoints : HRESULT;
var
pVertices : PByte;
hRet : HRESULT;
i, j, k : Integer;
bmp : TBitMap;
R, G, В : Byte;
begin
bmp := TBitMap.Create;
bmp.LoadFromFile ('Claudia.bmp'); // Загрузка растра
k := 0;
for i := 0 to 199 do
for j := 0 to 145 do begin
with VPoints [k] do begin
X := random (145);
Y := random (200);
Steps [i, j].StepX := (j - X) / 10;
Steps [i, j].StepY := (i - Y) / 10;
Z := 0.0;
// Цветовые веса пиксела растра
R := GetRValue (bmp.Canvas.Pixels [j, i]);
G := GetGValue (bmp.Canvas.Pixels [j, i]);
В := GetBValue (bmp.Canvas.Pixels [j, i]) ;
RHW := 0.0;
Color := D3DCOLOR__XRGB(R, G, B); // Цвет примитива
end;
Inc (k);
end;
bmp.Free ;
...
Приращения по координатам задаются так, чтобы за 10 шагов точка добралась до финиша. В этом примере точки, достигнув нужного положения, продолжают двигаться дальше, таким образом, что заветная картинка появляется только на миг. Через каждые 20 кадров направление движения точки меняется на противоположное:
var
Steps : Array [0..199, 0..145] of TStep;
procedure TfrmD3D.ApplicationEventslIdle(Sender: TObject;
var Done: Boolean);
var
hRet : HRESULT;
i, j : Integer;
begin
if FActive then begin
Inc (Frames);
hRet := Render;
if FAILED(hRet) then begin
FActive := False;
ErrorOut ('Render', hRet);
Exit;
end;
ThisTickCount := GetTickCount;
if ThisTickCount - LastTickCount > 25 then begin Caption := Format('%6.2f ,
[frames * 1000 / (ThisTickCount - LastTickCount)]);
Frames := 0; Inc (Count);
// Цикл движения точек в 20 кадров
if Count <= 20 then MovePoints else begin
for i := 0 to 199 do
for j := 0 to 145 do begin
Steps [i, jJ.StepX := -Steps [i, j].StepX;
Steps [i, jJ.StepY := -Steps [i, jj.StepY;
end;
Count := 0;
end;
end;
LastTickCount := GetTickCount;
end;
Done := False;
end;
Обратите внимание, что клиентская область окна в этих двух примерах неквадратная, размеры его подогнаны для конкретных образов примеров.
Итак, мы научились из отдельных точек формировать весьма сложные образы, но такой подход годится только для простых, или тестовых примеров и небольших изображений. На количество используемых примитивов системой наложено ограничение, и работа с десятками тысяч примитивов, как мы видим из этого примера, приводит к существенному снижению FPS.
Отрезки
Для рисования отрезков в Direct3D предусмотрены два типа примитивов: независимые отрезки и связанные отрезки. Начнем постижение этой темы с первого из этой пары типа примитивов.
Для построения независимых отрезков первым аргументом метода DrawPrimitive указывается константа D3DРТ_LINELISТ. По считываемым попарно из потока вершинам строятся отдельные, несвязанные, отрезки прямой.
Несложный пример из каталога Ех1б является иллюстрацией на эту тему. На экране строятся два отрезка красного цвета, параллельные друг другу. Координаты вершин хранятся в четырехэлементном массиве пользовательского типа TCUSTOMVERTEX. Массивы заполняются тривиальным образом: значения полей первых двух элементов определяют начало и конец первого отрезка, последние два элемента массива относятся ко второму отрезку.
Обратите внимание, что собственно при построении примитивов последним аргументом передается не количество вершин, а количество примитивов:
hRet := FD3DDevice. DrawPrimitive (D3DPT_LINELIST, 0, 2) ;
Если данных, поступающих из потока, недостаточно, ошибка генерироваться не станет, поскольку все недостающие данные будут считаться нулевыми.
Константа D3DРТ_LINELISТ является признаком другого примитива - группы связанных отрезков. В этом случае вершины, считываемые из потока, задают характеристики вершин, последовательно соединяемых отрезками прямой.
В проекте каталога Ех17 создается пятиугольник (рис. 7.9), в построении которого используется пять связанных отрезков.
Для получения пятиугольника требуется шесть точек, координаты первой и последней из них совпадают. Чтобы оживить картинку, текущие координаты вершин опираются на увеличивающееся значение переменной Angle:
for i := 0 to 5 do
with VPoints [i] do begin
X := 150 + cos (Angle +1*2* Pi /5) * Radius;
Y := 150 + sin (Angle +i*2*Pi/5) * Radius;
end;
Обращаю внимание на параметры метода воспроизведения примитивов:
hRet := FD3DDevice.DrawPrimitive(D3DPT_LINESTRIP, 0, 5);
Надеюсь, остальной код вопросов у вас не вызывает.
Теперь нам стоит обсудить, как воспроизводить одновременно несколько независимых групп примитивов. Организовать такое воспроизведение можно разными способами: хранить вершины в одном буфере, либо использовать отдельные буферы для каждой группы вершин.
Разберем первый вариант на примере проекта каталога Ех18. На экране вращаются два многоугольника: пятиугольник и квадрат (рис. 7.10).
Массив vpoints хранит координаты 11 вершин: первые 6 связаны с пятиугольником, оставшиеся предназначены для построения квадрата.
Квадрат и Пентагон вращаются в противоположные стороны с различными скоростями:
for i := 0 to 5 do // Первыми хранятся координаты вершин Пентагона
with VPoints [i] do begin
X := 150 + cos (Angle + i * 2 * Pi / 5) * Radius;
Y := 150 + sin (Angle +i*2*Pi/5) * Radius;
end;
for i := 0 to 4 do // Координаты вершин квадрата
with VPoints [6 + i] do begin
// Скорость вращения квадрата удвоена
X := 150 + cos (- 2 * Angle - i * Pi / 2) * Radius / 2;
Y := 150 + sin (- 2 * Angle - i * Pi / 2) * Radius / 2;
end;
Собственно при построении к методу Drawprimitive обращаемся дважды, поскольку строим две независимые фигуры. Обратите внимание на значение второго аргумента метода:
hRet := FD3DDevice.DrawPrimitive(D3DPT_LINESTRIP, 0, 5);
if FAILED(hRet) then begin