Engine 3D

psychoszayber

<font face="Courier New" size="5">POPRAWIONA WERSJA</span>

Na początek proponuję stworzyć sobie bazkę procedur których często będziesz
używał w swoim późniejszym programie. Pozamykaj wszystkie projekty w delphi i
naciśnij z menu "Nowy Unit", zapisz go i zaczniemy pisać.

X=szerość Y=wysokość Z=odległość

2D = 2 wymiar X, Y
3D = 3 wymiar X, Y, Z
</dfn>

W uses napisz:

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls.

Na początku napisz (za uses)

type
   _3DPoint = record
   X:integer;
   Y:integer;
   Z:integer;
end;

Tworzy on rekord _3DPoint składający się z 3 wartości X, Y, Z. Czyli jest to
punkt w przestrzeni (3D).

Teraz przydałaby się nam funkcja która zwraca z trzech wartości X, Y i Z punkt _3DPoint:

function _3DP(X, Y, Z: integer): _3Dpoint;
begin
   result.X:=X;
   result.Y:=Y;
   result.Z:=Z;
end; 

Teraz procedura, która tworzy z punktu 3D punkt 2D. Potrzebne jest to dlatego, że przecież
nie narysujesz na ekranie 2D punktu 3D, skoro ekran jest płaski.

Procedure _3Dto2D(X, Y, Z: Real; ScreenWidth, ScreenHeight: Integer; Var 
TargetX, TargetY: Integer);
begin
   TargetX := (ScreenWidth div 2) + Trunc(X / Z);
   TargetY := (ScreenHeight div 2) + Trunc(Y / Z);
end;

Parametry TargetX i TargetY muszą być zmiennymi, ponieważ procedura zapisuje w
nich wartości:
TargetX = rzeczywista szerokość z punktu X,Y,Z [3D]
TargetY = rzeczywista wysokość z punktu X,Y,Z [3D]

Parametry ScreenWidth i ScreenHeight to:
ScreenHeight = wysokość powierzchni po której chcesz rysować,
ScreenWidth = szerokść powierzchni po której chcesz rysować

Muszą być uwzględnione ponieważ punkt 0,0,0 [3D] będzie się znajdował w środku
powierzchni po której chcesz rysować. Czyli:

<font face="Courier New">-x,-y | x,-y
|
-----+-----
|
-x,y | x,y </span>

  • to jest punkt 0,0,0 [3D]

Teraz procedura rysująca linie 3D w kolorze podanym jako col na powierzchni image
z puktu xyz do puntu toxyz:

Procedure _3Dline(xyz: _3Dpoint; toXYZ: _3Dpoint; col: TColor; Var image: TBitmap);
var
   nx, ny, xx, yy: integer;
begin
   _3dto2d(xyz.X, xyz.y, xyz.z, image.Width, image.Height, nX, nY);
   _3dto2d(toxyz.X, toxyz.y, toxyz.z, image.Width, image.Height, XX, YY);
   image.Canvas.Pen.Color:=col;
   image.Canvas.Brush.Color:=col;
   image.Canvas.MoveTo(nx, ny);
   image.Canvas.LineTo(XX, YY);
end;

Teraz kwadrat [3D]: jeżeli transparent:=true to jest przezroczysty, jeżeli nie, to
jest kolorowany przez kolor background i jest rysowany na powierzchni image

Procedure _3DRectange(p1, p2, p3, p4: _3Dpoint; transparent: boolean; background: TColor; col: TColor; Var image: TBitmap);
var
pt:array [1..4] of Tpoint;
begin
   _3dto2d(p1.x, p1.y, p1.y, image.Width, image.Height, pt[1].x, pt[1].y);
   _3dto2d(p2.x, p2.y, p2.y, image.Width, image.Height, pt[2].x, pt[2].y);
   _3dto2d(p3.x, p3.y, p3.y, image.Width, image.Height, pt[3].x, pt[3].y);
   _3dto2d(p4.x, p4.y, p4.y, image.Width, image.Height, pt[4].x, pt[4].y);
   image.Canvas.Pen.Color:=col;
      if not transparent then image.Canvas.Brush.Color:=background else 
   image.Canvas.Brush.Style:=bsclear;
   image.Canvas.Polygon(pt);
end;

Kostka działa podobnie do kwadratu, ale niezbyt dobrze. Punkty połączeń:

<font face="Courier New"> 5----8
|\ |
| 1--+-4
| | | |
6-+--7 |
| |
2----3</span>

