DirectX Графика в проектах Delphi

       

Работа с переменным числом вершин



Мы уже хорошо освоились в построениях фигур с помощью Direct3D и в этом небольшом разделе попробуем развить наши навыки и узнать некоторые новые для нас вещи.
Как выяснилось из многочисленных предыдущих примеров, при использовании FVF-флага DSDFVF_XYZRHW в своих построениях мы опираемся на систему координат, ассоциированную с окном. Теперь нам предстоит постичь смысл еще одного флага: D3DFVF__XYZ. При его применении система координат экрана воспроизведения выглядит так: центру окна, независимо от его размеров, соответствует точка с координатами (0, 0), правому верхнему углу окна - (1, 1), левому нижнему углу - (-1, -1).
Пока мы ограничимся такой трактовкой этого флага, а позже узнаем о его особенностях кое-что дополнительно. Сейчас же для нас важно, что при использовании этого FVF-флага мы не сможем окрашивать вершины так, как привыкли это делать, и временно можем использовать только черно-белые картинки.
На примере проекта каталога Ех05 попробуем закрепить знания об этом флаге и попутно решим еще одну задачу: научимся работать с переменным числом вершин.
Самое простое решение задачи состоит, конечно, в том, чтобы задавать размер буфера вершин максимальным. Но такой прием приводит к неэффективному расходу памяти.
Во время работы программы на экране появляются узоры, образуемые отрезками, соединяющими равномерно расположенные точки на окружности. Число узлов меняется с течением времени случайно (рис. 8.4).

Рис. 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 - против часовой стрелки. Именно это значение и установлено по умолчанию. В плоскостных построениях мы не станем менять установки этого режима, а будем следить за порядком перечисления вершин треугольников.


Содержание раздела