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

       

Буфер глубины



Продолжим рассмотрение нашего примера с вращающимся кубом. В нем еще остались некоторые новые для нас вещи. Рисуемые примитивы накладываются друг на друга в том порядке, в котором они воспроизводятся: нарисованные позже лежат поверх созданных ранее. Это хорошо для двумерных построений, но при переходе в ЗD-пространство нам приходится беспокоиться о том, чтобы положения объектов передавались правильно: более удаленные от глаза наблюдателя объекты могут заслонять воспроизведенные позже, но располагающиеся ближе к камере. Графическая система предлагает решение в виде использования буфера глубины - вспомогательного экрана, предназначенного только для сортировки объектов, располагающихся в пространстве. При подключении этого буфера воспроизведение осуществляется дважды: первый раз в буфер записывается информация о значении расстояния от камеры до точки, второй раз в буфер кадра помещаются данные только о точках, действительно видимых и не заслоняемых другими точками.

Примечание
Другое название буфера глубины - Z-буфер.

При инициализации Direct3D надо указать, что будет использоваться буфер глубины, и задать его формат. Обычно используется 16-битный формат:

with d3dpp do begin
Windowed := True;
SwapEffect := D3DSWAPEFFECT_DISCARD;
BackBufferFormat := dSddm.Format;
// Будет использоваться буфер глубины
EnableAutoDepthStencil := True;
AutoDepthStencilFormat := D3DFMT_D16; // 16-битный формат
end;

Размеры буфера глубины будут автоматически определяться системой при каждом изменении размеров окна.
При очередной перерисовке кадра теперь должен очищаться не только буфер кадра, но и подключенный буфер глубины. Предпоследний параметр метода clear объекта устройства - значение, которым заполняется буфер глубины. Этим значением должна быть единица, фон экрана бесконечно удален в пространстве:

FD3DDevice.Clear(0, nil, D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER,
$00FFFFFF, 1.0, 0) ;

Для разрешения работы с буфером глубины надо также задать положительный флаг для соответствующего режима:


SetRenderState(D3DRS_ZENABLE, D3DZBJTRUE);

Флагом для этого состояния может быть и обычная булева константа.
Сейчас нам необходимо перейти к следующему примеру, проекту каталога Ех03, после его запуска на экране появляется вращающийся чайник и стрелки осей координат (рис. 9.4).



Рис. 9.4. Этот пример поможет досконально разобраться с матрицами

Буфер вершин заполняется данными для трех трехмерных объектов: цилиндра, конуса и чайника:

