Информатика и образование
  Мобильная версия сайта            
               
[Главная] [Новости]
[Статьи]
[Проекты]
[Ссылки]
[Автор]
               
    [Архив новостей]        
               
  [Форум] на форуме можно задать вопрос, посмотреть ответы на часто задаваемые вопросы  
       
  Здравствуйте! Вы попали на информационно-образовательный сайт посвященный информатике, информационным технологиям и компьютерным играм. Подробнее о целях и задачах сайта в разделе Главная. [English version of this page here...]    
       
  [Базовые уроки по DirectX] [Основы DirectMusic на Delphi] [Основы DirectInput8 на Delphi] [Основы DirectSound8 на Delphi]    
  [Разработка компьютерной игры] [Пример игры Donuts3D] [Delphi DirectX]    
       
  Основы 3D-программирования DirectX8.1 в Delphi 6-7: теоретические и практические основы создания игр.    
       
  Анимация    
       
 

10.01.2008

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

   
       
  [Все уроки]    
       
  Вывод моделей с использованием шейдеров    
       
  За основу взят пример SkinnedMesh.    
       
  Для начала нужно объявить все необходимые типы, константы и создать вспомогательные классы:    
       
  type
METHOD = (
D3DNONINDEXED,
D3DINDEXED,
SOFTWARE,

D3DINDEXEDVS,
NONE
);

   
       
  Данный тип будет определять метод выполнения анимации: неиндексированный, индексированный, программный, индексированный через вершинный шейдер.    
       
  SMeshContainer //этот класс исходя из названия будет служить контейнером меша модели    
       
 

структуры

PSRotateKeyXFile //поле определяющее трансформацию вращения в текущем кадре внутри XFile'а (кватернион)

PSScaleKeyXFile //поле определяющее трансформацию масштабирования (один вектор, определяющий масштабные коэффициенты по всем трем осям)

PSPositionKeyXFile //поле определяющее трансформацию смещения (вектор)

PSMatrixKeyXFile //поле определяющее матрицу трансформации

и аналогичные, но без суффикса XFile

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

   
       
  класс SFrame //этот класс исходя из названия будет служить контейнером кадра    
       
  класс SDrawElement //этот класс служит для отрисовки цепочки кадров    
       
 

В классе приложения объявляются следующие поля:

CMyD3DApplication = class(CD3DApplication)

{...}

m_method: METHOD; //используемый метод анимации

m_dwFVF: DWord; //формат вершин анимированной модели

m_ArcBall: CD3DArcBall; //инструмент для манипулирования моделью - вращения, перемещения, масштабирования

m_pmcSelectedMesh: SMeshContainer; //текущий меш
m_pframeSelected: SFrame;
//текущий кадр
m_pdeSelected: SDrawElement;
//текущая цепочка кадров
m_pdeHead: SDrawElement;
//основание цепочки кадров (корневой элемент иерархии кадров модели)

m_szPath: array [0..MAX_PATH-1] of Char; //имя файла модели
m_pBoneMatrices: array of TD3DXMatrix;
//массив матриц костей скелета
m_maxBones: DWord;
//максимальное число костей

m_dwIndexedVertexShader: array[0..3] of DWord; //вершинный шейдер
m_mView: TD3DXMatrixA16;
//View Matrix (матрица Вида)

{...}

   
       
 

В методе создания приложения создаем или обнуляем все необходимые объекты и поля.

constructor CMyD3DApplication.Create;
begin


{...}


m_ArcBall := CD3DArcBall.Create;
m_pmcSelectedMesh := nil;
m_pframeSelected := nil;
m_pdeHead := nil;
m_pdeSelected := nil;

m_dwFVF := D3DFVF_XYZ or D3DFVF_DIFFUSE or D3DFVF_NORMAL or D3DFVF_TEX1; //это можно записать как

const

AnimMeshFVF = D3DFVF_XYZ or D3DFVF_DIFFUSE or D3DFVF_NORMAL or D3DFVF_TEX1; //в секции interface модуля

