Программирование 3D-игр в DirectX на Delphi
 
         
    [Главная] [Новости] [Статьи] [Игры] [Проекты] [Автор]  
         
  Основы 3D-программирования DirectX8.1 в Delphi 6-7: теоретические и практические основы создания игр.  
     
 
Отображение моделей и работа с камерой
 
     
 

25.01.2008

Заметки посвящены проблемам отрисовки статичных моделей (мешей) и работе со свободной камерой CD3DCamera использующей возможности CD3DArcBall и управление от DirectInput

 
     
  [Все уроки]  
     
  Данные заметки покажут Вам - способ описания проблемы для лучшего уяснения самому себе; - загрузку и отображение статичных мешей; использование возможностей ArcBall для целей свободного управления игровой камерой.  
     
   

Для выполнения упражнений Вам понадобится:

- архив с исходным кодом; (1,84 Mb)

- архив с ОБЩИМ КОДОМ; (772 Кб)

Архив с исходным кодом содержит пример решения поставленной задачи, архив с общим кодом - содержит дополнительный общий код необходимый при разработке Delphi DirectX 8.1 - приложений.  
     
  За основу взят пример OpenMesh.    
       
 

В методе Create приложения (CMyD3DApplication.Create)

Задаем путь для загружаемой при старте приложения модели

m_strMeshFilename:= 'Media\Models\scene1\spacebox0.x';

Создаем объект для камеры (пока вместо камеры будет ArcBall!)

m_ArcBall := CD3DArcBall.Create;

- архив с исходным кодом; (1,84 Mb)
 
     
 

В методе InitDeviceObjects приложения (CMyD3DApplication.InitDeviceObjects)

Производим загрузку модели посредством интерфейса ID3DXMesh и ее текстур

Здесь же производим вычисление сферы ограничивающей модель (D3DXComputeBoundingSphere)

и вычисляем нормали (если они отсутствуют). Нормали будут нужны, т.к. при отображении будет

использоваться освещение (см. метод CMyD3DApplication.RestoreDeviceObjects)

 
     
 

В методе RestoreDeviceObjects приложения (CMyD3DApplication.RestoreDeviceObjects)

Происходит установка необходимых состояний, создание источника света

Установка вычисленного ранее радиуса ограничивающей сферы для ArcBall

Установка проекционной трансформации

 
     
  В методе Render приложения осуществляется отрисовка (отображение) с использованием примененных к модели материалов  
     
 

В методе FrameMove приложения осуществляется -

D3DXMatrixTranslation(matWorld, -m_vObjectCenter.x,
-m_vObjectCenter.y,
-m_vObjectCenter.z ); //учет смещений центра модели

D3DXMatrixMultiply(matWorld, matWorld, m_ArcBall.GetRotationMatrix^); //установка трансформаций от ArcBall
D3DXMatrixMultiply(matWorld, matWorld, m_ArcBall.GetTranslationMatrix^);
m_pd3dDevice.SetTransform(D3DTS_WORLD, matWorld); //установка мировой трансформации

// Set up view matrix
D3DXMatrixLookAtLH(matView, D3DXVector3(0, 0,-3*m_fObjectRadius),
D3DXVector3(0, 0, 0),
D3DXVector3(0, 1, 0));
m_pd3dDevice.SetTransform(D3DTS_VIEW, matView); //установка трансформации вида

 
     
 

Использование поведения ArcBall для работы с камерой CD3DCamera и управления от DirectInput (компонент TDXInput из модуля DX8_DIUtil8.pas)

За основу был взят метод CD3DArcBall.HandleMouseMessages

Нужно адаптировать его для указанных целей.

Нажатия клавиш мыши или любых других кнопок регистрируются элементарно.

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

Поскольку m_DXInput.Mouse.X и m_DXInput.Mouse.Y отображают только моментальное перемещение мыши,

то использование индикатора

m_UserInput.bMove := not ((m_DXInput.Mouse.X = 0) or
(m_DXInput.Mouse.Y = 0));