function TfrmD3D.InitVB : HRESULT;
const
radius =0.1; // Радиус цилиндра
var
Vertices : ^TCustomVertex;
hRet : HRESULT;
theta : Single;
i : Integer;
t : TextFile; // Данные модели хранятся в текстовом файле
wX, wY, wZ : Single;
egin hRet := FD3DDevice.CreateVertexBuffer((100 + 51 * 2 + 6322 * 3) *
SizeOf(TCustomVertex), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, FD3DVB);
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;
hRet := FD3DDevice.SetVertexShader(D3DFVF_CUSTOMVERTEX);
if Failed(hRet) then begin
Result := hRet;
Exit; end; hRet := FD3DVB.Lock(0, (100 + 51 * 2 + 6322 * 3)*
SizeOf(TCustomVertex), PByte(Vertices), 0);
if Failed(hRet) then begin
Result := hRet;
Exit;
end;
// 100 вершин цилиндра, по часовой стрелке
for i ;= 49 downto 0 do begin
theta := 2 * Pi * i / 49;
Vertices.X := sin(theta) * radius;
Vertices.Y := -1;
Vertices.Z := cos(theta) * radius;
Vertices.nX := sin(theta);
Vertices.nY := 0;
Vertices.nZ := cos(theta);
Inc(Vertices);
Vertices.X := sin(theta) * radius;
Vertices.Y := 1;
Vertices.Z := cos(theta) * radius;
Vertices.nX := sin(theta);
Vertices.nY := 0;
Vertices.nZ := cos(theta);
Inc(Vertices);
end;
// Вершина конуса
Vertices.X := 0.0;
Vertices.Y := 0.0;
Vertices.Z := 1.0;
Vertices.nX := 0.0;
Vertices.nY := 0.0;
Vertices.nZ := 1.0;
Inc(Vertices) ;
// Треугольники, образующие конус
for i := 0 to 49 do begin
theta := 2 * Pi * i / 49;
Vertices.X := cos(theta);
Vertices.Y := sin(theta);
Vertices.Z := 0.0;
Vertices.nX := cos(theta);
Vertices.nY := sin(theta);
Vertices.nZ := 1.0;
Inc(Vertices);
end;
// Центр донышка конуса
Vertices.X := 0.0;
Vertices.Y := 0.0;
Vertices.Z := 0.0;
Vertices.nX := 0.0;
Vertices.nY := 0.0;
Vertices.nZ := -1.0;
Inc(Vertices);
// Круг, закрывающий конус
for i := 0 to 49 do begin
theta := 2 * Pi * i / 49;
Vertices.X := sin(theta);
Vertices.Y := cos(theta);
Vertices.Z := 0.0;
Vertices.nX := 0.0;
Vertices.nY := 0.0;
Vertices.nZ := -1.0;
Inc(Vertices);
end;
// Считьшаем данные модели из файла
AssignFile (t, 'teapot.txt');
Reset (t) ;
while not EOF(t) do begin
Readln (t, wX); // Нормаль к треугольнику
Readln (t, wY);
Readln (t, wZ) ;
Readln (t, Vertices.X); // Первая вершина треугольника
Readln (t, Vertices.Y);
Readln (t, Vertices.Z);
Vertices.nX := wX;
Vertices.nY := wY;
Vertices.nZ := wZ;
Inc (Vertices);
Readln (t, Vertices.X); // Вторая вершина треугольника
Readln (t, Vertices.Y);
Readln (t, Vertices.Z);
Vertices.nX := wX;
Vertices.nY := wY;
Vertices.nZ := wZ;
Inc (Vertices);
Readln (t, Vertices.X) ; // Последняя вершина треугольника
Readln (t, Vertices.Y);
Readln (t, Vertices.Z);
Vertices.nX := wX;
Vertices.nY := wY;
Vertices.nZ := wZ;
Inc (Vertices); end;
CloseFile (t); Result := FD3DVB.Unlock;
end;



Цилиндр радиуса 0.1 и высотой 2 строится вокруг оси Y, а конус единичной высоты - вокруг оси Z. О том, как получены точки модели, мы поговорим чуть позже, сейчас же я должен сообщить, что вершины треугольников модели перечисляются против часовой стрелки.

Текущие параметры матриц вида и проекций хранятся в следующих переменных:

FromX, FromY, FromZ : Single;
AtX, AtY, AtZ : Single;
WorldUpX, WorldUpY, WorldUpZ : Single;
fFOV, fAspect, fNearPlane, fFarPlane : Single;

Инициализируются эти переменные значениями, такими же, как в предыдущих примерах, лишь точка зрения отодвинута на единицу:

procedure TfrmDSD.FormCreate(Sender: TObject);
var
hRet : HRESULT;
begin
hRet := InitD3D;
if Failed (hRet) then ErrorOut (4nitD3D'f hRet);
hRet := InitVB;
if Failed (hRet) then ErrorOut ('InitVertex', hRet);
// Включаем источники света и инициализируем материалы
SetupLights;
MaterialRed := InitMaterial(1, 0, 0, 1);
MaterialBlue := InitMaterial(0, 0,1, 1);
MaterialGreen := InitMaterial(0, 1, 0, 1) ;
MaterialYellow := InitMaterial(1, 1, 0, 1);
FromX := 0.0; // Вектор "From"
FromY := 0.0;
FromZ := -6.0;
AtX := 0.0; // Вектор "At"
AtY := 0.0;
AtZ := 0.0;
WorldUpX := 0.0; // Вектор "WorldUp"
WorldUpY := 1.0;
WorldUpZ := 0.0;
fFOV := 1.0; // Угол обзора по оси Y
fAspect := 1.0; // Угол обзора по оси X
fNearPlane := 1.0; // Передняя плоскость отсечения
fFarPlane := 20; // Задняя плоскость отсечения
end;

Для повышения красочности на сцене присутствует два источника света:

procedure TfrmDSD.SetupLights;
var
LightO : TD3DLight8;
Lightl : TD3DLight8;
begin
LightO := InitDirectionalLight(D3DVector(-1, -1, -1), 1, 1, 1, 0);
FDSDDevice.SetLight (0, LightO);
Lightl := InitDirectionalLight(D3DVector(0, 0, 1), 1, 1, 1, 0);
FDSDDevice.SetLight (1, Lightl);
FD3DDevice.LightEnable (0, True);
FD3DDevice.LightEnable (1, True);
end;

При воспроизведении объектов сцены параметры матриц вида и проекций опираются на текущие значения управляющих переменных:

procedure TfrmDSD.DrawScene;
var
matView, matProj : TD3DMatrix;
matRotate, matTranslate : TDSDMatrix;
matRotateX, matRotateY : TD3DMatrix;
matScale : TD3DMatrix;
begin
// Цилиндр по оси X
SetRotateZMatrix(matRotate, Pi / 2);
SetTranslateMatrix(matTranslate, 1.0, 0.0, 0.0);
with FD3DDevice do begin
SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
SetTransform(D3DTS_WORLD, MatrixMul(matTranslate, matRotate));
SetMaterial(MaterialRed); // Красного цвета
DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 50 * 2 - 2);
end;
// Конус стрелки по оси Z
SetRotateYMatrix(matRotate, Pi / 2);
SetTranslateMatrix(matTranslate, 2.0, 0.0, 0.0);
SetScaleMatrix(matScale, 1.0, 0.5, 0.5);
with FDSDDevice do begin
SetTransform(D3DTS_WORLD, MatrixMul(matScale,
MatrixMul(matTranslate, matRotate)));
DrawPrimitive(D3DPT_TRIANGLEFAN, 100, 49); // Сам конус
DrawPrimitive(D3DPT_TRIANGLEFAN, 151, 50); // Донышко конуса
end;
// Цилиндр по оси Y
SetTranslateMatrix(matTranslate, 0.0, 1.0, 0.0);
with FDSDDevice do begin
SetTransform(D3DTS__WORLD, matTranslate);
SetMaterial(MaterialGreen); // Цвет - зеленый
DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 50 * 2 - 2);
end;
// Конус стрелки по оси Y SetRotateXMatrix(matRotate, -Pi / 2);
SetTranslateMatrix(matTranslate, 0.0, 2.0, 0.0);
SetScaleMatrix(matScale, 0.5, 1.0, 0.5);
with FD3DDevice do begin
SetTransform(D3DTS_WORLD, MatrixMul(matScale,
MatrixMul(matTranslate, matRotate)));
DrawPrimitive(D3DPT_TRIANGLEFAN, 100, 49);
DrawPrimitive(D3DPT_TRIANGLEFAN, 151, 50);
end;
// Цилиндр по оси Z
SetRotateXMatrix(matRotate, Pi / 2) ;
SetTranslateMatrix(matTranslate, 0.0, 0.0, 1.0);
with FD3DDevice do begin
SetTransform(D3DTS_WORLD, MatrixMul(matTranslate, matRotate));
SetMaterial(MaterialBlue); // Синего цвета
DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 50 * 2 - 2);
end;
// Конус стрелки по оси Z
SetTranslateMatrix(matTranslate, 0.0, 0.0, 2.0);
SetScaleMatrix(matScale, 0.5, 0.5, 1.0); with FD3DDevice do begin
SetTransform(D3DTS_WORLD, MatrixMul(matScale, matTranslate));
DrawPrimitive(D3DPT_TRIANGLEFAN, 100, 49);
DrawPrimitive(D3DPT_TRIANGLEFAN, 151, 50);
end;
// Чайник, вращающийся вокруг осей X и Y
SetRotateXMatrix(matRotateX, Angle);
SetRotateYMatrixfmatRotateY, Angle);
SetTranslateMatrix(matTranslate, 0.0, -1.5, 0.0);
SetScaleMatrix(matScale, 0.5, 0.5, 0.5); // Уменьшаем в два раза
with FD3DDevice do begin
SetTransform(D3DTS_WORLD, MatrixMul(matRotateX, MatrixMul(matRotateY, MatrixMul(matScale, matTranslate))));
SetMaterial(MaterialYellow);
// Вершины модели перечисляются против часовой стрелки
SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
DrawPrimitive(D3DPT_TRIANGLELIST, 100 + 51 * 2, 6322);
end;
// Матрица вида
SetViewMatrix(matView, DSDVector(FromX, FromY, FromZ),
D3DVector(AtX, AtY, AtZ), DSDVector(WorldUpX, WorldUpY, WorldUpZ));
FD3DDevice.SetTransform(D3DTS_VIEW, matView); // Матрица проекций
SetProjectionMatrix(matProj, fFOV, fAspect, fNearPlane, fFarPlane);
FD3DDevice.SetTransform(D3DTS_PROJECTION, matProj);
end;



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