m_dwFVF := AnimMeshFVF; //и данное вместо написанного выше

m_method := D3DNONINDEXED;

m_pBoneMatrices:= nil;
m_maxBones:= 0;

m_szPath:= #0;
end;

   
       
 

Загружаем анимированный меш, устанавливаем параметры инструмента ArcBall

function CMyD3DApplication.InitDeviceObjects;
begin
{...}

m_szPath := 'nanoco_go.x';
//данную строку опять же можно записать константой в секции интерфейса с последующим присвоением здесь

LoadMeshHierarchy; //всё самое сложное, касающееся загрузки анимации находится внутри данной процедуры

if (m_pdeHead <> nil) then
m_ArcBall.SetRadius(m_pdeHead.fRadius);
m_ArcBall.SetRightHanded(TRUE);

Result:= S_OK;
end;

   
       
 

function CMyD3DApplication.RestoreDeviceObjects;

begin
{...}

//внутри этого метода происходит

m_pd3dDevice.SetRenderState(D3DRS_DITHERENABLE, iTRUE); //установка необходимых состояний ...

// создание контейнера для вершинного шейдера
dwIndexedVertexDecl1[0]:= D3DVSD_STREAM(0);
dwIndexedVertexDecl1[1]:= D3DVSD_REG(0, D3DVSDT_FLOAT3); // Position of first mesh
dwIndexedVertexDecl1[2]:= D3DVSD_REG(2, D3DVSDT_D3DCOLOR); // Blend indices
dwIndexedVertexDecl1[3]:= D3DVSD_REG(3, D3DVSDT_FLOAT3); // Normal
dwIndexedVertexDecl1[4]:= D3DVSD_REG(4, D3DVSDT_FLOAT2); // Tex coords
dwIndexedVertexDecl1[5]:= D3DVSD_END;

// ...

// загрузка и ассемблирование кода шейдера из внешнего текстового файла
ShaderFileName := 'Media\Shaders\skinmesh'+ IntToStr(i+1)+'.vsh';
Result:= D3DXAssembleShaderFromFile(PChar(ShaderFileName), 0, nil, @pCode, nil);
if FAILED(Result) then Exit;

{...}

end;

   
       
 

function CMyD3DApplication.InvalidateDeviceObjects;

begin
{...}

// осуществляется освобождение зависимых от графического устройства мешей
pdeCurr:= m_pdeHead;
while (pdeCurr <> nil) do
begin
ReleaseDeviceDependentMeshes(pdeCurr.pframeRoot);
pdeCurr:= pdeCurr.pdeNext;
end;


{...}
end;

   
       
 

function CMyD3DApplication.DeleteDeviceObjects;

begin
{...}

// удаление из памяти
if (m_pdeSelected = m_pdeHead) then m_pdeSelected:= nil;

FreeAndNil(m_pdeHead);


{...}
end;

   
       
 

В приложении SkinnedMesh анимация может воспроизводиться 4-мя способами (см. метод function CMyD3DApplication.MsgProc):

ID_OPTIONS_D3DNONINDEXED
ID_OPTIONS_D3DINDEXED
ID_OPTIONS_SOFTWARESKINNING
ID_OPTIONS_D3DINDEXEDVS

Значения данных констант обозначающих идентификаторы пунктов главного меню находятся в файле Resource.pas

   
       
  От метода анимации, устанавливаемого в поле m_method, будет зависеть как реализуется анимация внутри метода Render:    
       
 

function CMyD3DApplication.Render;

begin
// Set up viewing postion from ArcBall
pdeCur := m_pdeHead;
while (pdeCur <> nil) do
begin

pdeCur.pframeRoot.matRot:= m_ArcBall.GetRotationMatrix^;
//здесь мы видим, что матрицы ориентации и смещения
pdeCur.pframeRoot.matTrans:= m_ArcBall.GetTranslationMatrix^;
//зависят от положения ArcBall и устанавливаются