не позволяет вызывать код в строках -

if m_UserInput.bMove then
begin

{...}

Можно накапливать моментальные перемещения мыши следующим образом -

m_UserInput.MouseMoveX := m_UserInput.MouseMoveX + m_DXInput.Mouse.X;
m_UserInput.MouseMoveY := m_UserInput.MouseMoveY + m_DXInput.Mouse.Y;

 
     
 

При этом отслеживать перемещение можно запоминая последнее перемещение и

сравнивая его с текущим. Отсутствие изменений будет свидетельствовать об отсутствии перемещения -

iMouseX := m_UserInput.MouseMoveX;
iMouseY := m_UserInput.MouseMoveY;

m_UserInput.bMove := not ((iMouseX = iCurMouseX) or
(iMouseY = iCurMouseY));

 
     
  if m_UserInput.bMove then begin
iCurMouseX:= iMouseX;
iCurMouseY:= iMouseY;
end;
 
     
 

Для реализации перемещений от правой и средней кнопок мыши пришлось сделать:

if m_UserInput.bRMouseBtn or m_UserInput.bMMouseBtn then
begin
// Normalize based on size of window and bounding sphere radius
{fDeltaX:= (iCurMouseX - iMouseX) * m_fRadiusTranslation / m_iWidth;
fDeltaY:= (iCurMouseY - iMouseY) * m_fRadiusTranslation / m_iHeight;}

fDeltaX:= (-m_DXInput.Mouse.X) * m_fRadiusTranslation / m_iWidth;
fDeltaY:= (-m_DXInput.Mouse.Y) * m_fRadiusTranslation / m_iHeight;

//if (Msg.wParam and MK_RBUTTON) = MK_RBUTTON then
if m_UserInput.bRMouseBtn then
begin
D3DXMatrixTranslation(m_matTranslationDelta, -2*fDeltaX, 2*fDeltaY, 0.0);
D3DXMatrixMultiply(m_matTranslation, m_matTranslation, m_matTranslationDelta);
end
else // wParam & MK_MBUTTON
begin
D3DXMatrixTranslation(m_matTranslationDelta, 0.0, 0.0, 5*fDeltaY);
D3DXMatrixMultiply(m_matTranslation, m_matTranslation, m_matTranslationDelta);
end;

// Store mouse coordinate
{iCurMouseX:= iMouseX;
iCurMouseY:= iMouseY;}

end;

 
     
  Т.е. зависимость от индикатора bMove пришлось убрать, для перемещений использовать значения -m_DXInput.Mouse.X, -m_DXInput.Mouse.Y (минус - для смены направлений).  
     
  Как осуществляется работа по вращениям от левой кнопки мыши внутри CD3DArcBall.HandleMouseMessages?  
     
 

Записывается текущее состояние мыши -

iMouseX := LOWORD(Msg.lParam);
iMouseY := HIWORD(Msg.lParam);

Если нажата левая кнопка -

m_bDrag:= True;
s_vDown:= ScreenToVector(iMouseX, iMouseY);
m_qDown:= m_qNow;

Exit;

Если отпущена левая кнопка -

m_bDrag:= FALSE;

Exit;

Если мышь перемещается -

то если установлен индикатор m_bDrag

// recompute m_qNow
vCur:= ScreenToVector(iMouseX, iMouseY);
// вектор текущего положения мыши
D3DXQuaternionAxisToAxis(qAxisToAxis, s_vDown, vCur);
// кватернион ориентации между двумя векторами
m_qNow := m_qDown;
// текущий кватернион и

D3DXQuaternionMultiply(m_qNow, m_qNow, qAxisToAxis);
// ориентация по двум векторам

D3DXMatrixRotationQuaternion(m_matRotationDelta, qAxisToAxis);
// из кватерниона получаем матрицу момента //вращения
end else
D3DXMatrixIdentity(m_matRotationDelta);
// индикатор не установлен - матрица момента вращения = матрица //идентичности

