OMG, jos jedan tut by me (SDL + OGL)

OMG, jos jedan tut by me (SDL + OGL)

offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

Meni je opet doslo da piskaram malo osnove. Ovog puta sam pisao u 3 razlicita jezika kod koji bi trebalo da radi na skoro svim OS (testiram samo na Windows (Vista) i Linux (Ubuntu 8.04) jer samo njih imam).

Evo prvo jednog malog programcica, cisto da vidimo kako SDL i OGL rade zajedno.

Od kompajlera cu koristiti: GNU C++, Free Pascal, .NET/Mono kompajler.
IDE u kojima cu raditi: za C++ - Code Blocks, za Pascal - FreePascal IDE, za C# - Visual Studio Express za Windows i MonoDevelop za Linux.

Dakle, sve sto koristim je potpuno besplatno i moze se preuzeti sa interneta, ali vi mozete koristiti sta god zelite.

Kompajleri
GNU C++: http://gcc.gnu.org/
Free Pascal: http://www.freepascal.org/
.NET: http://msdn2.microsoft.com/en-us/netframework/default.aspx
Mono: http://www.mono-project.com/Main_Page

Razvojna okruzenja:
Code Blocks: http://www.codeblocks.org/
Free Pascal: http://www.freepascal.org/
Visual Studio Express: http://msdn2.microsoft.com/en-us/express/default.aspx
MonoDevelop: http://www.monodevelop.com/Main_Page

Pre kodiranja bice potrebno da imate instalirane potrebne biblioteke. Za ovu lekciju je potrebno da imate instaliran SDL i TAO.

SDL je biblioteka koja omogucava unificiran pristup ulaznim (tastatura, mis, dzojstik,...) i izlaznim (monitor, zvucnik) uredjajima na skoro svim platformama. Zbog toga je odlican izbor kada je potrebno pisati kod koji nije vezan samo za Windows.

SDL: http://www.libsdl.org/
SDL za Pascal: http://jedi-sdl.pascalgamedevelopment.com/

TAO Framework je skup .NET assembly fajlova koji omogucavaju koriscenje SDL i OpenGL u .Net jezicima.

TAO: http://www.taoframework.com/

Napomena: ovde necu objasnjavati kako se postavljaju opcije kompajlera/linkera, podrazumeva se da onaj ko cita tekst zna osnove C++, Pascal i C# jezika.

Sad kad imamo sve sto nam treba, mozemo krenuti sa pisanjem koda. Prvo cemo kreirati projekat za svaki jezik koji ce sadrzati samo praznu glavnu proceduru:

C++
int main ( int argc, char** argv ) {     return 0; }

Pascal
program basic; begin end.

C#
using System; namespace basic {     class Basic     {         static void Main(string[] args)         {         }     } }

Kad se malo bolje pogledaju ovi kodovi, vidi se da se ne razlikuju mnogo. Ok... idemo na inicijalizaciju SDL biblioteke. Ako inicijalizacija ne uspe, sve komande vezane za SDL nece moci da se izvrse pa je to prva komanda koju izvrsavamo. Na kraju programa moramo osloboditi resurse koje zauzima SDL. Najsigurniji nacin je da taj kod ubacimo u deo koji ce se uvek izvrsiti na kraju, bez obzira da li se neka greska desila u kodu:

C++
#include <SDL.h> int main ( int argc, char** argv ) {     // initialize SDL video     if (SDL_Init(SDL_INIT_VIDEO) < 0)     {         return 1;     }     // make sure SDL cleans up before exit     atexit(SDL_Quit);     return 0; }

Pascal
program basic; uses   SDL; var   OldExitProc: Pointer; procedure SDLExitProc; begin   ExitProc := OldExitProc;   SDL_Quit; end; begin   // initialize SDL video   if (SDL_Init(SDL_INIT_VIDEO) < 0) then     Halt(1);   // make sure SDL cleans up before exit   OldExitProc := ExitProc;   ExitProc := @SDLExitProc; end.