//для корневого объекта кадра (pframeRoot) у текущего элемента отрисовки (DrawElement - pdeCur)


pdeCur:= pdeCur.pdeNext;
//в цикле перебираются все элементы отрисовки начиная с корневого (m_pdeHead)
end;

// Clear the viewport
m_pd3dDevice.Clear(0, nil, D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER,
$FF000000, 1.0, 0 );

if (m_pdeHead = nil) then
begin
Result:= S_OK;
Exit;
end;

// Begin the scene
if SUCCEEDED(m_pd3dDevice.BeginScene) then
begin
cTriangles := 0;
D3DXMatrixTranslation(m_mView, 0, 0, -m_pdeSelected.fRadius * 2.8);
//данная строка определяет положение модели в окне Viewport. Если менять значение 2.8 в сторону уменьшения, то модель приближается и наоборот

Result:= m_pd3dDevice.SetTransform(D3DTS_VIEW, m_mView); //полученная трансформация устанавливается в качестве трансформации вида (View)
if FAILED(Result) then Exit;

pdeCur:= m_pdeHead;
while (pdeCur <> nil) do
begin
D3DXMatrixIdentity(mCur);
//поскольку наша анимированная модель движется на месте, то данная строка кода просто устанавливает матрицу идентичности

Result:= UpdateFrames(pdeCur.pframeRoot, mCur); // здесь устанавливается эта матрица для всех элементов отрисовки
if FAILED(Result) then Exit;
Result:= DrawFrames(pdeCur.pframeRoot, cTriangles);
// фактическая отрисовка, с учетом метода m_method
if FAILED(Result) then Exit;

pdeCur:= pdeCur.pdeNext;
end;

{...}

// End the scene.
m_pd3dDevice.EndScene;
end;

Result:= S_OK;
end;

   
       
  В первом приближении всё вроде бы и не так сложно. В методе FrameMove происходит просто приращение текущего времени для всех элементов отрисовки начиная с корневого.    
       
  function CMyD3DApplication.FrameMove;
var
pdeCur: SDrawElement;
pframeCur: SFrame;


begin
pdeCur:= m_pdeHead;
while (pdeCur <> nil) do
begin
pdeCur.fCurTime:= pdeCur.fCurTime + m_fElapsedTime * 4800;
if (pdeCur.fCurTime > 1.0e15) then pdeCur.fCurTime:= 0;

pframeCur:= pdeCur.pframeAnimHead;
while (pframeCur <> nil) do
begin
pframeCur.SetTime(pdeCur.fCurTime);
pframeCur:= pframeCur.pframeAnimNext;
end;

pdeCur:= pdeCur.pdeNext;
end;

Result:= S_OK;
end;

   
       
 

Теперь нам нужен ответ на вопросы:

1) Каким образом внедрить в код примера другие модели (например, земную поверхность)?

2) Как использовать в коде примера камеру ?

3) И наконец, как перемещать и вращать анимированную модель в 3D-сцене, например чтобы анимированный персонаж перемещался по земной поверхности ?

   
       
 

Ответом на последний вопрос является строка кода (которая была приведена выше):

D3DXMatrixIdentity(mCur); //поскольку наша анимированная модель движется на месте, то данная строка кода просто устанавливает матрицу идентичности

   
       
  Если взять за основу код из примера игры Donuts3D для земной поверхности с небольшими изменениями, то это можно легко воплотить. В коде данного примера это уже сделано.    
       
 

Перейдем к главному.

Т.к. в модифицированном примеме SkinnedMesh анимированная модель использует свои матрицы Мировую, Видовую и Проекционную и они используются примером глобально, то приходится для модели земной поверхности создать свои матрицы и использовать их.

   
       
 

В код метода function CMyD3DApplication.Render; между строками :

BeginScene и EndScene добавим