procedure TfrmDSD.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VKJESCAPE then Close else
if Key = VK_SPACE then with Form2 do begin
edtFromX.Text := FloatToStr (FromX);
edtFromY.Text := FloatToStr (FromY);
edtFromZ.Text := FloatToStr (FromZ);
edtAtX.Text := FloatToStr (AtX);
edtAtY.Text := FloatToStr (AtY) ;
edtAtZ.Text := FloatToStr (AtZ);
edtWorldUpX.Text := FloatToStr (WorldUpX);
edtWorldUpY.Text := FloatToStr (WorldUpY);
edtWorldUpZ.Text := FloatToStr (WorldUpZ);
edtFOV.Text := FloatToStr (fFOV);
edtAspect.Text := FloatToStr (fAspect);
edtNearPlane.Text := FloatToStr (fNearPlane);
edtFarPlane.Text := FloatToStr (fFarPlane);
Show;
end;
end;

Первоначально мы видим только две оси: стрелка оси Z закрыта вращающейся моделью. Меняя значения координат вектора "From", мы передвигаем точку обзора - координаты той точки в пространстве, где находится глаз наблюдателя. Вектор "At" определяет точку, находящуюся в середине сцены. Если здесь задавать отличные друг от друга значения, то наша композиция будет перемещаться по плоскости экрана, т. е. этот вектор соответствует направлению взгляда наблюдателя. Вектор "WorldUp" указывает направление и величину поворота головы. Если менять значения его составляющих, оси нашей сцены начнут "меняться местами".
Значение FOV задает величину производимого увеличения в радианах. Чем меньше это число, тем крупнее выглядит наша картинка. Обратите внимание, что сами объекты при этом не перемещаются, мы как будто просто вращаем колесико настройки бинокля. Значение величины Aspect определяет степень сжатия картинки по горизонтали: чем больше это число, тем сильнее растягивается изображение. Обычно здесь передается отношение ширины окна к его высоте.
Расстояния до передней и задней плоскостей отсечения задают видимую область пространства. Расстояния отмеряются от глаза наблюдателя. Все точки, выходящие за пределы этой области, не воспроизводятся. Из соображений оптимизации плоскости сечения располагаются максимально близко друг к другу, чтобы сократить время вычислений. Обратите внимание, это очень важно: нельзя устанавливать нулевым значение расстояния до передней плоскости отсечения. Такое значение равносильно отказу от использования буфера глубины.
Надеюсь, неспешная работа с этим примером позволит вам хорошо разобраться с матрицами, определяющими вид картинки.


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