- •Учебник
- •Оглавление
- •Введение
- •Глава 1
- •I. Установка
- •II. Удаление
- •Глава 2. Первый проект
- •Глава 3. Создание сцены в режиме design time
- •Создание объектов:
- •Глава 4. Примитивные объекты. Вкладка Basic geometry.
- •Глава 5. Ориентация, координаты.
- •Глава 6. Движение.
- •Глава 7. Как узнать, сколько сейчас fps?
- •Глава 8. Полноэкранный режим.
- •Глава 9. Работа с материалами.
- •I. Прямое обращение к .Material
- •Глава 10. Использование 3d моделей.
- •I. Форматы моделей
- •Глава 11. Прокси объекты.
- •Глава 12. Создание земной поверхности с glTerrainRenderer.
- •Глава 13. Создание земной поверхности с glFreeForm.
- •Глава 14. Итоги работы с glTerainrender и glFreeForm.
- •Глава 15. Создание земной поверхности с glHeightField.
- •Глава 15. Создание неба.
- •Глава 16. GlAtmosphere.
- •Глава 17. Спецэффекты.
- •I. Огонь
- •II. Молния
- •III. Дождь
- •IV. Снег
- •VI. Блики от источников света
- •Глава 18. Растительность.
- •I. Деревья и кустарники
- •II. Трава
- •Глава 19. Работа со шрифтами и вывод надписей.
- •I. Вывод текста через компонент glWindowsBitmapFont
- •II. Проблема с glWindowsBitmapFont в Windows Vista
- •Глава 20. Выделение объекта мышкой.
- •Глава 21. Создание сцены в режиме runtime.
- •Глава 22. Тени.
- •Глава 23. Туман.
- •Глава 24. Vbo или расширение arb_vertex_buffer_object в OpenGl.
- •Глава 25. Fbo или расширение ext_framebuffer_object в OpenGl.
- •Глава 26. Рисование на канве.
- •Глава 27. Использование чистого OpenGl.
- •Глава 28. Звуки.
- •I. Проигрывание звуков с помощью bass
- •II. Проигрывание звуков с помощью fmod
- •III. Проигрывание звуков с помощью OpenAl
- •Глава 29. Игровое меню.
- •Глава 30. Примитивная физика dce.
- •Глава 31. Физика ode.
- •II. Основы библиотеки ode(: tglodeManager и tglodeJointList) на примере создания подобия боулинга.
- •Глава 32. Физика Newton.
- •Глава 33. Ручная проверка коллизий.
- •V. Ручная проверка коллизии
- •Глава 34. Шейдеры. Терменология.
- •Глава 35. Шейдеры. История и компоненты.
- •Глава 36. Шейдеры glsl. Введение.
- •Глава 37. Шейдеры glsl. Использование без компонентов. Самый примитивный шейдер.
- •Глава 38. Шейдеры glsl. Немного о спецификации. Типы данных и переменные
- •Стандартные функции
- •Поддерживаемые компоненты векторов
- •Глава 40. Шейдеры glsl. Текстурирование.
- •Глава 41. Шейдеры glsl. Использование без компонентов. Мультитекстурирование. Смешение трёх цветов по базовой карте.
- •Глава 42. Шейдеры glsl. Использование через компонент glslShader. Часть первая – Первые шаги.
- •Глава 43. Шейдеры glsl. Использование через компонент glslShader. Часть вторая – знакомимся со светом. Передача параметров в шейдер.
- •Глава 44. Шейдеры glsl. Использование через компонент glslShader. Часть третья – текстуры.
- •Глава 45. RenderMonkey.
- •Глава 46. Оптимизация программы.
- •Глава 47. Практика. Создание мира
- •Приложение
- •I. Типы векторов и матриц
- •II. Работа с векторами
- •III. Работа с матрицами
- •IV. Методы объектов glScene
- •Предметный указатель по компонентам glScene
Глава 47. Практика. Создание мира
Здесь описывается создание земли, по земле ходит актёр с камерой от первого и третьего лица, вокруг растут деревья, а под деревьями грибы. Ну и ещё сделаем воду.
Начали. Поместим на форму:
GLScene1, GLSceneViewer1, GLCadencer1, GLNavigator1, GLUserInterface1, GLMaterialLibrary1, GLBitmapHDS1, Timer1.
Пользуясь рисунком, создадим необходимые компоненты.
Имя
Свойство
Значение
GLSceneViewer1
Camera
GLCamera2
Align
alClient
GLNavigator1
MovingObject
DummyCube2
UseVirtualUp
True
VirtualUp
X=0, Y=0,
Z=1
GLUserInterface1
GLNavigator
GLNavigator1
MouseSpeed
20
GLCadencer1
Scene
GLScene1
GLWaterPlane1
Position
X=-25, Y=-100,
Z=-40
Scale
X=2000,
Y=2000, Z=2000
Elastic
5
RainForce
80
RainTimeInterval
1
Up
X=0, Y=0,
Z=1
GLLightSource1
Position
X=50, Y=50,
Z=50
FreeForm1
Position
X=10, Y=10,
Z=5
Scale
X=5, Y=5,
Z=5
Up
X=0, Y=1,
Z=0
DummyCube2
Up
X=0, Y=0,
Z=1
GLCamera1
DepthOfView
2000
Up
X=0, Y=1,
Z=0
|
Position |
X=0, Y=3, Z=0 |
GLCamera2 |
DepthOfView |
2000 |
|
Position |
X=0, Y=2, Z=-5 |
|
Up |
X=0, Y=0, Z=1 |
|
TargetObject |
DummyCube2 |
Actor1 |
Up |
X=1, Y=0, Z=0 |
|
Material |
Texture-убрать галочку с Disable |
Actor2 |
Material |
Texture-убрать галочку с Disable |
GLTerrainRenderer1 |
HeightDataSource |
GLBitmapHDS1 |
|
Material |
Texture-убрать галочку с Disable |
|
Scale |
X=10, Y=10, Z=0,5 |
|
Up |
X=0, Y=1, Z=0 |
FreeForm2 |
Scale |
X= 0,01, Y= 0,01, Z= 0,01 |
|
Up |
X=0, Y=1, Z=0 |
Кое что поясню по поводу создания воды.
Рекомендую поэкспериментировать с цветом у GLWaterPlane1. Зайдите в свойство Material у GLWaterPlane1 поставьте BlendingMode в bmTransparency чтобы изменения цвета отображались на воде. Для придания реалистичности сделайте воду прозрачной, для этого щёлкните на Front, и на вкладке Diffuse изменяйте свойство Alpha. С помощью свойства Elastic меняется эластичность воды, чем больше, тем менее эластична. Свойство RainTimeInterval регулирует, с какой частотой появляются капли на воде. Свойство RainForce регулирует силу волны. Замечание: при изменении какого-либо параметра изменения отображаются на воде только после второй компиляции(по крайней мере у меня так).
Ну что ж начнём кодить. Сначала подключим необходимые модули.
uses jpeg, tga, GLkeyboard, VectorGeometry,GLFile3DS, GLFilemd2;
Далее объявим переменные:
public
interpolated_height : single;
GLTree : TGLTree; // наше дерево
proxy, proxy2 : TGLProxyObject; // прокси объекты дерева и гриба
end;
Следом загружаем всё необходимое, в OnCreate для формы пишем
procedure TForm1.FormCreate(Sender: TObject);
begin
// загружаем модель гриба
FreeForm2.LoadFromFile('media\mushroom.3ds');
// загружаем карту высот для нашей земли
GLBitmapHDS1.Picture.LoadFromFile('media\terrain.bmp');
// загружаем текстуру на землю
GLTerrainRenderer1.Material.Texture.Image.LoadFromFile('media\clover.jpg');
// модель актёра
Actor1.LoadFromFile('media\waste.md2');
// его текстура
Actor1.Material.Texture.Image.LoadFromFile('media\waste.jpg');
// его анимация
Actor1.Animations.LoadFromFile('media\Quake2Animations.aaf');
// делаем его поменьше
Actor1.Scale.SetVector(0.04, 0.04, 0.04, 0);
// грузим нашему актёру пушку
Actor2.LoadFromFile('media\WeaponWaste.md2');
// и текстуру к ней
Actor2.Material.Texture.Image.LoadFromFile('media\WeaponWaste.jpg');
// анимация проигрывается циклически
Actor1.AnimationMode:=aamLoop;
// проигрывать анимацию стоять на месте
Actor1.SwitchToAnimation('stand');
// активируем управление мышкой
GLUserInterface1.MouseLookActivate;
// далее добавляем текстуру для нашего дерева
with GLMaterialLibrary1.AddTextureMaterial('LeafFront','media\maple_multi.tga') do begin
Material.BlendingMode:=bmAlphaTest50;
Material.Texture.TextureMode:=tmModulate; Material.Texture.TextureFormat:=tfRGBA;
end;
with GLMaterialLibrary1.AddTextureMaterial('LeafBack','media\maple_multi.tga') do begin
Material.BlendingMode:=bmAlphaTest50;
Material.Texture.TextureMode:=tmModulate; Material.Texture.TextureFormat:=tfRGBA;
end;
with GLMaterialLibrary1.AddTextureMaterial('Branch','media\zbark_016.jpg') do begin
Material.Texture.TextureMode:=tmModulate;
end;
// добавляем прокси-деревья
AddTree;
// добавляем дерево
NewTree;
// добавляем грибы
AddMuroms; End;
Пора добавить дерево на сцену, для этого создадим процедуру NewTree:
procedure TForm1.NewTree;
begin
// добавляем во FreeForm1 дерево GLTree:=TGLTree(FreeForm1.AddNewChild(TGLTree)); with GLTree do
begin
// присваиваем библиотеку материалов и берём от туда текстуры MaterialLibrary:=GLMaterialLibrary1; LeafMaterialName:='LeafFront'; LeafBackMaterialName:='LeafBack'; BranchMaterialName:='Branch';
// размеры
Depth:=8;
// регулирует насколько дерево заросло листьями
LeafSize:=0.2;
// толщина ствола
BranchRadius:=0.08; BranchNoise:=0.5; Randomize;
Seed:=Round((2*Random-1)*(MaxInt-1));
end;
end;
Но что нам одно дерево, наделаем ему двойников, и оп ять нужно писать новую процедуру
AddTree
procedure TForm1.AddTree;
var i : Integer;
s : TVector;
f : Single;
begin
// создаём 50 клонов одного дерева которое содержится у нас во FreeForm1
for i:=0 to 50 do begin
//Создаем очередной прокси-объект
proxy:=TGLProxyObject(DummyCube1.AddNewChild(TGLProxyObject));
with proxy do begin
// наследуем только структуру объекта
ProxyOptions:=[pooObjects];
//В свойство MasterObject записываем наше дерево образец
MasterObject:=FreeForm1;
// ориентация в пространстве такая же как и у FreeForm1
Direction:=FreeForm1.Direction; Up:=FreeForm1.Up;
// делаем разной величины s:=FreeForm1.Scale.AsVector; f:=(1*Random+1); ScaleVector(s, f); Scale.AsVector:=s;
// позиция по X и Y выбирается случайно для каждого созданного //прокси-объекта
Position.SetPoint(random(990), random(990), interpolated_height);
// случайно поворачиваем на какой-то угол RollAngle:=Random(360); TransformationChanged;
// объясню дальше
with proxy.Position do begin interpolated_height:=GLTerrainRenderer1.InterpolatedHeight(AsVector);
if Z<>interpolated_height then
Z:=interpolated_height; FreeForm1.Position:=proxy.Position; end;
end;
end;
end;
with proxy.Position do begin
interpolated_height:=GLTerrainRenderer1.InterpolatedHeight(AsVector);
if Z<>interpolated_height then
Z:=interpolated_height;
end;
Этот блок нужен для того что бы деревья располагались не абы как а точно по склонам созданной нами земли. Объясняю как он работает, создаётся очередной прокси объект, X и Y выбираются случайно, а Z присваивается значение interpolated_height которое в свою очередь считается так. Сначала определяем высоту земли в точке в которую ставим дерево, потом смотрим если Z больше или меньше этой высоты то присваиваем Z значение высоты. А строчка FreeForm1.Position:=proxy.Position; нужна чтобы наше первое дерево не висело в воздухе.
Всё деревья растут точно по склонам надо теперь чтобы и грибы росли также эта процедура аналогична предыдущей(разве что грибов 100) так что объяснять я не буду:
procedure TForm1.AddMuroms;
var i : Integer; s : TVector; f : Single;
begin
for i:=0 to 100 do begin proxy2:=TGLProxyObject(DummyCube3.AddNewChild(TGLProxyObject));
with proxy2 do
begin ProxyOptions:=[pooObjects]; MasterObject:=FreeForm2;
Direction:=FreeForm2.Direction; Up:=FreeForm2.Up;
s:=FreeForm2.Scale.AsVector; f:=(1*Random+1); ScaleVector(s, f); Scale.AsVector:=s;
Position.SetPoint(random(1000)-(1000/5), random(1000)-(1000/5), interpolated_height);
RollAngle:=Random(360); TransformationChanged;
with proxy2.Position do begin interpolated_height:=GLTerrainRenderer1.InterpolatedHeight(AsVector);
if Z<>interpolated_height+0.2 then Z:=interpolated_height+0.2; FreeForm2.Position:=proxy2.Position; end;
end;
end;
end;
Здесь лишь отмечу следующее
Position.SetPoint(random(1000)-(1000/5), random(1000)-(1000/5), interpolated_height);
Такой способ расположения объектов отличается от предыдущего тем что он раскидывает грибы по кругу, а не в одну сторону как это сделано с деревьями, т.к деревья не очень смотрятся в воде.
Ну вот и готово, растительность расставлена по местам осталось научить ходить нашего героя для этого у GLCadencer1 в OnProgress запишем следующее:
procedure TForm1.GLCadencer1Progress(Sender: TObject; const deltaTime, newTime: Double);
var
speed : Single;
moving: string;
begin
// проигрывается анимация стоять на месте
moving:='stand';
// при нажатии на шифт интервал с которой проигрывается анимация //=100 т.е играется быстрей
if IsKeyDown(VK_SHIFT) then begin
Actor1.Interval:=100;
// бежим быстрей
speed:=50*deltaTime
// а иначе анимация замедляется
end else begin Actor1.Interval:=150;
// Бежим медленнее
speed:=10*deltaTime;
end;
// при нажатии на Q переключаем камеру2 на камеру1
if IsKeyDown('q') then begin GLSceneViewer1.Camera:=GLCamera1; end;
// при нажатии на E переключаем камеру1 на камеру2
if IsKeyDown('E') then begin GLSceneViewer1.Camera:=GLCamera2; end;
// при нажатии на W бежим вперёд
if IsKeyDown('w') then begin GLNavigator1.MoveForward(speed);
// играется анимация бежать
moving:='run';
end;
// при нажатии на S бежим назад
if IsKeyDown('s') then begin
GLNavigator1.MoveForward(-speed);
// анимация бежать moving:='run'; end;
// при нажатии на A передвинуться влево
if IsKeyDown('a') then begin
GLNavigator1.StrafeHorizontal(-speed);
moving:='run';
end;
// при нажатии на D бежим вправо
if IsKeyDown('d') then begin GLNavigator1.StrafeHorizontal(speed); moving:='run';
end;
// при нажатии на эскейп завершаем программу
if IsKeyDown(VK_ESCAPE) then Close;
// пояснения далее
with DummyCube2.Position do begin interpolated_height:=GLTerrainRenderer1.InterpolatedHeight(AsVector);
if Z<>interpolated_height then
Z:=interpolated_height+1;
end;
if Actor1.CurrentAnimation<>moving then
// играть анимацию которая находиться в Moving
Actor1.SwitchToAnimation(moving);
// синхронизируемся с первым актёром
Actor2.Synchronize(Actor1); GLUserInterface1.MouseUpdate; GLUserInterface1.MouseLook; end;
Этот блок
with DummyCube2.Position do begin interpolated_height:=GLTerrainRenderer1.InterpolatedHeight(AsVector);
if Z<>interpolated_height then
Z:=interpolated_height+1;
end;
служит для того чтобы наш герой не ломился сквозь холмы, а честно ходил по ним. Работает это так же как и в случае с proxy за одним лишь исключением что здесь мы прибавляем 1 если позиция по Z думмикуба <> interpolated_height . Делается это для того чтобы наш герой не стоял по пояс в земле. Добавим ещё кое что, а именно регулирование расстояния до актёра в виде от третьего лица колесиком мыши. Пишем в FormMouseWheel у Form1:
procedure TForm1.FormMouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
GLCamera2.AdjustDistanceToTarget(Power(1.1, WheelDelta/120));
end;
Ну и последний штрих в виде подсчёта фпс. В OnTimer у Timer1 пишем:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Caption:=Format('%.2f FPS', [GLSceneViewer1.FramesPerSecond]); GLSceneViewer1.ResetPerformanceMonitor;
end;
Теперь у нас в заголовке формы будет показываться сколько сейчас фпс.