//m_pd3dDevice.SetTransform(D3DTS_WORLD, PlanetMeshWorldMatrix);
//m_pd3dDevice.SetTransform(D3DTS_VIEW, PlanetMeshViewMatrix);
//m_pd3dDevice.SetTransform(D3DTS_PROJECTION, PlanetMeshProjMatrix);
PlanetMesh.Render( m_pd3dDevice );

   
       
  Откомпилируем и запустим. Как видим модель земной поверхности ориентирована и перемещается вместе с анимированной моделью. Это и понятно - она использует те же матрицы, что и анимированная модель.    
       
  Теперь раскомментировав сначала первую строку и снова откомпилировав и запустив приложение мы увидим очевидный результат. Модель земной поверхности стала располагаться самостоятельно. Раскомментировав вторую строчку мы получим положение модели ориентированное к виду, а вот раскомментировав третью строку мы увидим, что анимированная модель исчезает из виду.    
       
 

Что же делать?

Это связано с тем, что в примере SkinnedMesh установка Проекционной матрицы происходит внутри шейдера. Другими словами трансформация вершин Проекционной матрицей производится в коде шейдера, а не строкой m_pd3dDevice.SetTransform(D3DTS_PROJECTION, ...);

Как изменить код?

   
       
  Сначала при помощи автоматизированного поиска найдем в коде все места, где производится установка Проекционной матрицы. Нужно выполнять поиск с начала модуля (Ctrl+Home) по фразе m_pd3dDevice.SetTransform(D3DTS_PROJECTION    
       
  Обнаруживаем, что это имеет место только внутри function CMyD3DApplication.SetProjectionMatrix;    
       
  Подобным же способом ищем откуда вызывается данная функция - только внутри LoadMeshHierarchy, а данный метод вызывается только из InitDeviceObjects, т.е. только на стадии загрузки моделей.    
       
  Отсюда получается, что Проекционная матрица вообще не устанавливается приложением на стадии отрисовки командой m_pd3dDevice.SetTransform(D3DTS_PROJECTION, ...);    
       
  Это происходит по другому (см. уже упоминавшийся метод SetProjectionMatrix);    
       
  Итак, получается, что в качестве проекционной матрицы в приложении используется 2-ая константа АЛУ Обработки вершинного шейдера (Vertex Shader Pipeline ALU).    
       
 

Поиск по слову SetVertexShaderConstant(2 не приведет Вас ни к чему новому. Это потому что код использования Проекционной матрицы вынесен в шейдерный!

Чтобы убедиться в этом загляните внутрь любого из файлов в папке Media\Shaders из той папки куда Вы распаковали архив данного примера.

   
       
 

Следовательно вывод модели земной поверхности должен происходить с учетом этих фактов.

Возникает резонный вопрос: А как происходит вывод в случае анимированной модели?

   
       
  Теперь нам хочешь не хочешь, а придется "залезть" в код метода Result:= DrawFrames(pdeCur.pframeRoot, cTriangles);    
       
 

Опорными точками для понимания сути данного метода могут служить строки

m_pd3dDevice.SetStreamSource(0, m_pVB, sizeof(CUSTOMVERTEX));
m_pd3dDevice.SetVertexShader(m_dwShader);
m_pd3dDevice.SetIndices(m_pIB, 0);
m_pd3dDevice.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,
m_dwNumVertices, 0, m_dwNumIndices);

взятые из метода отрисовки Render примера Подлодка и Подводный мир.

Нечто подобное надо искать и внутри метода DrawFrames

Тогда станет ясно где и как это происходит.

   
       
  [далее - разберем подробнее код внутри DrawFrames]    
       
 

Обновления и новости о развитии Delphi DirectX проекта
смотри на сайтах:

http://www.megainformaticsite.pochta.ru

http://www.megainformatic.boom.ru

http://www.megainformatic.narod.ru

 

   
       
     
 

по всем вопросам пишите на megainformatic@mail.ru или оставьте сообщение на форуме

 
     
   Обмен ссылками  
     
     
             
 
 
         
(с) МЕГА ИНФОРМАТИК 2006-2009
Hosted by uCoz