Procedure _3DCube(XYZ, XYZ2, XYZ3, XYZ4, XYZ5, XYZ6, XYZ7, XYZ8: _3Dpoint; transparent:boolean; background: TColor; col: TColor; Var image: TBitmap);
begin
   _3drectange(Xyz5, Xyz6, Xyz7, Xyz8, transparent, background, col, image);
   _3drectange(Xyz6, Xyz2, Xyz3, Xyz7, transparent, background, col, image);
   _3drectange(Xyz5, Xyz6, Xyz2, Xyz, transparent, background, col, image);
   _3drectange(Xyz4, Xyz3, Xyz7, Xyz8, transparent, background, col, image);
   _3drectange(Xyz5, Xyz, Xyz4, Xyz8, transparent, background, col, image);
   _3drectange(Xyz, Xyz2, Xyz3, Xyz4, transparent, background, col, image);
end;

Cały kod unitu poniżej:

unit _3dengine;

interface

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls;

type
_3DPoint = record
   X:integer;
   Y:integer;
   Z:integer;
end;

function _3DP(X, Y, Z: integer): _3Dpoint;
procedure _3Dto2D(X, Y, Z: Real; ScreenWidth, ScreenHeight: Integer; Var TargetX, TargetY: Integer);
procedure _3Dline(xyz: _3Dpoint; toXYZ: _3Dpoint; col: TColor; Var Image: TBitmap);
procedure _3DRectange(p1, p2, p3, p4:_3Dpoint; transparent: boolean; background: TColor; col: TColor; Var image: TBitmap);
procedure _3DCube(XYZ, XYZ2, XYZ3, XYZ4, XYZ5, XYZ6, XYZ7, XYZ8: _3Dpoint; transparent: boolean; background: TColor; col: TColor; Var image: TBitmap);

implementation

function _3DP(X, Y, Z: integer): _3Dpoint;
begin
   result.X:=X;
   result.Y:=Y;
   result.Z:=Z;
end;

Procedure _3Dto2D(X,Y,Z:Real; ScreenWidth,ScreenHeight:Integer; Var 
TargetX,TargetY:Integer);<br>
begin
   TargetX := (ScreenWidth div 2) + Trunc(X / Z);
   TargetY := (ScreenHeight div 2) + Trunc(Y / Z);
end;

procedure _3Dline(xyz: _3Dpoint; toXYZ: _3Dpoint; col: TColor; Var image: TBitmap);
var
nx, ny, xx, yy: integer;
begin
   _3dto2d(xyz.X, xyz.y, xyz.z, image.Width, image.Height, nX, nY);
   _3dto2d(toxyz.X, toxyz.y, toxyz.z, image.Width, image.Height, XX, YY);
   image.Canvas.Pen.Color:=col;
   image.Canvas.Brush.Color:=col;
   image.Canvas.MoveTo(nx, ny);
   image.Canvas.LineTo(XX, YY);
end;

procedure _3DRectange(p1, p2, p3, p4: _3Dpoint; transparent: boolean; background: TColor; col: TColor; Var image: TBitmap);
var
pt:array [1..4] of Tpoint;
begin
   _3dto2d(p1.x, p1.y, p1.y, image.Width, image.Height, pt[1].x, pt[1].y);
   _3dto2d(p2.x, p2.y, p2.y, image.Width, image.Height, pt[2].x, pt[2].y);
   _3dto2d(p3.x, p3.y, p3.y, image.Width, image.Height, pt[3].x, pt[3].y);
   _3dto2d(p4.x, p4.y, p4.y, image.Width, image.Height, pt[4].x, pt[4].y);
   image.Canvas.Pen.Color:=col;
      if not transparent then image.Canvas.Brush.Color:=background else 
   image.Canvas.Brush.Style:=bsclear;
   image.Canvas.Polygon(pt);
end;

procedure _3DCube(XYZ, XYZ2, XYZ3, XYZ4, XYZ5, XYZ6, XYZ7, XYZ8: _3Dpoint; transparent:boolean; background: TColor; col: TColor; Var image: TBitmap);
begin
   _3drectange(Xyz5, Xyz6, Xyz7, Xyz8, transparent, background, col, image);
   _3drectange(Xyz6, Xyz2, Xyz3, Xyz7, transparent, background, col, image);
   _3drectange(Xyz5, Xyz6, Xyz2, Xyz, transparent, background, col, image);
   _3drectange(Xyz4, Xyz3, Xyz7, Xyz8, transparent, background, col, image);
   _3drectange(Xyz5, Xyz, Xyz4, Xyz8, transparent, background, col, image);
   _3drectange(Xyz, Xyz2, Xyz3, Xyz4, transparent, background, col, image);
