Трехмерная графика сложна для построения многих моделей вручную. Многие модели состоят из большого числа граней, и создать их вручную практически не возможно. Поэтому для большого числа моделей используются алгоритмы, генерирующие такие модели.
Например, трехмерная поверхность земли может состоять из нескольких сотен и тысяч граней.
Для получения такой поверхности можно использовать алгоритм срединного смещения, который описывает фрактал Плазма. Данный алгоритм позволяет создавать такие изображения:
А уже по этим изображения можно создать трехмерную проекцию поверхности. Черный цвет определяет самую высокую точку. Белый – самую низкую, или наоборот. Данное изображение состоит из оттенков серого и поэтому все высоты находятся в промежутке (0;255)
Наша цель написать программу, которая генерировала бы такие изображения и по ним создавать трехмерные поверхности в формате VRML. Мы будем использовать этот формат трехмерных файлов, так как его очень легко использовать в программировании.
Программировать будем в системе Lazarus – она бесплатная и основана на языке Паскаль, это позволяет очень легко ее освоить и так же для Lazarus существует трехмерный игровой движок на VRML, что позволяет напрямую работать с этими моделями в Lazarus.
Первое что должна делать эта программа – это генерировать изображения плазмы. Алгоритм состоит в том, что мы берем квадрат и случайным образом генерируем высоты в углах. Высота в данном случае это цвет пикселя в углу квадрата. Дале будем вычислят значение высот на сторонах, эти высоты будут равны полу сумме высот на концах стороны. Значение высоты в центре будет равно среднеарифметическому значению в углах квадрата и плюс некоторое смещение, которое будет зависеть от размера квадрата.
Теперь приступим к созданию самой программы. Создадим проект в Lazarus и разместим на нем панель, на которой и будем строить фрактал плазма. На рисунке панель имеет синий цвет. Дизайн программы можно выбирать по желанию, большой роли в программе он не играет.
И создадим кнопку для случайной генерации. И добавим поле Edit , в котором мы будем задавать размер стороны квадрата.
Для генерации будем использовать рекурсивную функцию, которая будет выполнять сама себя, уменьшая квадрат до минимума.
Алгоритм получения плазмы найти в интернете несложно, поэтому не будем изобретать велосипед и возьмем готовый код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForm1 = class(TForm) Button1: TButton; Image1: TImage; procedure Button1Click(Sender: TObject); procedure makeplasma; private { Private declarations } public { Public declarations } end; var Form1: TForm1; plasma : array[0..768,0..768] of byte; implementation procedure TForm1.SpeedButton1Click(Sender: TObject); var x, y: integer; begin makeplasma; for x := 0 to 255 do begin for y := 0 to 255 do begin Form1.Panel1.Canvas.Pixels[x, y] :=plasma[x, y]; end; Form1.update; end; end; procedure adjust(xa,ya,x,y,xb,yb: integer); var d: integer; v: double; begin if plasma[x,y]<>0 then exit; d:=Abs(xa-xb)+Abs(ya-yb); v:=(plasma[xa,ya]+plasma[xb,yb])/2+(random-0.5)*d*2; if v<1 then v:=1; if v>=193 then v:=192; plasma[x,y]:=Trunc(v); end; procedure halfway(x1,y1,x2,y2: integer); var x, y: integer; v: double; begin if (x2-x1<2) and (y2-y1<2) then exit; x:=(x1+x2) div 2; y:=(y1+y2) div 2; adjust(x1,y1,x,y1,x2,y1); adjust(x2,y1,x2,y,x2,y2); adjust(x1,y2,x,y2,x2,y2); adjust(x1,y1,x1,y,x1,y2); if plasma[x,y]=0 then begin v:=(plasma[x1,y1]+plasma[x2,y1]+plasma[x2,y2]+plasma[x1,y2])/4; plasma[x,y]:=Trunc(v); end; halfway(x1,y1,x,y); halfway(x,y1,x2,y); halfway(x,y,x2,y2); halfway(x1,y,x,y2); end; procedure TForm1.makeplasma; var x:integer; begin x:=strtoint(edit1.Text); randomize; plasma[0,x]:=random(192); plasma[x,x]:=random(192); plasma[x,0]:=random(192); plasma[0,0]:=random(192); halfway(0,0,x,x); end; |
Вот что получилось:
Копка очистить нужна для очистки канвы для генерации других поверхностей. Код очень простой:
1 |
Panel1.Canvas.Clear; |
И теперь необходимо сгенерировать трехмерную модель. Модель будем создавать в формате VRML, так как это просто текстовый файл. Этот файл мы будем записывать в memo1.
Для того чтобы повысить скорость генерации моделей Memo1 необходимо скрыть
Кодом
1 |
memo1.Hide; |
Вот функция генерации моделей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
procedure TForm1.SpeedButton3Click(Sender: TObject); var i,j,n,m,b:integer; c,v,r:integer; p:string; k:real; begin b:=0; n:=250; m:=250; memo1.Lines.Append('#VRML V2.0 utf8'); memo1.Lines.Append(' '); memo1.Lines.Append('Transform {'); memo1.Lines.Append('scale 20 1 20'); memo1.Lines.Append('children ['); memo1.Lines.Append('Shape{ appearance Appearance { material Material {}}'); memo1.Lines.Append('geometry ElevationGrid{'); memo1.Lines.Append('height ['); for i:=1 to n do for j:=1 to m do begin b:=b+1; label6.Caption:=inttostr(b); edit3.Text:=inttostr(GetRValue(panel1.Canvas.Pixels[i,j])); c:=strtoint(edit3.Text); if radiobutton2.Checked then k:=(255-c); if radiobutton1.Checked then k:=c; if (j=m) then begin v:=(GetRValue(panel1.Canvas.Pixels[i-1,j-1])); if radiobutton2.Checked then r:=(255-v); if radiobutton1.Checked then r:=v; memo1.Lines.Append(floattostr(r)); end else memo1.Lines.Append(floattostr(k)); end; memo1.Lines.Append(']'); memo1.Lines.Append('xDimension '+inttostr(n)); memo1.Lines.Append('zDimension '+inttostr(m)); memo1.Lines.Append('xSpacing 0.3'); memo1.Lines.Append('zSpacing 0.3'); memo1.Lines.Append('}'); memo1.Lines.Append('}'); memo1.Lines.Append(' '); memo1.Lines.Append(']'); memo1.Lines.Append('}'); memo1.Lines.SaveToFile(edit2.text+'.wrl'); end; |
Об авторе