C#
using System; using Tao.Sdl; namespace basic {     class Basic     {         static void Main(string[] args)         {             try             {                 // initialize SDL video                 Sdl.SDL_Init(Sdl.SDL_INIT_VIDEO);             }             finally             {                 // make sure SDL cleans up before exit                 Sdl.SDL_Quit();             }         }     } }

Imamo sve sto je potrebno za kreiranje prozora... ako nesto podje naopako, SDL_Quit ce biti pozvan tako da ne moramo mnogo da brinemo:

C++
// create a new window SDL_Surface* surface = SDL_SetVideoMode(width, height, bpp, flags); if (!surface) {     return 1; }

Pascal
// create a new window Surface := SDL_SetVideoMode(Width, Height, BPP, flags); if (Surface = nil) then   Halt(1);

C#
// create a new window IntPtr surface = Sdl.SDL_SetVideoMode(width, height, bpp, flags);

Eeee... imamo prozorce Smile Da ne bi nestalo tkao brzo, sledece na redu je glavna petlja programa. U njoj cemo obradjivati poruke i kazati programu kad da se zatvori:

C++
// program main loop bool done = false; while (!done) {     SDL_Event event;     while (SDL_PollEvent(&event))     {         // check for messages         switch (event.type)         {             // exit if the window is closed             case SDL_QUIT:             {                 done = true;                 break;             }             // check for keypresses             case SDL_KEYDOWN:             {                 // exit if ESCAPE is pressed                 if (event.key.keysym.sym == SDLK_ESCAPE)                     done = true;                 break;             }         }     } }

Pascal
// program main loop while not Done do begin   while SDL_PollEvent(@Event) <> 0 do   begin     // check for messages     case Event.type_ of       // exit if the window is closed       SDL_QUITEV:         Done := True;       // check for keypresses       SDL_KEYDOWN:         // exit if ESCAPE is pressed         if Event.key.keysym.sym = SDLK_ESCAPE then           Done := True;     end;   end; end;

C#
bool done = false; while (!done) {     Sdl.SDL_Event event_;     while (Sdl.SDL_PollEvent(out event_) != 0)     {         // check for messages         switch (event_.type)         {             // exit if the window is closed             case Sdl.SDL_QUIT:             {                 done = true;                 break;             }             // check for keypresses             case Sdl.SDL_KEYDOWN:             {                 // exit if ESCAPE is pressed                 if (event_.key.keysym.sym == Sdl.SDLK_ESCAPE)                     done = true;                 break;             }         }     } }

Ovo prozorce ne brise pozadinu, ne crta nista... idemo to da sredimo. Pre nego sto nesto nacrtamo, prvo cemo postaviti neke vrednosti za OpenGL (boju brisanja, velicinu ekrana i slicno):

C++
void InitGL() {     // initialize OpenGL     glShadeModel(GL_SMOOTH);     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     glClearDepth(1.0f);     glEnable(GL_DEPTH_TEST);     glDepthFunc(GL_LEQUAL);     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); } void ResizeWindow(int width, int height) {     if (height == 0)         height = 1;     // setup viewport     glViewport(0, 0, width, height);     // setup matrices     glMatrixMode(GL_PROJECTION);     glLoadIdentity();     gluPerspective(45.0f, (float)width / (float)height, 0.1f, 100.0f);     glMatrixMode(GL_MODELVIEW);     glLoadIdentity(); }

Pascal
procedure InitGL; begin   // initialize OpenGL   glShadeModel(GL_SMOOTH);   glClearColor(0, 0, 0, 0);   glClearDepth(1);   glEnable(GL_DEPTH_TEST);   glDepthFunc(GL_LEQUAL);   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); end; procedure ResizeWindow(Width: Integer; Height: Integer); begin   if (Height = 0) then     Height := 1;   // setup viewport   glViewport(0, 0, Width, Height);   // setup matrices   glMatrixMode(GL_PROJECTION);   glLoadIdentity;   gluPerspective(45, Width/Height, 0.1, 100);   glMatrixMode(GL_MODELVIEW);   glLoadIdentity; end;

C#
static void InitGL() {     // initialize OpenGL     Gl.glShadeModel(Gl.GL_SMOOTH);     Gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     Gl.glClearDepth(1.0f);     Gl.glEnable(Gl.GL_DEPTH_TEST);     Gl.glDepthFunc(Gl.GL_LEQUAL);     Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST); } static void ResizeWindow(int width, int height) {     if (height == 0)         height = 1;     // setup viewport     Gl.glViewport(0, 0, width, height);     // setup matrices     Gl.glMatrixMode(Gl.GL_PROJECTION);     Gl.glLoadIdentity();     Glu.gluPerspective(45.0f, (float)width / (float)height, 0.1f, 100.0f);     Gl.glMatrixMode(Gl.GL_MODELVIEW);     Gl.glLoadIdentity(); }

Ove funkcije pozivamo odmah posle kreiranja prozora kako bi svebilo spremno za crtanje. Na kraju funkcija koja ce da iscrta trouglic na ekranu:

C++
void DrawScene() {     // empty buffers     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     glPushMatrix();     glTranslatef(0.0f, 0.0f, -5.0f);     glRotatef(SDL_GetTicks() / 50.0f, 0.0f, 0.0f, 1.0f);     // draw scene     glBegin(GL_TRIANGLES);         glColor3f(1.0f, 0.0f, 0.0f);         glVertex3f(0.0f, 1.0f, 0.0f);         glColor3f(0.0f, 1.0f, 0.0f);         glVertex3f(1.0f, -1.0f, 0.0f);         glColor3f(0.0f, 0.0f, 1.0f);         glVertex3f(-1.0f, -1.0f, 0.0f);     glEnd();     glPopMatrix();     // show scene     SDL_GL_SwapBuffers(); }

Pascal
procedure DrawScene; begin   // empty buffers   glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);   glPushMatrix;   glTranslatef(0, 0, -5);   glRotatef(SDL_GetTicks / 50, 0, 0, 1);   // draw scene   glBegin(GL_TRIANGLES);     glColor3f(1, 0, 0);     glVertex3f(0, 1, 0);     glColor3f(0, 1, 0);     glVertex3f(1, -1, 0);     glColor3f(0, 0, 1);     glVertex3f(-1, -1, 0);   glEnd;   glPopMatrix;   // show scene   SDL_GL_SwapBuffers; end;

C#
static void DrawScene() {     // empty buffers     Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);     Gl.glPushMatrix();     Gl.glTranslatef(0.0f, 0.0f, -5.0f);     Gl.glRotatef(Sdl.SDL_GetTicks() / 50.0f, 0.0f, 0.0f, 1.0f);     // draw scene     Gl.glBegin(Gl.GL_TRIANGLES);         Gl.glColor3f(1.0f, 0.0f, 0.0f);         Gl.glVertex3f(0.0f, 1.0f, 0.0f);         Gl.glColor3f(0.0f, 1.0f, 0.0f);         Gl.glVertex3f(1.0f, -1.0f, 0.0f);         Gl.glColor3f(0.0f, 0.0f, 1.0f);         Gl.glVertex3f(-1.0f, -1.0f, 0.0f);     Gl.glEnd();     Gl.glPopMatrix();     // show scene     Sdl.SDL_GL_SwapBuffers(); }

Ova funkcija se poziva u glavnoj petlji posle obrada poruka... i to je to Smile
https://www.mycity.rs/must-login.png

Dopuna: 20 Maj 2008 21:05

Da bi uopste mogli da koristimo SDL, moramo ga prvo inicijalizovati. Funkcija za inicijalizaciju je definisana ovako:

C++
int SDL_Init(Uint32 flags);

Pascal
function SDL_Init( flags : UInt32 ) : Integer;

C#
public static int SDL_Init(int flags)

Kao parametar uzima vrednost koja oznacava koji deo SDL-a zelimo da inicijalizujemo. Moguce vrednosti su:
SDL_INIT_TIMER
SDL_INIT_AUDIO
SDL_INIT_VIDEO
SDL_INIT_CDROM
SDL_INIT_JOYSTICK
SDL_INIT_EVERYTHING

Na kraju programa je potrebno osloboditi resurse koje SDL zauzima. To se vrsi komandom:

C++
void SDL_Quit(void);

Pascal
procedure SDL_Quit;

C#
public static extern void SDL_Quit();

Program koji inicijalizuje i oslobadja memoriju i nije bas zanimljiv, zato cemo da pogledamo funkciju koja kreira SDL prozor:

C++
SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags);

Pascal
function SDL_SetVideoMode(width, height, bpp: Integer; flags: UInt32): PSDL_Surface;

C#
public static extern IntPtr SDL_SetVideoMode(int width, int height, int bpp, int flags);

Ulazni parametri su sirina, visina, broj bitova po pikselu i osobine prozora. Kao rezultat dobijamo pokazivac na kreiran prozor.
Moguce osobine prozora su:
SDL_SWSURFACE: memorija za sliku se zauzima u sistemskoj memoriji
SDL_HWSURFACE: memorija za sliku se zauzima u memoriji graficke kartice
SDL_ASYNCBLIT: asinhrono prebacivanje slike u memoriju
SDL_ANYFORMAT: broj bitova po pixelu nije bitan
SDL_HWPALETTE: SDL ima eksluzivno pravo nad paletom
SDL_DOUBLEBUF: Ukljucivanje hardverskog "double buffer-a"
SDL_FULLSCREEN: Prozor preko celog ekrana
SDL_OPENGL: OpenGL moze da crta po prozoru
SDL_OPENGLBLIT: OpenGL i obicne 2D funkcije mogu crtati po prozoru (nije preporucljivo)
SDL_RESIZABLE: Prozor moze da se siri i skuplja
SDL_NOFRAME: Prozor bez ivica (samo slika)

Posto SDL_Quit komanda zatvara sve kreirane prozore, nece nam trebati funkcija koja zatvara glavni prozor jer on treba da bude otvoren do samog kraja programa.

Od osnovnih funkcija, treba da spomenemo jos jednu:

C++
int SDL_PollEvent(SDL_Event *event);

Pascal
function SDL_PollEvent(event: PSDL_Event): Integer;

C#
public static extern int SDL_PollEvent(out SDL_Event sdlEvent);

Ova funkcija cita sta se dogadja s prozorom i daje nam informaciju o tome. Na osnovu te informacije mi znamo da li je korisnik pritisnuo taster, da li je pomerio misa, da li je zatvorio prozor, itd. Uzima strukturu u koju ce nam upisati sta se desilo, a vraca 0 ako nema novih poruka ili neki drugi broj ako ih ima.

Struktura koja sadrzi primljenu poruku izgleda ovako:

C++
typedef union{   Uint8 type;   SDL_ActiveEvent active;   SDL_KeyboardEvent key;   SDL_MouseMotionEvent motion;   SDL_MouseButtonEvent button;   SDL_JoyAxisEvent jaxis;   SDL_JoyBallEvent jball;   SDL_JoyHatEvent jhat;   SDL_JoyButtonEvent jbutton;   SDL_ResizeEvent resize;   SDL_ExposeEvent expose;   SDL_QuitEvent quit;   SDL_UserEvent user;   SDL_SywWMEvent syswm; } SDL_Event;

Pascal
TSDL_Event = record   case UInt8 of     SDL_NOEVENT: (type_: byte);     SDL_ACTIVEEVENT: (active: TSDL_ActiveEvent);     SDL_KEYDOWN, SDL_KEYUP: (key: TSDL_KeyboardEvent);     SDL_MOUSEMOTION: (motion: TSDL_MouseMotionEvent);     SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP: (button: TSDL_MouseButtonEvent );     SDL_JOYAXISMOTION: (jaxis: TSDL_JoyAxisEvent );     SDL_JOYBALLMOTION: (jball: TSDL_JoyBallEvent );     SDL_JOYHATMOTION: (jhat: TSDL_JoyHatEvent );     SDL_JOYBUTTONDOWN, SDL_JOYBUTTONUP: (jbutton: TSDL_JoyButtonEvent );     SDL_VIDEORESIZE: (resize: TSDL_ResizeEvent );     SDL_QUITEV: (quit: TSDL_QuitEvent );     SDL_USEREVENT : ( user : TSDL_UserEvent );     SDL_SYSWMEVENT: (syswm: TSDL_SysWMEvent ); end;

C#
public struct SDL_Event {     // Fields     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_ActiveEvent active;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_MouseButtonEvent button;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_ExposeEvent expose;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_JoyAxisEvent jaxis;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_JoyBallEvent jball;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_JoyButtonEvent jbutton;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_JoyHatEvent jhat;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_KeyboardEvent key;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_MouseMotionEvent motion;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_QuitEvent quit;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_ResizeEvent resize;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_SysWMEvent syswm;     [FieldOffset(0)]     public byte type;     [FieldOffset(0)]     public Tao.Sdl.Sdl.SDL_UserEvent user; }

Kao sto vidite, dogadjaji su razvrstani tako da mozemo dobiti tacno one podatke koje su nam potrebni za svaki tip.

To su neke od najosnovnijih funkcija koje cemo koristiti. Sledece na redu je kreiranje jednostavne klase koja ce nam olaksati rad sa SDL prozorom.

Da ne bi morali da se zezamo svaki put sa kreiranjem prozora, podesavanjem OpenGL-a i ostalim koliko-toliko standardnim stvarima, kreiracemo jednu klasu koja ce nam omoguciti lako kreiranje prozora, crtanje po njemu i obradjivanje poruka. Ovu klasu cemo kasnije nadogradjivati i koristiti u svim projektima.

Necemo koristiti nista sto do sad nismo, a objektno orientisano programiranje svi znaju Smile Ovo ce biti lako Smile

Klasa ce za pocetak izgledati ovako:

C++
class CSDLWindow { protected:     SDL_Surface* surface;     bool initialized;     int width, height, bpp;     bool fullscreen;     virtual bool EventHandler(SDL_Event Event);     virtual bool InitGL();     virtual bool Resize(int Width, int Height);     virtual bool Draw(float AppTime, float DeltaTime); public:     CSDLWindow(int Width, int Height, int BPP, bool FullScreen);     virtual ~CSDLWindow();     int Run(); };

Pascal
TSDLWindow = class protected   FSurface: PSDL_Surface;   FInitialized: Boolean;   FWidth, FHeight, FBPP: Integer;   FFullScreen: Boolean;   function EventHandler(Event: TSDL_Event): Boolean; virtual;   function InitGL: Boolean; virtual;   function Resize(Width, Height: Integer): Boolean; virtual;   function Draw(AppTime, DeltaTime: Double): Boolean; virtual; public   constructor Create(AWidth, AHeight, ABPP: Integer;     AFullScreen: Boolean); virtual;   destructor Destroy; override;   function Run: Integer; end;

C#
class SDLWindow {     protected IntPtr surface;     protected bool initialized;     protected int width, height, bpp;     protected bool fullscreen;     protected virtual bool EventHandler(Sdl.SDL_Event Event);     protected virtual bool InitGL();     protected virtual bool Resize(int Width, int Height);     protected virtual bool Draw(float AppTime, float DeltaTime);     public SDLWindow(int Width, int Height, int BPP, bool FullScreen);     ~SDLWindow();     public int Run(); }

Posto se u klasi pozivaju funkcije koje su vec objasnjene (inicijalizacija SDL, kreiranje prozora, citanje poruka), necu postaviti kod svake funkcije u klasi nego cu samo objasniti kako se koristi. OpenGL funkcije koriscene u ovoj klasi ce biti objasnjene u sledecem postu.

Klasa se jednostavno koristi. Kreira se instanca klase, pozove se Run metoda i, na kraju, se objekat oslobodi. To je sve. U kodu bi to ovako izgledalo:

C++
int main ( int argc, char** argv ) {     CSDLWindow* sdlwin = new CSDLWindow(800, 600, 24, false);     sdlwin->Run();     delete sdlwin;     return 0; }

Pascal
var   SDLWin: TSDLWindow; begin   SDLWin := TSDLWindow.Create(800, 600, 24, False);   SDLWin.Run;   SDLWin.Free; end.

C#
static void Main(string[] args) {     SDLWindow sdlwin = new SDLWindow(800, 600, 24, false);     sdlwin.Run();     // C# sam oslobodi kreirane objekte }

Prva dva parametra kod kreiranja instance klase su sirina i visina prozora, zatim ide broj bitova po pikselu i, na kraju, da li je prozor preko celog ekrana ili ne.

E, sad... kreiranje klase koja nasledjuje ovu je isto lako. Dovoljno je da promenimo Draw metod i scena ce se drugacije iscrtavati, ako promenimo InitGL metod, OpenGL ce se drugacije inicijalizovati, ako promenimo EventHandler, mozemo poruke da obradjujemo kako hocemo, itd... Evo jednog malog primera u kojem cemo promeniti samo Draw metod:

C++
class CMySDLWindow : public CSDLWindow { protected:     virtual bool Draw(float AppTime, float DeltaTime)     {         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);         glPushMatrix();         glTranslatef(0.0f, 0.0f, -5.0f);         glRotatef(AppTime * 90.0f, 0.0f, 0.0f, 1.0f);         glBegin(GL_TRIANGLES);             glColor3f(1.0f, 0.0f, 0.0f);             glVertex3f(0.0f, 1.0f, 0.0f);             glColor3f(0.0f, 1.0f, 0.0f);             glVertex3f(1.0f, -1.0f, 0.0f);             glColor3f(0.0f, 0.0f, 1.0f);             glVertex3f(-1.0f, -1.0f, 0.0f);         glEnd();         glPopMatrix();         SDL_GL_SwapBuffers();         return true;     } public:     CMySDLWindow(int Width, int Height, int BPP, bool FullScreen) :         CSDLWindow(Width, Height, BPP, FullScreen) {} }; int main ( int argc, char** argv ) {     CSDLWindow* sdlwin = new CMySDLWindow(800, 600, 24, false);     sdlwin->Run();     delete sdlwin;     return 0; }

Pascal
type   { TMySDLWindow }   TMySDLWindow = class(TSDLWindow)   protected     function Draw(AppTime, DeltaTime: Double): Boolean; override;   end; { TMySDLWindow } function TMySDLWindow.Draw(AppTime, DeltaTime: Double): Boolean; begin   glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);   glPushMatrix;   glTranslatef(0, 0, -5);   glRotatef(AppTime * 90, 0, 0, 1);   glBegin(GL_TRIANGLES);     glColor3f(1, 0, 0);     glVertex3f(0, 1, 0);     glColor3f(0, 1, 0);     glVertex3f(1, -1, 0);     glColor3f(0, 0, 1);     glVertex3f(-1, -1, 0);   glEnd;   glPopMatrix;   SDL_GL_SwapBuffers;   Result := True; end; var   SDLWin: TSDLWindow; begin   SDLWin := TMySDLWindow.Create(800, 600, 24, False);   SDLWin.Run;   SDLWin.Free; end.

C#
class MySDLWindow : SDLWindow {     protected override bool Draw(float AppTime, float DeltaTime)     {         Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);         Gl.glPushMatrix();         Gl.glTranslatef(0.0f, 0.0f, -5.0f);         Gl.glRotatef(AppTime * 90.0f, 0.0f, 0.0f, 1.0f);         Gl.glBegin(Gl.GL_TRIANGLES);             Gl.glColor3f(1.0f, 0.0f, 0.0f);             Gl.glVertex3f(0.0f, 1.0f, 0.0f);             Gl.glColor3f(0.0f, 1.0f, 0.0f);             Gl.glVertex3f(1.0f, -1.0f, 0.0f);             Gl.glColor3f(0.0f, 0.0f, 1.0f);             Gl.glVertex3f(-1.0f, -1.0f, 0.0f);         Gl.glEnd();         Gl.glPopMatrix();         Sdl.SDL_GL_SwapBuffers();         return true;     }     public MySDLWindow(int Width, int Height, int BPP, bool FullScreen) : base(Width, Height, BPP, FullScreen) { } } class OOSDL {     static void Main(string[] args)     {         SDLWindow sdlwin = new MySDLWindow(800, 600, 24, false);         sdlwin.Run();     } }

To je to za sad... klasu cemo prisirivati/popravljati kad kod vidimo da je to potrebno. Na kraju bi trebalo da dobijemo finu klasicu koja ce pomoci svakom ko se prvi put sretne sa SDL + OpenGL.

https://www.mycity.rs/must-login.png

Dopuna: 23 Maj 2008 20:15

Ovog puta necu pisati nov kod, samo cu objasniti neke od OpenGL funkcija koje smo do sad koristili. OpenGL radi kao jedna velika state masina. Kada se neko stanje postavi ono vazi sve dok se ponovo ne promeni. Zbog toga se na poceku programa, kod nas u InitGL, postavljaju neke vrednosti za koje mislimo da ih necemo menjati. U poslednjem primeru smo u InitGL imali kod koji:
1. postavlja nacin sencenja objekata
2. postavlja boju kojom ce se pozadina brisati
3. ukljucuje depth buffer (z-buffer)
4. postavlja nacin racunanja za prevodjenje 3d koordinate u 2d koordinatu

Objekti u OpenGL mogu da se sence na dva nacina. Prvi nacin se zove FLAT. Taj nacin sencenja oboji ceo ovjekat jednom bojom. Na osnovu boja verteksa i svetla se racuna srednja vrednost boje koja se tada iscrtava. Drugi nacin se zove SMOOTH. Kada je ovaj nacin aktiviran, boje izmedju vertexa se interpoliraju i time se dobija fin prelaz.
Podatak o ovom stanju se postavlja funkcijom glShadeModel koja kao parametar uzima konstantu GL_SMOOTH ili GL_FLAT.

Ove slike prikazuju isti objekat u oba rezima sencenja
FLAT
SMOOTH

Boja pozadine se postavlja funkcijom glClearColor. Ona uzima cetiri parametra koji predstavljaju kolicinu crvene, zelene i plave boje, kao i providnost. Providnost kod ove boje nema znacenje jer ona brise sve sto je bilo nacrtano i nema niceg sto bi se videlo kroz pozadinu. Vrednost za svaki od ova cetiri parametra moze da bude od 0 do 1.

Depth buffer ili, tzv., Z-buffer se koristi da ne bi morali da crtamo prvo najdalje objekta pa onda one blize. Kad ne bi bilo Z-buffer-a, Z koordinata bi sluzila samo da bi se na onovu nje odredilo koliko objekat treba da bude mali (sto je dalji, to je manji) i onda bi moglo da se desi da nacrtamo objekat koji se nalazi jako blizu kamere, a posle njega da nacrtamo objekat u daljini i da se taj objekat u daljini vidi preko prvog objekat jer smo njega poslednjeg nacrtali. Z-buffer resava bas taj problem.
Sta se desava u Z-bufferu... kada iscrtamo neki objekat, u Z-buffer-u se upise na kojoj daljini se on nalazi, sledeci objekat koji se crta prvo mora da prodje test. Taj test se svodi na uporedjivanje njegove Z koordinate sa koordinatom upisanom u Z-buffer. Ako objekat prodje test, bice iscrtan i njegova vrednost ce biti upisana u Z-buffer, a ako ne prodje, OpenGL ga nece iscrtati, a Z-bufer ce ostati nepromenjen. Vrednosti koje se upisiju u Z-buffer idu od 0 (najblize kameri) do 1 (najdalje).
Da bi podesili Z-buffer treba da kazemo kojom vrednoscu zelimo da brisemo Z-buffer kada crtamo nov frejm i koju funkciju zelimo za uporedjivanje sadrzaja Z-buffer-a sa objektima koji se crtaju.
Funkcija za uporedjivanje se uglavnom postavlja na GL_LEQUAL, sto znaci da ce objekat proci test ako je njegova Z koordinata manja ili jednaka onoj upisanoj u Z-buffer-u, sto znaci da ce objekat biti iscrtan ako se nalazi na istoj Z koordinati ili blize od poslednjeg iscrtanog objekata na toj poziciji.
Postavljanje test funkcije se vrsi pozivom glDepthFunc. glDepthFunc prihvata samo jedan parametar koji moze biti:
GL_NEVER: objekat nikad ne prolazi test
GL_LESS: objekat prolazi test ako je blizi
GL_EQUAL: objekat prolazi test ako je na istoj daljini kao prethodni iscrtan na toj poziciji
GL_LEQUAL: objekat prolazi test ako je blizi ili na istoj daljini kao prethodni
GL_GREATER: objekat prolazi test ako je dalji
GL_NOTEQUAL: objekat prolazi test ako je blizi ili dalji
GL_GEQUAL: objekat prolazi test ako je dalji ili na istoj daljini kao prethodni
GL_ALWAYS: objekat uvek prolazi test

Brisanje Z-buffer-a se skoro uvek vrsi vrednoscu 1. To je najdalja moguca vrednost pa ce objekat proci test ako nista nije iscrtano ispred njega.
Postavljanje ove vrednosti se vrsi funkcijom glClearDepth.

Za kraj ostaje jos ukljucivanje Z-buffer-a. Ukljucivanje raznih mogucnosti se vrsi komandom glEnable, a iskljucivanje glDisabled. Za ukljuciavnje Z-buffer-a se prosledjuje parametar GL_DEPTH_TEST.

Na kraju inicijalizacije kazemo OpenGL-u kako zelimo da prevodi 3d koordinate u 2d. Ako ste igrali neke stare igre, verovatno ste primetili kako se teksture blizu kamere pomalo deformisu i ne izgledaju kako bi trebale. Takve stvari se sad mogu izbeci postavljanjem nacina na koji ce OpenGL da racuna neke parametre. Funkcija koja se koristi je glHint, a sve moguce parametre mozete pronaci na ovoj adresi: http://www.opengl.org/documentation/specs/man_page...../hint.html

Posle inicijalizacije OpenGL-a, u programu se poziva funkcija koja podesava opcije koje su vezane za velicinu prozora. Tu podesavamo:

1. Deo prozora na kojem OpenGL crta
2. Osnovnu matricu projekcije
3. Osnovnu matricu polozaja modela

Deo prozora po kojem OpenGL crta se definise funkcijom glViewport. Ona uzima 4 parametra koji definisu pravougaonik na prozoru koji je rezervisan za OpenGL. Za igre je normalno da se cela povrsina koristi za iscrtavanje.

Matrica projekcije sluzi da se odredi kako se 3d tacke konvertuju u 2d tacke. Ucili ste iz tehnickog da postoji perspektiva, ortogonalna projekcija, izometrija i slicno... to su sve nacini na koji moguze 3d tacka da se prevede u 2d tacku. Za pocetak cemo koristiti perspektivu jer ona daje najrealniji prikaz objekata... kako budemo napredovali spomenucemo i ostale vrste ako nam budu zatrebale.

Da bismo postavili neku matricu prvo moramo da je izaberemo. Biranje matrice koju zelimo da menjamo se vrsi funkcijom glMatrixMode. Ona uzima samo jedan parametra koji kaze koju matricu zelimo: GL_MODELVIEW, GL_PROJECTION ili GL_TEXTURE.

Za postavljanje matrice projekcije imamo pomocne funkcije. Pomocna funkcija za perspektivu se zove gluPerspective. Prva dva parametra odredjuju koliko siroko kamera vidi. Prvi parametar je sirina pogleda po Y pravcu, dok se je drugi parametar odnos izmedju sirine i visine ekrana koji OpenGL koristi za racunanje sirine pogleda po X pravcu. Druga dva parametra predstavljaju najmanju i najvecu udaljenost koju kamera moze da vidi. To znaci da se objekti koji su blizi od najblize ili dalji od najdalje udaljenosti nece prikazivati.

Matrica polozaja modela odredjuje gde ce se 3d tacke pomeriti pre nego sto budu iscrtane. To nam omogucava da, recimo, svaki objekat definisemo oko koordinatnog pocetka i da ga zatim ovom matricom pomerimo gde zelimo da bude u 3d svetu. U primeru ovu matricu samo resetujemo na matricu koja nece nista menjati. To se radi funkcijom glLoadIdentity.

Imajte samo na umu da OpenGL pamti koju ste zadnju matricu izabrali za menjanje i svaka funkcija koja menja matricu ce raditi nad tom izabranom. O matricama cemo govoriti jos malo kasnije.

Kada je sve podeseno, moze se preci na crtanje. Sada cu objasniti samo funkcije za brisanje ekrana pre svakog frejma i za prikaz scene na ekran.

Brisanje ekrana se vrsi funkcijom glClear. glClear funkcija ustvari brise buffer-e koje koristi OpenGL. Ona uzima samo jedan parametar koji kaze koje buffer-e treba obrisati. Za sad je dovoljno da znate da moze da brise sliku i Z-buffer sto i radimo u svakom primeru.

Funkcija koja prikazuje sliku iz OpenGL na ekran se zove SDL_GL_SwapBuffers. Ona je deo SDL biblioteke. Kada se zavrsi crtanje frjma, dovoljno je da pozovemo ovu funkciju i slika ce se pojaviti na ekranu.

To je to za sad, sledece na redu je crtanje jednostavnih oblika, ako jos ima zainteresovanih Smile

Dopuna: 23 Maj 2008 20:17

Sad malo da objasnimo osnovne funkcije za crtanje objekata. Imajte na umu da su ove funkcije jako spore i da se uglavnom koriste kada je potrebno nesto na brzinu nacrtati i videti kako to izgleda.

Ovaj kod smo videli u ranijim primerima (ne pisem za svaki jezik posebno jer ce svako ko je pratio dosadasnje primere prepoznati deo koda):
... glMatrixMode(GL_PROJECTION); glLoadIdentity; gluPerspective(45, Width / Height, 0.1, 1000); glMatrixMode(GL_MODELVIEW); glLoadIdentity; ...

glMatrixMode funkcija sluzi da bismo izabrali matricu koju cemo menjati. OpenGL radi sa 3 matrice:
1. GL_MODELVIEW: matrica koja vertex (vertex je tacka u 3D prostoru) iz koordinatnog sistema objekta u koordinatni sitem cele scene.
2. GL_PROJECTION: ona sluzi da vertex iz koordinatnog sistema scene pretvori u pixel (pixel je tacka u 2D) u koordinatnom sistemu ekrana.
3. GL_TEXTURE: ova matrica sluzi za manipulaciju koordinatnog sistema tekstura koje se crtanu na objektima.

Kada se matrica izabere uz pomoc glMatrixMode funkcije, svaki poziv funkciji koja menja matricu ce vaziti za tu selektovanu.

glLoadIdentity funkcija ce resetovati trenutno izabranu matricu tako da ne modifikuje poziciju tacke. Posto skoro sve funkcije za promenu matrica rade tako sto na trenutnu verdnost dodaju jos i svoju, ponekad je potrebno isprazniti matricu, i bas tome sluzi ova funkcija.

Za postavljanje matrice projekcije imamo jednu pomocnu funkciju koja se zove gluPerspective. Ona uzima 4 parametra, ugao gledanja po Y osi, odnos izmedju ugla gledanja po X i Y osi, minimalna i maksimalna udaljenost objekata koji ce se videti.

Model matricu cemo za sad ostaviti... idemo da crtamo pa cemo je tamo objasniti Smile

... glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); glLoadIdentity; glTranslatef(0, 0, -5); glBegin(GL_TRIANGLES);   glColor3f(1, 0, 0);   glVertex3f(-1, -1, 0);   glColor3f(0, 1, 0);   glVertex3f(1, -1, 0);   glColor3f(0, 0, 1);   glVertex3f(0, 1, 0); glEnd; ...

Brisanje pozadine smo vec spomenuli... sledece dve linije cemo za sd da preskocimo i idemo direktno na deo za crtanje.

glBegin oznacava pocetak bloka za crtanje. Posle ove funkcije je dozvoljeno pozovati druge koje vrse iscrtavanje. Ona uzima samo jedan parametar od kojeg zavisi kako ce se vertexi koje budemo crtali spajati u objekat.

GL_POINTS: Svaki vertex predstavlja jednu tacku.

GL_LINES: Svaki par vertexa definise jednu liniju.

GL_LINE_STRIP: Crta liniju koja povezuje vertexe od prvog do poslednjeg.

GL_LINE_LOOP: Isto kao predhodno samo sto na kraju spoji i poslednju i prvu tacku.

GL_TRIANGLES: Svaka 3 vertexa definisu jedan trougao.

GL_TRIANGLE_STRIP: Crta listu trouglova. Jedan trougao je definisan za svaku tacku posle prve dve. Za neparno n, trougao definisu tacke n, n+1 i n+2. Za parno n, trougao definisu tacke n+1, n, n+2.

GL_TRIANGLE_FAN: Crta listu trouglova. Jedan trougao je definisan za svaku tacku posle prve dve. Trougao je definisan tackama 1, n+1, n+2.

GL_QUADS: Svaka 4 vertexa definisu jedan pravougaonik.

GL_QUAD_STRIP: Crta listu pravougaonika. Pravougaonik je definisan svakim parom verteksa posle prvog para. Vertexi 2n-1, 2n, 2n+2 i 2n+1 definisu pravougaonik n.

GL_POLYGON: Crta jedan konveksan poligon od svih tacaka.

U gore pokazanom kodu se koristi GL_TRIANGLES sto znaci da ce od tri vertexa koja su unesena biti nacrtan jedan trougao. Najjednostavniji nacin unosenja vertexa je komanda glVertex3f. Postoje vise vrsta glVertex* komandi i one se razlikuju u broju i tipu promenljivih koje uzimaju. Npr. glVertex2i uzima 2 parametra (x i y, z postavlja na 0) i tip podatka je ceo broj. U najvecem broju slucajeva glVertex3f ce vrsiti posao kako treba.

Vertex koji se crta uzima boju, materijal, texturu, itd, koja je bila podesena pre poziva glVertex* funkcije. Zbog jednostavnosti primera do sada je menjana samo boja. Boja se menja funkcijama glColor*. Opet ce glColor3f skoro uvek biti sasvim dovoljna (ona definise r, g i b vrednosti).

Ako pogledate kod, videcete da se za sve vertexe Z postavlja na 0, a pre toga je matrica proekcije bila podesena da ne prikacuje objekte koji su blizi od 0,1. To znaci da trougao nece biti vidljiv. Zato imamo one dve funkcije odmah posle brisanja buffera. Vec znamo da glLoadIdentity resetuje matricu. glTranslatef je funkcija koja u trenutnu matricu ubacuje matricu za pomeranje po X, Y i Z osi. U ovom primeru cemo X i Y ostaviti na 0, a Z na -5... to znaci da ce svaka koordinata vertexa biti pomerena po Z osi za -5 (u OpenGL, -Z znaci da je objekat dalji od kamere... -Z ulazi u ekran). To znaci da ce prvi pizel biti nacrtan na -1, -1, -5 i bice vidljiv. glTranslatef je samo jedna od funkcija za rad sa matricama... kasnije cemo spomenuti jos neke.

Na kraju crtanja pozivamo glEnd funkciju. Posle nje je moguce poceti nov blok glBegin komandom (recimo prvo hocemo da crtamo trougolove, pa kvadrate, pa linije,...).

Toliko za sada... sad znate osnovne komande za crtanje, pa probajte malo da se poigrate s njima Smile

Next... textures...



Registruj se da bi učestvovao u diskusiji. Registrovanim korisnicima se NE prikazuju reklame unutar poruka.
offline
  • Pridružio: 21 Okt 2005
  • Poruke: 65
  • Gde živiš: localhost

Svaka cast Srki_82.
Posebno je interesantno ovo "sviranej na 3 klavijature" Very Happy

Ovakvim tutorialima mnogima otvarash ochi i nadam se da ce znati da ih iskoriste.

Zahvaljujem Zagrljaj



offline
  • Srđan Tot
  • Am I evil? I am man, yes I am.
  • Pridružio: 12 Jul 2005
  • Poruke: 2483
  • Gde živiš: Ljubljana

SDLWindow klasa se malo promenila. Dodate su 2 funkcije, jedna za ucitavanje i jedna za oslobadjanje objekata koje koristimo za iscrtavanje, i mogucnost imenovanja prozora. Ono sto je bitno se nalazi u GLTexture klasi.

Teksturu ucitavamo uz pomoc IMG_Load funkcije iz SDL_image paketa. Ona pokusa da ucita sliku, i vrati nam SDL_Surface objekat. Posto svaka slika ima neki svoj format, ne mozemo podatk koristiti takve kakvi jesu. Zbog toga popunjavamo SDL_PixelFormat strukturu u kojoj pisemo kako zelimo da dobijemo podatke. Pri popunjavanju te strukture proveravamo da li se program izvrsava na sistemu koji koristi Big Endian ili Little Endian pakovanje bajtova pa na osnovu toga odredimo kako da se trenutni podaci konvertuju u one koji nama odgovaraju. Na taj nacin dobijamo kod koji moze da se izvrsava sasvim lepo i na Windows i na Linux i na Mac operativnim sistemima. Kad popunimo SDL_PixelFormat strukturu, pozivamo SDL_ConvertSurface funkciju koja izvrsi konverziju. Sada imamo SDL_Surface objekat koji podatke drzi u formatu koji nam odgovara za kreiranje OpenGL teksture.

Da bi kreirali teksturu prvo moramo od OpenGL zatraziti da nam rezervise mesto za nju. To se vrsi pozivanjem funkcije glGenTextures. Kad dobijemo identifikator kreirane teksture, kazemo za sta nam ta tekstura treba. U ovom primeru pravimo obicnu 2D teksturu (postoje jos 1D, 3D i CUBE MAP teksture). Sledece sto radimo je definisanje filtera za prikazivanje teksture na povrsinama koje su manje ili vece od nje. Najbolji rezultati se dobijaju koriscenjem MIPMAPPING-a. To je sistem koji od jedne teksture napravi vise manjih (vece se ne prave) tako da se za manje povrsine koriste manje verzije teksture. Mi cemo u ovom primeru koristiti jedan od jednostavnijih nacina. GL_NEAREST je najbrzi filter, a ujedno i najlosiji. Postavljanje filtera se vrsi pozivanjem funkcije glTexParameteri. Za kraj ostaje jos samo da podatke iz SDL_Surface prebacimo u OpenGL teksturu pozivom funkcije glTexImage2D. Nakon toga imamo spremnu teksturu za iscrtavanje.

Ostale funkcije koje se nalaze u GLTexture klasi sluze za izbor trenutne teksture i njeno brsiaenj. Izbor teksture se vrsi funkcijom Bind koja jednostavno pozove glBindTexture i kaze da tekstura treba da se koristi za obicno 2D iscrtavanje. Brisanje teksture se vrsi pozivanjem funkcije DeleteTexture koja u sebi poziva glDeleteTextures.

Da bi iscrtavanje objekata sa teksturama radilo kako treba, mora se ukljuciti iscrtavanje tekstura. To se vrsi pozivom funkcije glEnable(GL_TEXTURE_2D). Iscrtavanje pravougaonika sa teksturom izgleda ovako:
glBegin(GL_QUADS);     glTexCoord2f(1.0f, 0.0f);     glVertex3f(4.97f, 2.56f, 0.0f);     glTexCoord2f(0.0f, 0.0f);     glVertex3f(-4.97f, 2.56f, 0.0f);     glTexCoord2f(0.0f, 1.0f);     glVertex3f(-4.97f, -2.56f, 0.0f);     glTexCoord2f(1.0f, 1.0f);     glVertex3f(4.97f, -2.56f, 0.0f); glEnd();
Kao sto vidite, pojavljuje se nova funkcija glTexCoord2f. Ona sluzi da se za sledeci vertex definise koordinata teksture. Parametri idu od 0 do 1 (dozvoljeno je da idu i vise od 1... onda se dobija efekat ponavljanja u zavisnosti od definisanih parametara). 0 oznacava pocetak, 1 kraj. U ovom primeru smo za svaki cosak pravougaonika definisali po jedan cosak u teksturi, i tako prikazali celu teksturu.

Ovo je najosnovnije koriscenje tekstura.

Ovde smo koristili jos jednu funkciju za rad sa matricama, glRotatef. Ona kao prvi parametar uzima ugao rotacije, a sa sledeca 3 se definise osa oko koje se rotacija vrsi. Sasvim jednostavno Smile

https://www.mycity.rs/must-login.png

Ko je trenutno na forumu
 

Ukupno su 806 korisnika na forumu :: 31 registrovanih, 6 sakrivenih i 769 gosta   ::   [ Administrator ] [ Supermoderator ] [ Moderator ] :: Detaljnije

Najviše korisnika na forumu ikad bilo je 3466 - dana 01 Jun 2021 17:07

Korisnici koji su trenutno na forumu:
Korisnici trenutno na forumu: 357magnum, bestguarder, BORUTUS, cenejac111, darkojbn, djboj, FileFinder, Georgius, hyla, janbo, krkalon, Kubovac, laki_bb, Lazarus, milanovic, Miškić, MrNo, Nemanja.M, oldtimer, operniki, raptorsi, Simon simonović, Singidunumac, Srle993, vathra, Vlad000, vladetije, vladulns, voja64, x9, zeo