Михаил Краснов - Графика DirectX в Delphi
Attenuation0 := 1.0;
Attenuationl := 0.0;
Attenuation2 := 0.0;
Range := 2.5;
end;
with FD3DDevice do begin SetLight(0, LightO);
SetLight(l, Lightl);
SetLight(2, Light2);
LightEnable(0, True);
LightEnable(1, True);
LightEnable (2, True);
end;
end;
Все объекты сцены, за исключением человечков, освещаются тремя источниками света. При воспроизведении человечков точечный источник выключается.
При воспроизведении сцены голову наблюдателя "помещаем" в точку, соответствующую его текущему положению в пространстве, и поворачиваем ее в направлении RotY:
procedure TfrmDSD.DrawScene;
var
i : Integer;
matView : TD3DMatrix;
begin
// Видовая матрица, в соответствии с текущими параметрами игрока
SetViewMatrix(matView, D3DVector(XPos, 0.25, ZPos).,
D3DVector (XPos + cos (RotY) ,* 0.25 + Lookupdown,
ZPos - sin (RotY)), ZVector);
with FD3DDevice do begin
SetTransform(D3DTS_VIEW, matView);
SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
SetRenderState(D3DRS_LIGHTING, DWORD (True));
end;
// При необходимости выводим значение
FPS if flgFPS then DrawLetters; // Рисуем человечков
DrawManl; // Красный
DrawMan2; // Синий
DrawMan3; // Зеленый
// Подготовка к рисованию стен
with FD3DDevice do begin
// Учитывать освещение
SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
// Проводить интерполяцию текстур
SetTextureStageState(0,D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
SetTextureStageState(0,D3DTSS_MINFILTER, D3DTEXF_LINEAR);
// He учитывать диффузию треугольников окружения
SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
SetRenderState(D3DRS_AMBIENT, $OOOFOFOF);
// Задаем белый материал
SetMaterial(MaterialWhite);
// Направляем потоки на буфер вершин описания мира
SetStreamSource(0, FD3DVB, SizeOf(TNormDiffTextVertex));
SetVertexShader(D3DFVF_NORMDIFFTEXTVERTEX);
// Координаты треугольников заданы в глобальной системе координат
SetTransform(D3DTS_WORLD, IdentityMatrix);
end;
// Цикл вывода треугольников окружения
for i := 0 to NumTriangles - 1 do with FDSDDevice do begin
// Устанавливаем нужную текстуру в соответствии с описанием
SetTexture(0, FD3DTextures[World [i].NumTexture]);
DrawPrimitive(D3DPT_TRIANGLELIST, i * 3, 1); // Вывод треугольника
end;
FD3Ddevice.SetTexture(0, nil); // Текстура больше не используется
end;
Обратите внимание, что в этом примере задано интерполирование текстур, так называемая билинейная фильтрация. Сделано это для того, чтобы при приближении к ящику и стенам не проявлялась блочность текстур, а изображение не становилось бы крупнозернистым.
Учтите, что использование интерполяции существенно снижает работу приложения, поэтому обычно ее включают только при приближении к поверхности, покрытой текстурой. Другой способ достижения мелкой зернистости - использование чередующихся текстур. В зависимости от расстояния до объекта на него накладываются текстуры различной детализации.
Также я должен напомнить, что для оптимизации работы приложения следует применять запомненные блоки состояний.
Три человечка, присутствующие на сцене, перемещаются по различным законам. Первый, одетый в красную футболку, беспрерывно кружит вокруг центра комнаты:
procedure TfrmD3D.MoveManl;
begin
// Поворот вокруг вертикальной оси
SetRotateYMatrix (rotManl, Angle + Pi);
// Перемещение по кругу
transManl._41 := cos (-Angle) / 2;
transManl._43 := sin(-Angle) / 2;
// Опорная трансформация первого человечка
matManl := MatrixMul(transManl, MatrixMul(rotManl, matWrkl));
Второй человечек пересекает комнату, появляясь из одной стены и исчезая в противоположной:
procedure TfrmD3D.MoveMan2;
begin
// Изменение Х-координаты
transMan2._41 := transMan2._41 - 0.01;
// При прохождении комнаты процесс начинается сначала
if transMan2._41 < -3.1 then transMan2._41 := 3.1;
matMan2 := MatrixMul(transMan2, matWrk2);
Третий человечек назойливо преследует игрока, перемещается в направлении к наблюдателю, всегда разворачиваясь к нему лицом:
procedure TfrmD3D.MoveMan3;
var
wrkAngle : Single;
distX, distZ : Single;
begin
// Расстояния до игрока
distX := XPos - МапЗРозХ;
distZ := ZPos - ManSPosZ;
// Вычисляем угол поворота человечка
if distZ < 0
then wrkAngle := arctan (distX / distZ) - Pi / 2 else
wrkAngle := arctan (distX / distZ) + Pi / 2; // Разворот человечка лицом к игроку
SetRotateYMatrix (rotMan3, wrkAngle);
// Если человечек удален от зрителя, то двигается,в его направлении
if (abs(distX) > 0.02) and (abs (distZ) > 0.02) then begin
МапЗРозХ := МаnЗРозХ + distX / 20; // Новое положение человечка
Man3PosZ := Man3PosZ + distZ / 20;
transMan3._41 := МаnЗРозХ;
transMan3._43 := Man3PosZ;
end;
// Опорная матрица третьего человечка
matMan3 := MatrixMul(transManS, MatrixMul(rotMan3, matWrk2));
Для упрощения вычислений я позволяю человечкам проходить сквозь препятствия и друг через друга. Код предотвращения этих ситуаций очень прост, и вы можете самостоятельно дополнить его отслеживанием таких ситуаций.
Для вывода значения FPS треугольники символов цифр и точки объединены мною в один файл numbers.txt. Процедура piaceLetter определяет в потоке положение и количество треугольников для нужного символа:
procedure TfrmD3D.DrawLetters;
var
i : Integer; nS, nW : Integer;
begin
with FDSDDevice do begin
// Некоторые треугольники построены против часовой
SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
//Не тратить время на освещение
SetRenderState(D3DRS_LIGHTING, DWORD (False));
// Направляем поток на буфер символов
SetStreamSource(0, FD3DVBLetter, SizeOf(TNormVertex));
SetVertexShader(D3DFVF_NORMVERTEX);
end;
// Цикл вывода в пространстве символов FPS for i := 1 to Length(FpsOut) do begin
// Получаем положение треугольников символа
PiaceLetter (FpsOut[i], nS, nW);
// Сдвигаемся в пространстве для вывода очередного символа
LetTrans._41 := i * 0.1;
FD3DDevice.SetTransform(D3DTS_WORLD, LetTrans);
FD3DDevice.DrawPrimitive(D3DPTJTRIANGLELIST, nS, nW);
end;
// Возвращаем обычные установки with FD3DDevice do begin
SetRenderState(D3DRS_COLLMODE, D3DCULL_CCW);
SetRenderState(D3DRS_LIGHTING, DWORD (True) ) ;
end;
end;
Символы выводятся "подвешенными" в воздухе, что выглядит красиво и загадочно.
Приложение я тестировал на машине с очень скромными ресурсами. Возможно, вы получите более впечатляющую цифру. Наиболее весомый удар по скорости работы данного примера наносится фильтрацией текстуры, а усложнение игрового мира не приведет к сильному падению этого значения, до некоторой степени. Например, удаление человечков практически не сказывается на скорости работы программы. Также динамическая смена текстуры, используемая мною для стен, символизирующих выходы из сектора, не привела к заметному замедлению:
function TfrmD3D.BukupTexture (var FDSTextBMP : IDIRECT3DTEXTURE8;
const FileName : String) : HRESULT;
var
hRet : HRESULT;
d3dlr : TD3DLOCKED__RECT;
dwDstPitch : DWORD;
X, Y : DWORD;
Bmp : TBitmap;
R, G, В : Byte;
begin
Bmp := TBitmap.Create;
try
Bmp.LoadFromfile (FileName);
except
raise EAbort.Create ('Can''t open file: ' + FileName);
Result := S_FALSE;
Exit;
end;
hRet := FD3DDevice.CreateTexture (Bmp.Width, Bmp.Height, 0, 0,
D3DFMT_A8R8G8B8, D3DPOOL MANAGED, FD3TextBMP);
if FAILED(hRet) then begin
Result := hRet;
Exit;
end;
hRet := FD3TextBMP.LockRect(0, d3dlr, nil, 0) ;
if FAILED(hRet) then begin
Result := hRet;
Exit;
end;
dwDstPitch := d3dlr.Pitch;
for Y := 0' to Bmp.Height - 1 do
for X := 0 to Bmp.Width - 1 do begin
R := GetRValue (Bmp.Canvas.Pixels [X,
DWORD (Bmp.Height -1) - Y] ) ;
G := GetGValue (Bmp.Canvas.Pixels [X,
DWORD (Bmp.Height -I) - Y] ) ;
В := GetBValue (Bmp.Canvas.Pixels [X,
DWORD (Bmp.Height -I) - Y] ) ;
PDWORD(DWORD(d3dlr.pBits)+Y*dwDstPitch+X*4)л :=
D3DCOLOR_XRGB(R, G, B);
end;
// Резервируем место для копии первоначального растра
GetMem (TexPointer, 4 * Bmp.Width * Bmp.Height); // Запоминаем первоначальньй растр
CopyMemory (TexPointer, d3dlr.pBits, 4 * Bmp.Width * Bmp.Height)
wrkTexWidth := Bmp.Width; wrkTexHeight := Bmp.Height; Bmp.Free;
Result := FDSTextBMP.UnlockRect(0);
end;
// Покрытие снегом текстуры
function TfrmDSD.SnowTexture (var FD3TextBMP : IDIRECT3DTEXTURE8)
HRESULT;
var
hRet : HRESULT;
d3dlr : TD3DLOCKED_RECT;
i : Integer;
dwDstPitch : DWORD;
begin
// Запираем прямоугольник текстуры
hRet := FDSTextBMP.LockRect(0, d3dlr, nil, 0);
if FAILED(hRet) then begin
Result := hRet;
Exit;
end;
// Копируем в него первоначальный растр
CopyMemory (d3dlr.pBits, TexPointer, 4 * wrkTexWidth * wrkTexHeight);
dwDstPitch := d3dlr.Pitch;
// Произвольные точки текстуры закрашиваем черным
for i := 1 to 10000 do
PDWORD (DWORD(d3dlr.pBits) + DWORD(random(wrkTexHeight)) * dwDstPitch +
DWORD(random(wrkTexWidth)) * 4)Л := 0; Result := FD3TextBMP.OnlockRect(0);
end;
Одно из самых важных мест кода - управление игроком. Перемещения мыши изменяют его положение и угол поворота головы по горизонтали, клавиши управления курсором отвечают за положение игрока в пространстве, клавиши <Page Up> и <Page Down> ответственны за угол поворота головы по вертикали:
function TfrmD3D.ReadImmediateData : HRESULT;
var
hRet : HRESULT; dims2 : TDIMOUSESTATE2;
NewXPos, NewZPos : Single;
begin
Zero-Memory (8dims2, SizeOf (dims2) ) ;
hRet := DIMouse.GetDeviceState(SizeOf(TDIMOUSESTATE2), @dims2);
if Failed (hRet) then begin
hRet := DIMouse.Acquire;
while hRet = DIERR_INPUTLOST do
hRet := DIMouse.Acquire; end;
// Перемещение курсора мыши влево-вправо
if dims2.1X <> О
// Меняем угол поворота головы по горизонтали
then RotY := RotY + 0.01 * dims2.1X; // Перемещение курсора мыши вперед-назад
if dims2.1Y > 0 then begin // Движение игрока назад
// Вычисляем новое положение
NewXPos := XPos + sin(RotY - Pi / 2) * 0.05;
NewZPos := ZPos + cos(RotY - Pi / 2) * 0.05;
// Нет ли препятствий к движению назад, голову разворачиваем
if TestRender (NewXPos, NewZPos, RotY - Pi) then begin
XPos := NewXPos; // Препятствий нет, перемещаем игрока
ZPos := NewZPos;
end
end else if dims2.1Y < 0 then begin // Движение вперед
NewXPos := XPos + sin(RotY + Pi / 2) * 0.05;
NewZPos := ZPos + cos(RotY + Pi / 2) * 0.05;
// Есть ли препятствия к движению
if TestRender (NewXPos, NewZPos, RotY) then begin