D3DXMatrixRotationQuaternion(m_matRotation, m_qNow); // определение матрицы вращения по кватерниону
m_bDrag:= TRUE;

// Store mouse coordinate
iCurMouseX:= iMouseX;
// запоминаем текущие координаты мыши
iCurMouseY:= iMouseY;

Exit;

 
     
 

момент вращения в коде нигде не используется, поэтому его можно смело убрать

// recompute m_qNow
vCur:= ScreenToVector(iMouseX, iMouseY);
// вектор текущего положения мыши
D3DXQuaternionAxisToAxis(qAxisToAxis, s_vDown, vCur);
// кватернион ориентации между двумя векторами
m_qNow := m_qDown;
// текущий кватернион и

D3DXQuaternionMultiply(m_qNow, m_qNow, qAxisToAxis);
// ориентация по двум векторам

D3DXMatrixRotationQuaternion(m_matRotation, m_qNow); // определение матрицы вращения по кватерниону
m_bDrag:= TRUE;



// запоминание координат и выход Exit также не играют никакой роли, поэтому были убраны

 
     
 

В итоге получается следующее:

- В момент нажатия LMB формируется вектор s_vDown по координатам iMouseX, iMouseY и запоминается текущая

ориентация (m_qDown) ;

- В момент перемещений мыши с нажатой LMB определяется текущий вектор vCur, определяется ориентация между положением в момент нажатия LMB и в момент перемещений с LMB.

 

 
     
 

Остаются следующие трудности: вращение происходит не слишком послушно перемещениям мыши.

Это проявляется в том, что вращение несогласовано с движениями мыши - слишком быстро и не зависит от направления перемещения. Очень трудно подобрать нужную позицию мыши, чтобы осуществить изменение ориентации не только в плоскости, но и в пространстве. В случае с ArcBall это происходит легко и непринужденно.

Что важно для определения ориентации?

if m_UserInput.bLMouseBtn then
begin
s_vDown:= ScreenToVector(iMouseX, iMouseY);
//вектор текущего положения мыши
m_qDown:= m_qNow;

if m_UserInput.bMove then
begin
// recompute m_qNow
vCur:= ScreenToVector(m_DXInput.Mouse.X,
m_DXInput.Mouse.Y);
//вектор положения мыши в момент начала движения с нажатой LMB
D3DXQuaternionAxisToAxis(qAxisToAxis, s_vDown, vCur);
m_qNow := m_qDown;

D3DXQuaternionMultiply(m_qNow, m_qNow, qAxisToAxis);

D3DXMatrixRotationQuaternion(m_matRotation, m_qNow);

end;

end;

Введение в код учета момента времени fTimeLapsed := DXUtil_Timer( TIMER_GETELAPSEDTIME );

s_vDown:= ScreenToVector(Round(iMouseX*fTimeLapsed),
Round(iMouseY*fTimeLapsed));

улучшает ситуацию в плане согласованности от перемещений мыши. Причем введение любых дополнительных коэффициентов - iMouseX*fTimeLapsed*10000 или iMouseX*fTimeLapsed*0.0001 на ситуацию никак не влияет.

 
     
  Для улучшения изменений ориентации нужно сравнить насколько должны отличаться друг от друга векторы s_vDown и vCur на которые непосредственно влияют значения координат мыши в моменты времени формирования этих векторов.  
     
 

s_vDown получается медленно изменяемой величиной (с точки зрения мнгновенных моментов времени) - s_vDown:= ScreenToVector(iMouseX, iMouseY);

vCur более быстро изменяемая величина, но диапазон значений очень узок из-за того, что берутся мнгновенные значения перемещений мыши - vCur:= ScreenToVector(m_DXInput.Mouse.X,
m_DXInput.Mouse.Y);

Если для vCur использовать те же принципы изменения величины (т.е. iMouseX, iMouseY), то опять получаем слишком несогласованные и быстрые вращения - vCur:= ScreenToVector(iMouseX, iMouseY);

