Программирование 3D-игр в DirectX на Delphi
 
         
    [Главная] [Новости] [Статьи] [Игры] [Проекты] [Автор]  
         
  Основы 3D-программирования DirectX8.1 в Delphi 6-7: теоретические и практические основы создания игр.  
     
  [Все уроки]  
     
  Конечно, данная статья не является заменой DirectX SDK. В первую очередь нужно обращаться к нему, если Вы все-таки решили заняться созданием 3D-игр под DirectX. Однако цель данной статьи - помочь Вам сделать первый шаг в освоении программирования под DirectX8.1 на Delphi 6-7. Поэтому здесь Вы не найдете исчерпывающих объяснений и описаний. Только подспорье в освоении DirectX'а!!!  
     
  Итак, что же такое шейдеры?    
     
  Применительно к DirectX 8.1 - это небольшие куски кода (написанные на особом, шейдерном языке) в виде ascii-текста в обычном текстовом файле, но с расширением *.vsh или *.psh. Они позволяют видоизменить способ по-пиксельной и по-вершинной обработки информации участвующей в потоке рендеринга (отрисовки). Пиксельные шэйдеры (pixel shader) могут включать максимально до 12 команд; вершинные (vertex shader)- до 128 команд. Описание команд и их использования приводится в DirectX8.1 SDK.  
     
  В нашем уроке файлы шейдеров в папке media\shaders из архива, который Вы, надеюсь, уже скачали.  
     
  Следует отметить, что начинать изучение шейдеров лучше с пиксельных (pixel shaders). Они меньше по размеру и несколько проще для понимания. Однако всю мощь и силу шейдеров Вы обретете изучив и вершинные шейдеры.  
     
  Можно ли обойтись вообще без шейдеров? На первых этапах да, но когда Вы разберетесь с ними, то, наверное, уже и не сможете от них отказаться. Многие эффекты в играх реализованы именно при помощи шейдеров. Это не что-то сверхъестественное. Просто DirectX 8.1 предоставляет Вам большую свободу в выборе средств реализации эффектов в играх, поэтому пригодятся Вам и знания шэйдеров. К тому же, именно по такому принципу - принципу перевода ascii-текста в байт-коды - осуществляется, надо полагать, и скриптование в реализованных игровых движках.  
     
 

Вот необходимые шаги для использования шейдера:

в классе приложения объявить поля соответствующие обработчику шейдера -

m_dwSubmarineVertexShader: Cardinal;

в методе CSubmarineD3DApp.Create обнулить данный обработчик

m_dwSubmarineVertexShader := 0;

в методе CSubmarineD3DApp.RestoreDeviceObjects смотри содержимое метода RestoreRenderCausticsStates;

т.е. ввести описания вершин

var
dwSubmarineVertexDecl: array[0..4] of Cardinal;

begin

{...}

// Create vertex shader for the submarine
dwSubmarineVertexDecl[0] := D3DVSD_STREAM(0);
dwSubmarineVertexDecl[1] := D3DVSD_REG(0, D3DVSDT_FLOAT3); // Position of first mesh
dwSubmarineVertexDecl[2] := D3DVSD_REG(3, D3DVSDT_FLOAT3); // Normal
dwSubmarineVertexDecl[3] := D3DVSD_REG(6, D3DVSDT_FLOAT2); // Tex coords
dwSubmarineVertexDecl[4] := D3DVSD_END;

{...}

// и загрузить код шейдера из файла

hr := D3DUtil_CreateVertexShader(m_pd3dDevice, 'media\shaders\submarine.vsh',
@dwSubmarineVertexDecl, m_dwSubmarineVertexShader);

end;

 

 
   
 

в методе CSubmarineD3DApp.FrameMove смотри метод AnimateCausticTex;

там в константные регистры АЛУ вершинного шейдера загружаются необходимые данные в таком виде

m_pd3dDevice.SetVertexShaderConstant( 0, vZero, 1);

{...}

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

 
     
 

И наконец в методе CSubmarineD3DApp.RenderCaustic происходит установка и использование шейдеров для обработки вершин в таком виде:

// Render the submarine

m_matWorldOfMesh := m_matSubmarine;
SetMatWorldOfMesh;
m_pd3dDevice.SetTexture(0, IDirect3DTexture8(m_pSubmarine.m_pTextures^[0]));
m_pd3dDevice.SetVertexShader(m_dwSubmarineVertexShader);
m_pd3dDevice.SetStreamSource(0, m_pSubmarineVB, sizeof(TVertex));
m_pd3dDevice.SetIndices(m_pSubmarineIB, 0);
m_pd3dDevice.DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
0, m_dwNumSubmarineVertices, 0, m_dwNumSubmarineFaces);

т.е. вершины моделей должны быть предварительно скопированы в вершинный буфер, для того, чтобы их можно было обрабатывать шейдером. Следовательно, предварительно требуется еще и использование вершинных и индексных буферов, в нашем случае это m_pSubmarineVB, m_pSubmarineIB. Как они создаются и используются - смотрите в исходном коде данного примера, который Вы скачали.

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

применив комментирующие скобки {} закомментируйте следующий блок кода:

// Render the submarine

{

m_matWorldOfMesh := m_matSubmarine;
SetMatWorldOfMesh;
m_pd3dDevice.SetTexture(0, IDirect3DTexture8(m_pSubmarine.m_pTextures^[0]));
m_pd3dDevice.SetVertexShader(m_dwSubmarineVertexShader);
m_pd3dDevice.SetStreamSource(0, m_pSubmarineVB, sizeof(TVertex));
m_pd3dDevice.SetIndices(m_pSubmarineIB, 0);
m_pd3dDevice.DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
0, m_dwNumSubmarineVertices, 0, m_dwNumSubmarineFaces);

}

 
     
  Откомпилируйте и запустите приложение. Теперь плавает не сама подлодка, а ее бликовая копия!!  
     
  То же самое можно проделать с другими участками кода, выяснив, где отрисовываются блики, а где сама модель.  
     
  Еще одно интересное упражнение. Обратите внимание, для отрисовки статичного дна используется один шейдер m_dwSeaFloorVertexShader3, а для подлодки - m_dwSubmarineVertexShader.  
     
 

Попробуем поменять шейдеры местами:

найдите кусочек кода -

// Render the seafloor
m_matWorldOfMesh := m_matSeaFloor;
//SetMatWorldOfMesh;
m_pd3dDevice.SetTexture(0, IDirect3DTexture8(m_pSeaFloor.m_pTextures^[0]));
m_pd3dDevice.SetVertexShader(m_dwSeaFloorVertexShader3);

и строку m_pd3dDevice.SetVertexShader(m_dwSeaFloorVertexShader3); перепишите так:

m_pd3dDevice.SetVertexShader(m_dwSubmarineVertexShader{m_dwSeaFloorVertexShader3}); //этим мы добиваемся подмены используемого вершинного шейдера для морского дна на используемый для подлодки.

Перекомпилируем и запускаем. Ничего не произошло! Что ж, тоже результат. Делаем вывод - эти шейдеры равнозначны! Если теперь попробовать открыть код данного шейдера и намеренно внести в него ошибку, то можно понять как тестировать код шейдера на корректность. Однако мы еще не закончили наши упражнения!

 
     
 

Теперь попробуем по-другому, найдите кусочек кода:

// Render the seafloor
m_matWorldOfMesh := m_matSeaFloor;
SetMatWorldOfMesh;

и закомментируйте так

//SetMatWorldOfMesh;

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