Михаил Краснов - Графика DirectX в Delphi
procedure TfrmDSD.MoveMan;
begin
// Поворот глобальной системы координат,
// вращение всей модели вокруг своей оси
SetRotateZMatrix (matRot, Angle);
// Переменная, задающая вращение конечностей
AngleFoot := AngleFoot + StepFoot;
if (AngleFoot > Pi / 4) or (AngleFoot < -Pi / 4}
then StepFoot := -StepFoot; // Ноги вращаются в противофазе
SetRotateXMatrix (rotLeftFoot, AngleFoot);
SetRotateXMatrix (rotRightFoot, -AngleFoot); // Поворот левой ноги, в три этапа
matLeftFoot := MatrixMul(matRot,
MatrixMul(transFoot2, MatrixMul(rotLeftFoot, transFootl))); // Поворот правой ноги
matRightFoot := MatrixMul(matRot,
MatrixMul(transFoot2,
MatrixMul(rotRightFoot, transFootl))); // Поворот левой руки
matLeftHand := MatrixMul(matRot,
MatrixMul(transHand2,
MatrixMul(rotRightFoot, transHandl))); // Поворот правой руки
matRightHand := MatrixMul(matRot,
MatrixMul(transHand2, MatrixMul(rotLeftFoot, transHandl)));
end;
Рабочие матрицы, связанные с перемещениями в точки крепления конечностей, инициализируются один раз, в начале работы приложения:
SetTranslateMatrix(transFootl, О, О, 0.25);
SetTranslateMatrix(transFoot2, О, О, -0.25);
SetTranslateMatrix(transHandl, 0.25, 0.0, -0.23);
SetTranslateMatrix(transHand2, -0.25, 0.0, 0.23);
Этот пример я подготовил для использования в дальнейшем в расчете на то, что человечком можно будет легко управлять, перемещая его в пространстве. Но если на сцене присутствует только одна модель, для оптимизации можно сократить количество операций с матрицами. В самом деле, в этом примере матрица matRot, связанная с глобальной системой координат, может вообще не использоваться: модель можно не вращать и оставить неподвижной, а перемещать точку зрения наблюдателя. Эффект вращения модели останется, а количество операций существенно уменьшится.
И теперь мы можем перейти к разбору заключительного примера - проекта каталога Ех21. Как я уже говорил, это заготовка трехмерной игры: игрок попадает внутрь комнаты, населенной движущимися человечками .
Окружение игрока построено из текстур, накладываемых на треугольники, описание окружающего мира загружается из текстового файла.DirectX.
type
// Формат вершин для треугольников окружения
TNormDiffTextVertex = packed record
X, Y, Z : Single;
nX, nY, nZ : Single;
DColor : DWORD;
U, V : Single;
end;
// Формат вершин для треугольников человечков
TNormVertex = packed record
X, Y, Z : Single;
nX, nY, nZ : Single;
end;
// Отдельный треугольник описания окружения
TTriangle '= record
NumTexture : Integer; // Номер текстуры
DIFFUSE : DWORD; // Диффузная составляющая треугольника
end;
const
// FVF-флаг для треугольников окружения
D3DFVF_NORMDIFFTEXTVERTEX = D3DFVF_XYZ or D3DFVF_NORMAL or
D3DFVF_DIFFUSE or D3DFVFJTEX1; // FVF-флаг для треугольников человечков
D3DFVFJSIORMVERTEX = D3DFVF_XYZ or D3DFVFJTORMAL;
// Имя файла с описанием мира
WorldFile = 'Data/World.txt';
// Имя файла с треугольниками символов, для вывода FPS
NumbersFile = 'Data/Numbers.txt';
// Количество треугольников в описании окружения
NumTriangles = 58;
($1 legoman.pas) // Данные модели
var
frmD3D: TfrmD3D;
Frames : Integer =0; // Счетчик кадров
FpsOut : String = ''; // Значение FPS
// Вспомогательная матрица, для вывода символов FPS
LetTrans : TDSDMatrix;
// Используется как вспомогательный массив для хранения образа текстуры
TexPointer : Pointer;
// Характеристики образа текстуры
wrkTexWidth, wrkTexHeight :
Integer;
// Флаг, выводить ли FPS
flgFPS : BOOL = True;
// Угол зрения по вертикали
Lookupdown : Single = 0.0;
// Вспомогательный вектор для оптимизации
ZVector : TD3DVector;
// Угол зрения по горизонтали и положение игрока
RotY, XPos, ZPos : Single;
// Массив описания мира
World : Array [0..NumTriangles - 1] of TTriangle;
// Переменные для обработки устройств ввода
DInput : IDIRECTINPUT8 = nil;
DIMouse : IDIRECTINPUTDEVICE8 = nil;
DIKeyboard : IDirectlnputDeviceS;
KeyBuffer : TDIKeyboardState;
// Угол поворота красного человечка
Angle : Single = 0.0;
// Угол поворота конечностей человечков
AngleFoot : Single = 0.0;
StepFoot : Single = 0.1;
// Тестовая точка для определения столкновений с препятствиями
TestPointX, TestPointY : DWORD;
В файле описания окружения данных идут в следующем порядке:
строка комментария; номер текстуры; цвет треугольника; три строки описания вершин треугольника, которые включают координаты в пространстве; нормаль и текстовые координаты каждой вершины треугольника.
*
Вот что записано в текстовом файле для первого треугольника:
// Потолок
4
$00FF0000
-3.0 1.0 3.0 0.0 -1.0 0.0 0.0 0.0
-3.0 1.0 -3.0 0.0 -1.0 0.0 0.0 12.0
1.0 3.0 0.0 -1.0 0.0 12.0 0.0
Пол и потолок комнаты представляют собой квадраты с координатами точек углов по диагонали (-3; -3) и (3; 3). Координата Y для всех вершин пола нулевая, для вершин потолка - единичная. При считывании данных предусматриваем обработку исключений на случай отсутствия файла данных или присутствия ошибки при описании треугольников:
procedure TfrmD3D.SetupWorld;
var
t : TextFile;
i, j : Integer;
Vertices : /4TNormDiffTextVertex;
wrkStr : tring;
begin
if FileExists(WorldFile) then begin AssignFile(t, WorldFile);
try
Reset(t); FD3DVB.Lock(0, NumTriangles * 3 * SizeOf(TNormDiffTextVertex),
PByte(Vertices), 0) ;
for i := 0 to NumTriangles - 1 do begin
// Строка комментария, в программе не используется
ReadLn (t, wrkStr) ;
ReadLn (t, World[i].NumTexture); // Текстура треугольника
ReadLn (t, World[i].DIFFUSE); // Цвет вершин треугольника
for j := 0 to 2 do begin // Три вершины треугольника
ReadLn (t, Vertices.X, Vertices.Y, Vertices.Z,
Vertices.nX, Vertices.nY, Vertices.nZ,
Vertices.U, Vertices.V);
Vertices.DColor := World[i].DIFFUSE;
Inc(Vertices);
end;
end;
FD3DVB.Unlock;
except // Данные на треугольник заданы неверно
raise EAbort.Create ('Can''t read file: ' + WorldFile);
end;
CloseFile(t) ;
end else raise EAbort.Create ('Can''t read file: ' + WorldFile);
end;
При возникновении исключений программа завершается, описание ошибки выводится в текстовый файл.
Помимо треугольников, образующих стены комнаты, на сцене присутствуют треугольники стоящего в комнате ящика и пирамиды источника света, прикрепленного к потолку. Обратите внимание, что треугольники пола и потолка окрашены красным цветом, а треугольники препятствий, стен и ящика - синим. Позже я поясню смысл этого окрашивания.
Координаты игрока задаются значениями переменных xpos и Zpos, переменная RotY определяет угол поворота головы наблюдателя вокруг своей оси, а переменная Lookupdown - наклон головы по вертикали. Сразу после запуска игрок "располагается" в точке (0, 0, 0), направление взгляда параллельно оси X.
Текстуры треугольников задаются обычным образом, но текстуры, накладываемые на квадраты выходов из сектора, инициализируются отдельной функцией:
procedure TfrmD3D.FormCreate(Sender: TObject);
var
hRet : HRESULT;
matView, matProj : TD3DMatrix;
wrkMat : TDSDMatrix; // Вспомогательная матрица разворота человечков
begin
// Приложение полноэкранное, курсор отключаем
ShowCursor (False);
Randomize;
hRet := InitDSD;
if Failed (hRet) then ErrorOut ('InitD3D', hRet);
hRet := InitVB;
if Failed (hRet) then ErrorOut ('InitVertex', hRet);
try
InitVBLetter; // Считываются треугольники цифр
except // Возможно, файл удален
on E : EAbort do ErrorOut (PChar(E.Message), S_FALSE);
end;
InitMan; // Инициализация буфера вершин человечков
try
SetupWorld; // Считываем данные мира
// Вспомогательный вектор для видовой трансформации
ZVector := D3DVector(0, 1, 0);
// Матрица перемещений букв при выводе FPS
LetTrans := IdentityMatrix;
LetTrans._42 := 0.5;
LetTrans._43 := 0.9;
// Первоначальные положения человечков
transManl := IdentityMatrix;
transMan2 := IdentityMatrix;
transMan2._41 := 3.1; // Синий человечек перемещается по оси X
transManS := IdentityMatrix;
// Зеленый человечек устанавливается в первоначальное положение
transMan3._41:= МапЗРозХ;
transMan3._43 := ManSPosZ;
// Разворот модели человечков
SetRotateYMatrix (wrkMat, -Pi / 2);
SetRotateXMatrix (matWrkl, -Pi / 2) ;
matWrk2 := MatrixMul (wrkMat, Matwrkl);
matWrk3 := matWrk2;
// Вспомогательные матрицы для поворота конечностей
SetTranslateMatrix(transFootl, 0, 0, -0.1);
SetTranslateMatrix(transFoot2, 0, 0, 0.1);
SetTranslateMatrix(transHandl, 0.25, 0.0, -0.2);
SetTranslateMatrix(transHand2, -0.25, 0.0, 0.2);
SetupLights;
// Первоначальные установки, в дальнейшем переопределяются
SetViewMatrix(matView, D3DVector(0, 0, 0), D3DVector(0, 0, 1),
ZVector);
FDSDDevice.SetTransform(D3DTS_VIEW, matView);
SetProjectionMatrixfmatProj, 1, 1, 0.01, 6) ;
FDSDDevice.SetTransform(D3DTS_PROJECTION, matProj);
// Инициализация текстур
try
InitTexture (FD3DTextures [0], 'data/0.bmp');
InitTexture (FD3DTextures [1], 'data/1.bmp1);
InitTexture (FD3DTextures [2], 'data/2.bmp');
InitTexture (FD3DTextures [3], 'data/3.bmp');
InitTexture (FD3DTextures [4], 'data/4.bmp');
InitTexture (FDSDTextures [5], 'data/5.bmp');
BukupTexture (FD3DTextures [6], 'data/6.bmp1);
except
on E : EAbort do ErrorOut (PChar(E.Message), S_FALSE) ;
end;
OnCreateDevice; // Инициализация устройств ввода
end;
Всего предусмотрено три источника света: два направленных и один точечный, располагающийся под потолком в центре комнаты:
procedure TfrmD3D.SetupLights;
var
LightO : TD3DLight8;
Lightl : TD3DLight8;
Light2 : TD3DLight8;
begin
// Направленные источники светят во взаимно противоположных направлениях
LightO := InitDirectionalLight(D3DVector(-0.5, -0.5, -1) , 0.5,
0.5, 0.5, 0); Lightl := InitDirectionalLight(VectorNormalize(DSDVector(0.5, 0.5, D),
0.5, 0.5, 0.5, 0); // Точечный источник
ZeroMemory(@Light2, SizeOf(Light2));
with Light2 do begin
JType := D3DLIGHT_POINT;
Diffuse.r := 0.5;
Diffuse.g := 0.5;
Diffuse.b := 0.5;
Specular := Diffuse;
Ambient := Diffuse;
Position := DSDVector(0.0, 1.0, 0.0);
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;