Если для s_vDown использовать принципы изменения для мнгновенных перемещений - s_vDown:= ScreenToVector(m_DXInput.Mouse.X,
m_DXInput.Mouse.Y);
, то вращения вообще перестают происходить из-за формирования практически идентичных векторов

 
     
 

Введение в код отладочной строки выводящей текущие значения векторов для ArcBall и для GameCamera -

в методе CMyD3DApplication.Render -

{[m_ArcBall.m_vDown.x, m_ArcBall.m_vDown.y, m_ArcBall.m_vDown.z,
m_ArcBall.m_vCur.x, m_ArcBall.m_vCur.y, m_ArcBall.m_vCur.z]);}
[m_UserInput.vDown.x, m_UserInput.vDown.y, m_UserInput.vDown.z,
m_UserInput.vCur.x, m_UserInput.vCur.y, m_UserInput.vCur.z]);

позволило установить ряд заблуждений, которые были успешно выявлены и исправлены.

 
     
 

- формирование вектора s_vDown должно происходить не в момент нажатия, а в момент отпускания левой кнопки мыши (LMouseButtonUp);

- формирование вектора vCur должно происходить когда мышь движется с нажатой LMB (LMouseButtonDown);

- если в качестве координат курсора мыши использовать накапливаемые моментальные перемещения, то ощущается значительная несогласованность вращений. Это связано с тем, что данные координаты имеют знак и не согласованы с координатами в границах окна. Проблема решается очень просто -

Windows.GetCursorPos(MouseCursorPos);
Windows.ScreenToClient(m_hWnd, MouseCursorPos);

Т.е. если использовать системные координаты курсора приведенные к размерам окна приложения, а в соотвествующих координатах векторов использовать их -

s_vDown := ScreenToVector(MouseCursorPos.X, MouseCursorPos.Y);

{...}

vCur := ScreenToVector(MouseCursorPos.X, MouseCursorPos.Y);

То мы получим полное ощущение полноценных перемещений и вращений как и в случае с ArcBall.

 
     
 

Важные следствия вытекающие из результатов данной работы:

1) в примере с организацией перемещений курсора мыши по экрану (создание игрового меню) можно использовать не только функцию текущих координат мыши, но и использовать собственный учет координат через переменные -

(Можно накапливать моментальные перемещения мыши следующим образом) -

m_UserInput.MouseMoveX := m_UserInput.MouseMoveX + m_DXInput.Mouse.X;
m_UserInput.MouseMoveY := m_UserInput.MouseMoveY + m_DXInput.Mouse.Y;

2) поскольку в примере SkinnedMesh также используется ArcBall, то его легко переделать под использование камеры CD3DCamera в соответствии с особенностями описанными нами выше.

 
     
  Код данного примера поможет Вам разобраться как происходит переделка возможностей ArcBall под видоизмененную CD3DCamera - камеру.  
     
    Что нужно сделать с кодом, чтобы работал ArcBall Что нужно сделать с кодом, чтобы работала камера  
         
   

в методе CMyD3DApplication.MsgProc - раскомментировать строку кода -

//** m_ArcBall.HandleMouseMessages(hWnd, uMsg, wParam, lParam);

в методе CMyD3DApplication.RestoreDeviceObjects - раскомментировать строку кода - //** SetupArcBall;

а строку кода SetupCamera; закомментировать -

//SetupCamera

в методе CMyD3DApplication.FrameMove - раскомментировать строку кода - //** MoveArcBall;

а строки

UpdateInput;

MoveCamera; - закомментировать -

//UpdateInput;

//MoveCamera;

 

Вернуть произведенные изменения к исходному виду  
         
  Не забудьте также после проделанных изменений перекомпилировать код!!!  
     
     
     
  [Назад] [Все уроки]  
     
    [Главная] [Новости] [Статьи] [Игры]    
         
(c) Мега Информатик 2006-2008    
Hosted by uCoz