end;

end.

Autor PsYcHoSzAyBeR, Paweł Wilkowski.

16 komentarzy

a jak sie tego uzywa? :D znaczy co trzeba wpisac, bo nie do konca kminie, zrobilem to wszystko w jednym unicie, i nie wiem co trzeba na forme wrzucic zeby dzialalo (np na kostce 3D). Wrzucilem imageboxa, buttona i w buttonie dalem funkcje:
_3DCube(001,021,221,201,000,020,220,200,false,clBlue,clRed,image1);
i wywala blad w tych wszystkich 001,021 itp. bo musze okreslic 3dpointy a nie integery, tylko jakbym wiedzial jak sie to okresla...

Slepak : ale to nie bedzie rzutowanie perspektywistytczne:)

Uffff... Właśnie zrobiłem poprawki :).
Zmiany:

  • Poprawienie literówek oraz błędów ortograficznych i (rażących) gramatycznych
  • tagi <delphi>
  • formatowanie kodu
  • poprawki wizualne
  • usunięcie niepotrzebnego formatowania HTML

W treść kodu się zbytnio nie zagłębiałem, więc ewentualnych błędów nie poprawiłem...

jednym najszybszych renderow jakim znam jest:

pomoc = z/ (z + d)
xe = x * pomoc
ye = y * pomoc

gdzie

x - współrzędna x punktu
y - współrzędna y punktu
z - współrzędna z punktu
d - odległość ekranu od obserwatora
xe - współrzędna x punktu na ekranie
ye - współrzędna y punktu na ekranie

tylko jedno dzielenie = 2x szybszy algorytm

Ale nie wziąłeś pod uwagę tego, że "kamera" może się znaleźć przed obiektem. Ja też próbowałem napisać coś takiego, z uwzględnieniem kątów nachylenia, ale efekt jest dość interesujący :)

Jak Crono Napisał To jest "3D linear"

nie czytalem dokladnie kodu ale mam jedne ale, nie ma czegos tutaj takiego jak przeksztalcenie wzgledem pozycji kamery oraaz wzgledem padania kata widzenia[x axis, y axis] oraz przeksztalcenia kata widzenia fov :/

rysunek mi nie wyszedł, ale mam nadzieje, że rozumieć się da bez niego

No tak, ale to jest "3D linear"
Wzory pozmieniaj na:

X':= x0- (x-x0)/(z-z0)*z0
Y':= k(y0- (y-y0)/(z-z0)*z0)

przy czym:

k - szerokość ekr/wysokość ekranu

x0,y0,z0 - współrzędne pynktu w przestrzeni
x,y,z - współrzędne obserwatora przed ekranem....(można dzć stałe [0,0,0] im większe z tym węższy zakres widzialności pynktów)

\ /

  • \ * / *
    A \ B / C
    \ /
    ---------------------- ekran
    \ | /
    \ | /
    \ | /
    \ | /
    * obserwator

jak widać tylko punkt B może być widoczny na ekranie więc trza napisać w procedurce zabezpieczenie

z jest ujemne!!!!

zwiększając y daje się efekt podnoszenia kamery

do tego można pynkty obracać o kąt względem osi OX OY OZ.
Skalować względem punktu...
no i zmieniać połoźenie "kamery" a takrze jej kąt nachylenia do OX OY OZ.

TO BY BYŁO PROSTE 3D

mam 2 zastrzeżenia: 1. ENGINE nie ENGIN i 2. używaj tagu < delphi > i jakos napisz czytelniej kod. Źle się czyta/analizuje :) ale, art spoko

skoro sa problemy z odpaleniem, to moze moglbys zalaczyc zrodla gotowego projektu, ktory mozna o razu uruchomic?

Drajwer to sie tak nie robi. Jeżeli chcesz narysować to to musisz użyć double bufferingu i będzie chodzić supeer płynnie.

STARY TO JEST POPIEPRZONE :[

PS. naprawde duzo czasu potrzeba zaby to uruchomic... ale sie da
PS2. sory za capsa;)

hehe moze sie przydac:)

Sory za błędy ale pisałem w pośpiechu

Jeżeli czegoś nie rozumiesz to mail ślij do mnie