Gå til innhold

Anbefalte innlegg

Dette er en kort guide for hvordan en bruker OpenGL (GL fra nå av).

Jeg skal forsøke å være så lite språkspesifikk som mulig, men alle kodeeksempler vil være skrevet i C.

 

GL er som kjent en grafikk-standard som er tilgjengelig på nærmest alle operativsystem verdt å nevne. Noen bruker også GL som rendering motor for GUI (Mac OS) fremfor å bruke vanlig 2D grafikk.

Hvordan GL fungerer internt eller lignende kan du slå opp på wikipedia, din late slask.

OpenGL wrappere finnes til de aller fleste språk, eksempelvis PyOpenGL til Python, Glorg (eller Tao) til .NET

Glorg.zip

 

Andre nyttige ressurser til C/C++:

DevIL - cross platform image library

nVidia OpenGL SDK 10

Andre nyttige ressurser:

Lumina - Cross platform GLSL IDE

 

 

Dersom du bruker Glorg, så skriver jeg kort hvordan en bruker OpenGL i Glorg ved å bruker OpenGL direkte:

Bruk Glorg.OpenGLDevice for vinduet ditt, og deretter legger du til denne linjen:

using GL = OpenGL.NativeOpenGL;

Dette gjør at GL spesifiserer den statiske klassen OpenGL.NativeOpenGL.

Denne delen av guiden gjelder ikke Glorg (eller andre biblioteker som utfører initialisering for deg) og du kan trygt hoppe over vindu-delen

 

Først og fremst må vi begynne med initialisering av GL. Jeg vil bare gå igjennom hvordan dette gjøres i Windows (WGL) da jeg ikke har vært borti dette i X (XGL)

 

Først og fremst må en endre litt på vinduet en vil tegne til før GL kan brukes, dette gjøres ved å endre pixel formatet ved å først lage en struktur som heter PIXELFORMATDESCRIPTOR i Windows Platform SDK som ser nogenlunde slik ut:

 

Klikk for å se/fjerne innholdet nedenfor
typedef struct tagPIXELFORMATDESCRIPTOR { // pfd   
 WORD  nSize; 
 WORD  nVersion; 
 DWORD dwFlags; 
 BYTE  iPixelType; 
 BYTE  cColorBits; 
 BYTE  cRedBits; 
 BYTE  cRedShift; 
 BYTE  cGreenBits; 
 BYTE  cGreenShift; 
 BYTE  cBlueBits; 
 BYTE  cBlueShift; 
 BYTE  cAlphaBits; 
 BYTE  cAlphaShift; 
 BYTE  cAccumBits; 
 BYTE  cAccumRedBits; 
 BYTE  cAccumGreenBits; 
 BYTE  cAccumBlueBits; 
 BYTE  cAccumAlphaBits; 
 BYTE  cDepthBits; 
 BYTE  cStencilBits; 
 BYTE  cAuxBuffers; 
 BYTE  iLayerType; 
 BYTE  bReserved; 
 DWORD dwLayerMask; 
 DWORD dwVisibleMask; 
 DWORD dwDamageMask; 
} PIXELFORMATDESCRIPTOR;

 

Det er mange felt i denne strukturen, men først begynner vi med å bare fylle den med null (ZeroMemory eller memset i C)

De fleste feltene er ikke lenger i bruk, og det er bare et lite fåtall som trenger å endres.

Først begynner vi å fylle inn nSize. Dette er simpelthen størrelsen på strukturen, og for de som bruker programmeringsspråk uten sizeof er denne 40 byte stor, så fyll inn enten 40 eller sizeof(PIXELFORMATDESCRIPTOR)

Neste blir å sette nVersion, som er rett og slett 1, og etter dette er det bare cDepthBits, cColorBits og dwFlags vi trenger å tenke på. cDepthBits er enten 16 eller 24 og cColorBits er 32 for de aller fleste tilfeller. dwFlags derimot er litt verre. Denne skal være PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER.

For de med dårlig tid er dette lik 0x25

 

Så for å oppsummere:

Klikk for å se/fjerne innholdet nedenfor
PIXELFORMATDESCRIPTOR pfd;

ZeroMemory(&pfd, sizeof(pfd));
pfd.nVersion = 1;
pfd.nSize = sizeof(pfd);
pfd.cDepthBits = 24;
pfd.cColorBits = 32;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; //0x25

 

Og vi skal ha en fullverdig struktur.

 

Nå må vi sette dette pixelformatet gjeldende for grafikkobjektet vårt. Merk at vi er kun interessert i HDC-en og ikke HWND-en til et vindu. Med andre ord: dersom du har en window handle (for eksempel [Form].Handle i .NET) så må vi først få tak i HDC-en til dette vinduet. I de fleste tilfeller kan en bare bruke GetDC([HWND])

 

Når vi nå har HDC-en til et vindu må vi velge et pixel-format som ligger nærmest den strukturen vi har laget, dette bruker vi ChoosePixelFormat til. Denne returnerer et integer vi må beholde til neste funksjon.

Deretter endrer vi pixel-formatet ved å bruke SetPixelFormat strukturen, og når dette er gjort, er vi klare for å lage GL konteksen med wglCreateContext som vi deretter gjør aktive med wglMakeCurrent.

 

For å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
HDC dc = [hdc-en til vinduet vårt];
int pixelformat;
HGLRC gl;

pixelformat = ChoosePixelFormat(dc, &pfd);

SetPixelFormat(dc, pixelformat, &pfd);

// Lag OpenGL konteksen
gl = wglCreateContext(dc);

// Gjør konteksen gjeldende
wglMakeCurrent(dc, gl);

 

Når vi er ferdig med GL vinduet og programmet skal avslutte, kaller vi wglMakeCurrent(0, 0) og wglDeleteContext(gl)

 

Nå er vinduet klart for GL kall :)

 

Det vi først må vite litt om, er matriser i GL. Vi har tre forskjellige matriser, men det er bare to av dem det er trolig at du noensinne vil bruke, men for referansens skyld er det:

- Modelview

- Projection

- Texture

 

Hvor du sannsynligvis aldri kommer til å bruke texture matrisen.

 

Ok, modelview er matrisen en bruker for å flytte objekter, projection brukes i de aller fleste programmer utelukkende til å manipulere perspektiv. Grunnen til at vi ikke kan bruke den til å flytte kamera har med tåkeberegninger å gjøre.

Så innse først som sist: kamera ligger alltid på (0, 0, 0) og hele verden blir snurret rundt kamera.

 

Vi starter med å lage en funksjon som skal sette opp viewport og skru på ortografisk projeksjon, dvs. vi starter uten perspektiv, siden det ikke er viktig i første omgang.

 

Så skriv en funksjon med et fornuftig navn, og i denne funksjonen bytter vi først til projection matrisen. Dette gjøres ved å kalle glMatrixMode med et passende parameter, som i dette tilfellet er GL_PROJECTION. Merk at det finnes noe som heter GL_PROJECTION_MATRIX også, men denne har en annen bruk (glGet funksjonene)

Dette gjør at vi bytter til projection matrisen som gjeldende matrise, og vi kan begynne med å endre den.

Først passer vi på å laste inn en identitetsmatrise ved å kalle glLoadIdentity, grunnen til dette er at alle endringer vil bli multiplisert inn i matrisen, og vi vil helst starte med blanke ark dersom denne funksjonen blir kalt flere ganger.

Tips:Det kan innimellom være en god idé å bruke glPushAttrib(GL_MATRIX_MODE) og avslutte funksjonen med glPopAttrib. Det dette gjør er at OpenGL husker hvilken matrise som var aktiv, og setter den tilbake til det den var. Dette er dog avhengig av hvordan programmet ditt er laget, og er ikke kritisk i dette tilfellet, så bare la det ligge i bakhodet.

Så med mindre du bruker glPushAttrib, så setter vi matrisen tilbake til MODELVIEW ved enden av funksjonen ved å kalle glMatrixMode(GL_MODELVIEW) på slutten av funksjonen, men først må vi sette opp litt saker og ting.

Vi lager en ortografisk matrise ved å bruke glOrtho funksjonen. Dette er en temmelig enkel funksjon, som rett og slett lar deg sette opp en boks, alt som er inni vil synes på skjermen (og siden det er en boks, så blir det heller ingen perspektiv)

La oss bare bruke en boks som går fra -1 til 1 i alle retninger.

glOrtho( -1, 1, -1, 1, -1, 1 )

 

 

Så for å oppsummere:

Klikk for å se/fjerne innholdet nedenfor
void SetupProjection()
{
glMatrixMode(GL_PROJECTION);

glLoadIdentity();	
glOrtho( -1, 1, -1, 1, -1, 1 )

glMatrixMode(GL_MODELVIEW);
}

 

Nå er vi klar til å rendre noe, dette gjør vi i en funksjon vi kaller Render

Vi starter med å lage noe såpass enkelt som en hvit firkant på en svart bakgrunn.

 

Det vi starter med, er å si til OpenGL at vi vil fylle med svart, og resette Z-bufferet, dette bruker vi glClear og glClearColor til.

glClearColor forteller hvilken farge vi vil bruke, og glClear utfører det.

glClearColor er selvforklarende (alle fargene er en verdi mellom 0 og 1) men glClear krever ett litt spesielt parameter. Dette parameteret forteller hvilke buffer vi vil slette.

Mulighetene er GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT og GL_STENCIL_BUFFER_BIT. Vi skal i første omgang bare slette color-buffer, så da blir det bare GL_COLOR_BUFFER_BIT

 

Nå har vi slettet backbufferet, og vi er klar til å tegne noe på det. For enkelhetens skyld starter vi med state funksjonene for å rendre i GL. Alle state tegninger er funksjoner som ligger innenfor glEnd() og glBegin()

glBegin krever et parameter som forteller hva vi egentlig vil tegne, og vi vil tegne GL_QUADS. i mellom glBegin(GL_QUADS) og glEnd() så skal vi sette inn fire punkter. Siden vi nå egentlig jobber i 2D, kan vi bruke glVertex2f funksjonen.

Når glEnd() er kalt, vil vi gjerne vise dette til brukeren. Det som må gjøres da, er å vente til GL er ferdig med å rendre, og flytte backbufferet til frontbufferet med SwapBuffers funksjonen fra Platform SDK.

 

Så for å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
void Render()
{
SetupProjection();		// For enkelhets skyld
glClearColor(1, 0, 0, 0);	// Alpha kanalen blir ignorert, men vi setter den allikevel til 1
glClear(GL_COLOR_BUFFER_BIT);

glBegin(GL_QUADS);
glVertex2f( .5f, .5f );
glVertex2f( -.5f, .5f );
glVertex2f( -.5f, -.5f );
glVertex2f( .5f, -.5f );
glEnd();

glFlush();
SwapBuffers(dc);
}

 

Nå skal det dukke opp en hvit firkant på en svart bakgrunn.

Merk at en sjeldent bruker glBegin/glEnd i vanlige programmer.

 

Nå er det på tide å animere litt. Til dette trenger du å gjøre slik at programmet kjører hele tiden. Eksempelvis kan du i C bruke PeekMessage fremfor GetMessage og kalle Render i Paint event.

Vær obs på at GL konteksen vi har laget er unik for tråden den ble laget i. Derfor kan du ikke kalle render fra en annen tråd å forvente at det fungerer. i .NET bruker du Invoke funksjonen for å få dette til.

 

Når dette er gjort, er den enkleste måten å få firkanten vår til å rotere at du bruker glRotate funksjonen. Det denne gjør, er å multiplisere den aktive matrisen (modelview) med en rotasjonsmatrise.

Så lenge du ikke kaller glLoadIdentity vil denne matrisen bli beholdt til hver gang funksjonen kaller, så for at den skal rotere konstant om Y aksen eksempelvis holder det å kalle

glRotate(1, 0, 1, 0), altså betyr dette én grad om Y aksen.

 

For å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
void Render()
{
SetupProjection();		// For enkelhets skyld
glClearColor(1, 0, 0, 0);	// Alpha kanalen blir ignorert, men vi setter den allikevel til 1
glClear(GL_COLOR_BUFFER_BIT);
glRotatef(1, 0, 1, 0);

glBegin(GL_QUADS);
glVertex2f( .5f, .5f );
glVertex2f( -.5f, .5f );
glVertex2f( -.5f, -.5f );
glVertex2f( .5f, -.5f );
glEnd();

glFlush();
SwapBuffers(dc);
}

 

Siden matrisen blir beholdt, og programmet kjører denne i en evig løkke, vil firkanten rotere rundt sin Y akse (den roterer horisontalt)

For å flytte objekter bruker en glTranslatef, og for å skalere bruker en glScalef.

Vær obs på glPushMatrix() og glPopMatrix som er en veldig nyttig funksjon for å bevare matrisene.

Disse brukes ved at dersom du skal endre en matrise, men vil beholde en kopi, bruker du glPushMatrix(). Denne legger en kopi av den nåværende matrisen på stack, og du kan fortsette å endre matrisen. For å hente tilbake den gamle matrisen kaller du glPopMatrix().

For eksempel kan dette gjøre at du vil rotere ett objekt etter en fast vinkel, mens et annet roterer hele tiden, så kaller du glPushMatrix() før det ene, og kaller glLoadIdentity for å laste identitetsmatrisen, tegner objektet, og henter den gamle matrisen.

 

For å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
float angle = 0;
void Render()
{
SetupProjection();		// For enkelhets skyld
glClearColor(1, 0, 0, 0);	// Alpha kanalen blir ignorert, men vi setter den allikevel til 1
glClear(GL_COLOR_BUFFER_BIT);
glRotatef(1, 0, 1, 0);

glBegin(GL_QUADS);
glVertex2f( .5f, .5f );
glVertex2f( -.5f, .5f );
glVertex2f( -.5f, -.5f );
glVertex2f( .5f, -.5f );
glEnd();

// Lag en kopi av matrisen
glPushMatrix();

// Last identitetsmatrisen
glLoadIdentity();
// Roter objektet
glRotatef(angle, 0, 1, 0);

// Tegn firkant
glBegin(GL_QUADS);
glVertex2f( .5f, .5f );
glVertex2f( -.5f, .5f );
glVertex2f( -.5f, -.5f );
glVertex2f( .5f, -.5f );
glEnd();

glPopMatrix();

glFlush();
SwapBuffers(dc);
}

Endret av GeirGrusom
  • Liker 1
Lenke til kommentar
Videoannonse
Annonse

Ah, spennende! Har alltid hatt lyst til å få til litt magi i OpenGL.

 

Orka ikke lese gjennom alt nå, men skal definitivt gjøre det en annen gang.

 

Synes det er interessant med programmering.

 

Tusen takk for en herlig innføring.

Endret av Kadmium
Lenke til kommentar

Bra innføring! Ikke for mye å lese ;)

Har konvertert koden til BlitzMax:

 

SuperStrict
Framework BRL.GLGraphics
Import BRL.Graphics
AppTitle = "GeirGrusom OpenGL innføring 1"
GLGraphics 640,480

Global angle:Float = 0
While Not KeyHit(KEY_ESCAPE)
Render()
Wend
End

Function SetupProjection()
glMatrixMode(GL_PROJECTION)
glLoadIdentity
glOrtho(-1,1,-1,1,-1,1)
glMatrixMode(GL_MODELVIEW)
End Function

Function Render()
SetupProjection
glClearColor(0,0,0,1) 'Alpha kanalen er sist i BMax
glClear(GL_COLOR_BUFFER_BIT)

'funksjonen glRotatef tar (vinkel,x,y,z)
glRotatef(1, 0, 1, 0) 'vinkel=1, x=0, y=1, z=0

'Tegn firkant
glBegin(GL_QUADS)
	glVertex2f( .5, .5)
	glVertex2f( -.5, .5)
	glVertex2f( -.5, -.5)
	glVertex2f( .5, -.5)
glEnd()

'Lag en kopi av matrisen
glPushMatrix()

'Last identitetsmatrisen
glLoadIdentity()
'Roter objektet
glRotatef(angle, 0,0,1) 'roter om Z

'Tegn firkant
glBegin(GL_QUADS)
	glVertex2f( .5, .5)
	glVertex2f( -.5, .5)
	glVertex2f( -.5, -.5)
	glVertex2f( .5, -.5)
glEnd()

glPopMatrix()

glFLush()
Flip 'swap buffers
angle:+1
End Function

 

Og resultatet ble:

post-49636-1230850255_thumb.png

 

Stemmer det at den ene firkanten skal rotere og den andre krympe/vokse i bredde? Isåfall, hvorfor krymper den?

Første glRotatef(1, 0, 1, 0) i Render()-funksjonen gjør vel dette? Hvordan?

Edit: Så dum jeg er. Den roterer seff. om y-aksen, og ortografisk visning får dette til å se unaturlig ut. Husker ikke hva jeg har lest:

 

Så lenge du ikke kaller glLoadIdentity vil denne matrisen bli beholdt til hver gang funksjonen kaller, så for at den skal rotere konstant om Y aksen eksempelvis holder det å kalle

glRotate(1, 0, 1, 0), altså betyr dette én grad om Y aksen.

Endret av JAPCU
Lenke til kommentar

På tide å lage et program som ser en smule bedre ut.

Vi vil bytte ut glBegin og glEnd med vertex arrays, legge til teksturer, og vise perspektiv.

 

Først begynner vi med det grunnleggende, vertex arrays. Måten dette gjøres på i OpenGL er at en viser til et array og beskriver dataene i dette arrayet.

Vi må bruke to forskjellige typer arrays, vertex array og texture coordinate array.

Vertex array inneholder vertex posisjon, og texture coordinate array inneholder tekstur-koordinater (duh)

 

Så først begynner vi med å definere en firkant som at array av fire punkter hvor hvert punkt inneholder 2 koordinater (vi fortsetter med en 2D beskrivelse foreløpig)

Det samme gjelder texture coordinate array, men merk her at (0, 0) er nederst i venstre hjørne, mens (1, 1) er øverst i høyre hjørne på teksturen.

 

For å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
typedef struct
{
float x;
float y;
}vertex2d;

vertex_array = { {.5f, .5f}, {-.5f, .5f }, {-.5f, -.5f }, {.5f, -.5f} };
tex_array = { {1, 1}, {0, 1}, {0, 0}, {1, 0} };

 

Når vi da har dette, er det på tide å se hvordan en forteller OpenGL at disse skal brukes som vertex buffers.

Først så binder vi hvert buffer til OpenGL ved å bruke funksjonene glVertexPointer og glTexCoordPointer. Disse funksjonene er mer eller mindre like hverandre, og første parameter er hvor mange dimensjoner arrayet er, i vårt tilfelle 2. Neste er hva slags datatype som fyller den, i våre er det GL_FLOAT.

Neste parameter er litt spesielt, og du vil helst sikker komme borti det senere. Dette forteller om det er noe data som skal hoppes over i vertex bufferetD dette brukes dersom du for eksempel har vertex og texture coordinate i samme array. Denne skal alltid være lik størrelsen av strukturen. 0 forteller at alle data ligger tettpakket.

Når dette er gjort, er de klar for å rendres.

Siden vi ikke har noen kompliserte former eller noe slikt, bruker vi en funksjon som heter glDrawArrays, denne tar 3 parameter, det første er hva slags former som buffrene former.

I vårt tilfelle er det GL_QUADS. Det andre er hvor formen starter å bli beskrevet i arrayet, i vårt tilfelle er det 0. Deretter er neste parameter hvor mange indexer som skal tegnes, 4 i vårt tilfelle.

 

Så for å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
glEnableClientState(GL_VERTEX_ARRAY);		// Fortell OpenGL at vi vil bruke vertex array
glEnableClientState(GL_TEXTURE_COORD_ARRAY);	// Fortell OpenGL at vi vil bruke texture coord array

glVertexPointer(2, GL_FLOAT, 0, vertex_array);
glTexCoordPointer(2, GL_FLOAT, 0, vertex_array);

glDrawArrays(GL_QUADS, 0, 4);

 

Nå vil den tegne firkanter på nytt, men ved å bruke vertex buffer istedet, mindre og mer fleksibel kode :)

 

Da er det på tide å skru på litt perspektiv. Det finnes en funksjon for dette i OpenGL som heter glFrustum, men denne er temmelig vanskelig i bruk, så vi bruker heller gluPerspective.

En frustum (for de som ikke vet det) er en kjegle som har avkappet tupp. glFrustum beskriver en slik viewport frustum, men vi er mer interessert i å kunne fortelle vinkelen, det kan vi i gluPerspective.

 

Så da tar vi for oss SetupViewPort funksjonen. Vi skal også legge til en liten sak ekstra som gjør at en kan endre størrelsen på vinduet.

Først bytter vi ut glOrtho funksjonen vår med gluPerspective. Dette er en temmelig enkel funksjon, første parameteret er vinkelen mellom topp og bunn på skjermen. Vanlig verdi for dette er mellom 45 og 80 grader. Neste parameter er aspektet, denne skal være lik width / height av viewport.

Neste parameterne beksriver synsrekkevidden. Her er det absolutt kritisk at near er større enn 0, far burde også være større en near. Prøv å hold avstanden mellom disse så liten som mulig, fordi ved lavere mellomrom (for eksempel 1 - 2) så får vi et mye mer presist Z-buffer.

I vårt program bruker vi .01 og 2

Deretter setter vi opp viewport med glViewport. Denne funksjonen forteller OpenGL hvor på vinduet den skal tegne. Merk at Y her går fra ned til opp i motsetning til andre grafikk API-er.

 

Ideelt blir som sagt denne funksjonen kalt når vinduet endrer størrelse.

 

Så for å oppsummere:

Klikk for å se/fjerne innholdet nedenfor
void SetupViewport()
{
glMatrixMode( GL.GL_PROJECTION );
glLoadIdentity();
gluPerspective( 60f, (float)width / (float)height, .01f, 2f );
glViewport( 0, 0, width, height );
glMatrixMode( GL_MODELVIEW );
}

 

 

Ok. Når vi starter programmet nå, vil vi kanskje ikke se så mye, kanskje at en firkant forsvinner og fukker opp litt. Det vi gjør for å fikse dette, er å flytte firkanten litt ut i bildet.

Dette gjøres enkelt med glTranslate funksjonen, men merk at vi må enten kalle glLoadIdentity eller glPushMatrix fordi vi ikke vil at firkanten skal forsvinne ut i horisonten.

 

Men ihvertfall før firkanten skal rendres kaller vi glTranslatef(0, 0, -1f); ideelt med en glLoadIdentity først.

 

Nå skal firkanten være midt på skjermen, bruk gjerne en variabel for å rotere firkanten rundt Y-aksen.

 

Nå er det på tide å få til litt teksturer. Dette bruker en glTexImage2D funksjonen til.

Helt teoretisk sett kan en kalle glTexImage2D uten å gjøre noenting først, men en god idé er å få lagt teksturen på skjermkortminnet. Dette gjøres dersom vi bruker glGenTextures og glBindTexture.

glGenTexture holder av en plass som kan brukes til å beksrive en tekstur, og glBindTexture gjør denne teksturen gjeldende.

 

Så de vi først gjør, er å holde av en plass til vår tekstur med et glGenTextures kall, og deretter gjøre det gjeldende med glBindTexture. Når dette er gjort, kaller vi glTexImage2D, og deretter setter noen parameter for teksturen med glTexEnvi og glTexParameteri funksjonene.

glTexImage2D er en funksjon som kan se skummel ut, men den er ikke egentlig det. Det første parameteret må være GL_TEXTURE_2D. Neste forteller hvilket mip-map level denne teksturen er for, og siden dette er originalteksturen skal den stå på null. neste er internalformat, denne forteller om datastrukturen, og dersom detter er et 32-bit RGBA skal den stå til GL_RGBA8 (eller bare GL_RGBA)

Neste parameterne er størrelsen i pixler. Deretter kommer det en som heter border, denne kan være 0 eller 1, vi setter den til 0. Neste forteller om hva teskturen beskriver, og for oss er det RGBA bildet (noen ganger bruker vi GL_BGRA_EXT her i Windows, det merker du fort hvis fargene er feil) og da skal den være satt til GL_RGBA.

Neste forteller GL hva slags datatype hver kanal er, og i de aller fleste tilfeller skal denne stå til GL_UNSIGNED_BYTE. Neste er pekeren til arrayet.

 

Når dette er kalt skal vi sette noen parameter, som for eksempel for å fortelle GL at teksturkoordinater utenfor teksturens område skal få teksturen til å gjenta seg, og deretter si at vi vil bruke LINEAR som interpoleringsalgoritme.

 

For å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
glEnable( GL.GL_TEXTURE_2D );

int texture = 0;
glGenTextures(1, &texture);

glBindTexture( GL_TEXTURE_2D, m_tex );
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, img_width, img_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data );

 

Denne kalles ideelt kun én gang, og dataene som er lastet inn i minnet kan trygt frigjøres.

 

Da skal det dukke opp en tekstur på firkanten :)

 

Hvis en vil ha mipmapping finnes det faktisk temmelig mange måter å få til dette på, det er lagt inn opp til flere extensions for det i GL, og glu har en funksjon som heter gluBuildMipMaps2D.

gluBuildMipmaps2D erstatter du bare med glTexImage2D, og deretter bytter du GL_TEXTURE_MAG_FILTER med GL_LINEAR_MIPMAP_LINEAR eller lignende.

En må også merke seg at dersom du skal tegner 32-bit bilder med alpha kanal er det en fordel om du har skrudd på GL_BLEND og GL_ALPHA_TEST med glEnable.

 

Litt Glorg spesifikk informasjon:

En kan bruker Texture eller OpenGLDevice.LoadTexture i Glorg, men dersom du vil lære deg hele fremgangsmåten, så laster du en Bitmap klasse med et bilde, og bruker LockBits på dette, og deretter Scan0 fra BitmapData som peker til datakilden.

For vertex buffer bruker du fixed funksjonen i C# for å hente pekeren til et array, eksempelvis

Klikk for å se/fjerne innholdet nedenfor
fixed(vertex2d* vb_ptr = vertex_array)
{
fixed(vertex2d* tb_ptr = tex_array)
{
	GL.glVertexPointer( 2, GL.GL_FLOAT, 0, vb_ptr );
	GL.glTexCoordPointer( 2, GL.GL_FLOAT, 0, tb_ptr );
	GL.glDrawArrays( GL.GL_QUADS, 0, 4 );
}
}

Eventuelt GCHandle, men fixed er mindre kode, enklere i bruk og tryggere.

post-31659-1230886495_thumb.jpg

Sånn skal resultatet bli.

 

Edit: mikset litt for mye Glorg kode inn. Byttet ut med C

Endret av GeirGrusom
Lenke til kommentar
  • 3 uker senere...

EDIT: Det har kommet meg for øyet at denne er helt gal, jeg har gjort en temmelig alvorlig feil, som jeg skal rette opp i morgen.

Klikk for å se/fjerne innholdet nedenfor
Jeg har byttet om på cosinus og sinus, samt at indeksene er feil. Rettes opp i morgen.

Jeg stusset over hvorfor det ble riktig med 2PI for vertikal vertex, men jeg regnet med at jeg hadde tenkt feil. Det viste seg at jeg hadde tenkt riktig i utgangspunktet, men på grunn av at jeg har blingset med cosinus og sinus, så så det riktig ut forsåvidt, men det var feil.

 

Normaler og geometri-oppbygning

 

Nå som vi har teksturer og en enkel modell, er det på tide å bygge en litt mer komplisert modell.

 

Først skal vi kikke på hvordan vi kan modellere en kule. Til dette må vi introdusere en ny funksjon, som kalles index buffer.

Indexbuffer er rett og slett en array av integer som forteller hvilke vertexer som danner en viss primitiv figur (triangel i vårt tilfelle)

Så siden det er tre vertexer i en triangel, vi det si at det kreves tre indexer for å danne en triangel. Neste tre indekser, danner neste triangel.

Det er dermed fint lov å bruke en struktur som danner tre vertexer og kalle den for Triangle om det er ønskelig, men jeg kommer ikke til å gjøre det i denne tutorialen.

Videre må vi kikke på hva normaler er, og hva de brukes til. Normaler forteller bare i hvilken retning hver vertex peker. På en kule vil alle normaler peke utifra origo.

Disse hjelper til med skyggelegging av objekter. Så nå vet vi hva teksturkoordinater er, og hva normaler er, da begynner vi med å definere en funksjon som heter CreateSphere.

Denne skal ta en rekke verdier, og også ha noen utverdier. Siden jeg antar at vi bruker C, og ikke C++ vil vi bruke pointer to pointer for enkelhets skyld.

Funksjonen må definere en radius på kulen, hvor mange segmenter, ut-parameter for vertex, texture, normal og index-buffer.

Det er også mulig å ha vertex, teksturkoordinater og normaler i samme array, men for enkelhetsskyld sløyfer vi det denne gangen.

For å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
void CreateSphere(
float radius,
int segments,
vertex3d** vertices,
vertex3d** normals,
vertex2d** texcoords,
int** indices,
int* vertex_count,
int* index_count)
{

}

 

Nå legger vi også merke til at vi bruker et ekstra array format, dette må bli skrudd på med glEnableClientState(GL_NORMAL_ARRAY)

Normaler kan kun være 3- floats, det finnes ingen andre støttede normal typer.

 

Ok, men da er det på tide å danne kulen.

 

Litt relativt enkel matematikk:

Husk at datamaskinen jobber med radianer, og ikke grader, så en full sirkel er 2PI og ikke 360°

Det første vi må markere oss, er at kulen går i en full sirkel rundt (duh) men den går bare i en halvsirkel fra topp til bunn... eller den gjør ihvertfall det i dette eksempelet, det spiller ingen rolle om du snur den rundt av grunner ukjente for meg.

OK, så den snurrer 2PI rundt Y aksen, og bare PI rundt X aksen (og Z aksen for den saks skyld) Vi må også merke oss at alle sidene vil være firkanter, med to unntak: de som er nærmest toppen og de som er nærmest bunnen, disse vil være triangler.

Dette betyr at vi må skrive et unntak fra disse når vi itererer fra topp til bunnen i index-bufferet, og vi trenger bare lage ett vertex for topp, og ett for bunnen, sånn at vi ikke lager ekstra unødvendig vertexer for hver iterasjon.

 

OK, vi begynner med å regne ut hvor mange grader det vil gå per XZ akse iterasjon (den vertikale), dette er lik PI / antall segmenter. For den som går om Y aksen (horisontal) er det 2PI / (antall segmenter)

Deretter må vi regne ut hvor mange vertices dette blir totalt. Dette er såpass enkelt som at vi vet hvor mange vertexer det blir per side (segmenter), trekker fra to (topp og bunn vertex), multipliserer det med segmenter og legger til to (topp og bunn vertex).

Da kan vi begynne å danne vertex buffrene våre.

 

Vi begynner ved å danne topp og bunn vertex, men for å gjøre vertex genereringen vår enklere, velger vi å la disse ligge på slutten av vertex bufferet.

Hvis du jobber i C, kan det være en fordel å lage funksjoner for å danne vertex3d og vertex2d, siden vi kommer til å gjøre dette en del. Vi antar at jeg har gjort det, og kalt dem CreateVert2D og CreateVert3D.

 

Nå må vi bare iterere horisontalt først, og deretter vertikalt innenfor denne. Vi må huske å telle opp hvor mange grader vi er på både for horisontal og vertikal ved å addere de vinklene vi regnet ut tidligere.

 

La oss kalle disse iterasjonene x(horisontal) og y(vertikal)

Det vi må gjøre, er å finne ut av hvordan disse vertexen skal plasseres i forhold til origo (0, 0, 0)

Problemet du fort vil møte på, er hvordan skal du kunne legge sammen alle disse tre aksene samtidig på en vertex?

Jeg laget en arbeidstegning for dette, som gjør at en kan regne ut enkelt hvordan det blir:

post-31659-1232638228_thumb.png

 

For å oppsummere:

Klikk for å se/fjerne innholdet nedenfor
  int vert_count = (segments - 2) * segments + 2;
 float angle_inc_vertical = (float)(PI / segments);
 float angle_inc_horizontal = (float)(2 * PI / segments);

 int x, y, i = 0;
 float fx, fy, fz;

 float angle_x = 0f;
 float angle_y;

 vertex3d *verts = malloc(sizeof(vertex3d) * vert_count);
 vertex3d *norms = malloc(sizeof(vertex3d) * vert_count);
 vertex2d *texcs = malloc(sizeof(vertex2d) * vert_count);


 for(x = 0; x < segments; x++)
 {
angle_y = angle_inc_vertical; // Vi hopper over topp vertexen

for(y = 1; y < segments - 1; y++) // Vi hopper over bunn vertexen også
{
  fx = (float)(sin( angle_x ) * sin( angle_y ));
  fy = (float)(cos( angle_y ));
  fz = (float)(cos( angle_x ) * sin( angle_y ));
  // Vi har nå effektivt kalkulert normalene til hvert punkt
  norms[i] = CreateVert3D(fx, fy, fz);
  // Multipliser normalene med radius, og vi har vertexene våre
  verts[i] = CreateVert3D(fx * radius, fy * radius, fz * radius);	  

  texcs[i] = CreateVert2D((float)(angle_x / 2 * PI), (float)(angle_y / PI));
}
 }

 

OK, da kommer den delen som kan trenge litt mer tankearbeid: index data.

 

Her må en først tenke seg hvordan vi har satt opp vertex data. Først og fremst er dette nesten et rutenett, vi har en X akse, og en Y akse. Vertexene går nedover hver Y akse, og deretter går den bortover X aksen.

 

Sæ vi må ha to løkker, en for X aksen ytterst, og en for Y aksen innerst.

Hver X akse begynner først med triangelen som går mellom hvert pol-punkt. Da må vi tenke oss at det er (segments - 2) vertexer for hver kolonne, så vi lage en egen #define eller variabel som holder denne, da vi kommer til å bruke det mye.

Hvordan disse pol-trianglene dannes er likt som andre polygoner, men ett punkt går til de vertexene du valgte å være pol-punkter, så jeg går rett på det vanlige indeks problemet.

Hvert andre triangel vil se noe slikt ut:

x * (segments - 2)
x * (segments - 2) + 1
(x + 1) * (segments - 2)

 

Men da møter vi på et problem, hva skjer når x er helt på slutten av iterasjonen? da vil denne havne utenfor index bufferet.

Løsningen vi bruker er enkel, vi deler på segments og henter resten, dette vil alltid være innenfor segments.

Så koden blir noe slik:

x * (segments - 2)
X * (segments - 2) + 1
((x + 1) % segments) * (segments - 2)

 

Da vil alltid indeksen havne innenfor segments.

 

 

Så for å oppsummere:

 

Klikk for å se/fjerne innholdet nedenfor
int segs = segments - 2;

for ( x = 0; x < segments; x++ )
{
// Nord pol
indices[i++] = vertex_count - 1;
indices[i++] = ((x + 1) % segments ) * segs;
indices[i++] = x * segs;
for ( y = 0; y < segs; y++ )
{
	indices[i++] = x * segs + y;
	indices[i++] = ( ( x + 1 ) % segments ) * segs + y;
	indices[i++] = x * segs + y + 1;

	indices[i++] = ( ( x + 1 ) % segments ) * segs + y;
	indices[i++] = ( ( x + 1 ) % segments ) * segs + y + 1;
	indices[i++] = x * segs + y + 1;							

}  
// Sør pol
indices[i++] = vertex_count - 2;
indices[i++] = (segs - 1 ) + x * segs;
indices[i++] = (segs - 1 ) + ((x + 1) % segments ) * segs;

}

 

For å tegne denne, må vi bruker glDrawElements

glDrawElements( GL_TRIANGLES, index_count, GL_UNSIGNED_INT, indices );

 

Gratulerer! du har nå dannet en kule ved ren programmering.

 

Hvis du skrur på lys

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

 

Skal det se nogenlunde slik ut når du starter programmet:

post-31659-1232635872_thumb.png

Endret av GeirGrusom
Lenke til kommentar
  • 4 måneder senere...

Har konvertert koden din til evaldraw. Et bilde sier mer enn 1000 ord:

post-49636-1245185343_thumb.png

 

evaldraw kan lastes ned her: http://advsys.net/ken/download.htm

 

 

Koden er ikke riktig. Topp og bunn verteksene mangler.

 

Hakke studert matten enda... men regner med at du ikke fikset cos/sin ombyttingen? Får vel kikke på det selv så lærer jeg kanskje noe :p

 

enum{maxverts=1000, maxindices=2000};
static vertsx[maxverts];
static vertsy[maxverts];
static vertsz[maxverts];

static normsx[maxverts];
static normsy[maxverts];
static normsz[maxverts];

static texcsx[maxverts];
static texcsy[maxverts];

static indices[maxindices];

static index_count = 0;
static vert_count = 0;
() 
{
  if(numframes==0)
  {
     CreateSphere(1,16);
  }


  clz(1e32); 
  cls(0); 
  setcol(0xffffff);
  t = klock(); 
  r = 2.5;

  setcam(cos(t)*cos(t)*r,-sin(t)*r,sin(t)*cos(t)*r,-t-PI/2,t);
  //setcam(0,0,-r, 0,0 );

  //setcol(511,0,0); // 0-511 for 3D
  //setcol(511,511,511);
  glCullFace(GL_NONE );
  //sphere()
  glDrawElements();

  setcol(255,255,0)
  printf("\nindex_count=%g",index_count);
  printf("\nvert_count=%g",vert_count);

  //for(i=0; i<35; i++)
  //   printf("\n[%g]=%g",i,vertsy[i]);
}

sphere()
{
  nu = 16; du = PI*2/nu;
  nv = 16; dv = PI/nv;

  for(v=PI,iv=0;iv<=nv;iv++,v+=dv)
  {
     glBegin(GL_TRIANGLE_STRIP);
     for(u=0,iu=0;iu<=nu;iu++,u+=du)
     {
        glTexCoord(u*.5/PI,v/PI);
        glVertex(sin(v)*cos(u),cos(v),sin(v)*sin(u));
        glTexCoord(u*.5/PI,(v-dv)/PI);
        glVertex(sin(v-dv)*cos(u),cos(v-dv),sin(v-dv)*sin(u));  
     }
     glEnd();
  }
}

// out data, pointers in C
// vertices, normals, texcoords, indices, vertex_count, index_count

CreateSphere(radius, segments)
{
 vert_count = (segments - 2) * segments + 2;
 angle_inc_vertical = (1*PI / segments);
 angle_inc_horizontal = (2*PI / segments);

 x=0, y=0, i=0;
 fx=0, fy=0, fz=0;

 angle_x = 0;
 angle_y = 0;

 //vertex3d *verts = malloc(sizeof(vertex3d) * vert_count);
 //vertex3d *norms = malloc(sizeof(vertex3d) * vert_count);
 //vertex2d *texcs = malloc(sizeof(vertex2d) * vert_count);

 for(x =0; x < segments; x++)
 {
   angle_y = angle_inc_vertical; // Vi hopper over topp vertexen

   for(y = 1; y < segments-1; y++) // Vi hopper over bunn vertexen ogsaa
   {
     fx = (sin( angle_x ) * sin( angle_y ));
     fy = (cos( angle_y ));
     fz = (cos( angle_x ) * sin( angle_y ));

     // Vi har naa effektivt kalkulert normalene til hvert punkt
     normsx[i] = fx; normsy[i] = fy; normsz[i] = fz;
     // Multipliser normalene med radius, og vi har vertexene vaare
     vertsx[i] = fx * radius; vertsy[i] = fy * radius; vertsz[i] =fz * radius;      

     //texcs[i] = CreateVert2D((float)(angle_x / 2 * PI), (float)(angle_y / PI));
     texcsx[i] = angle_x/(2*PI); texcsy[i] = -angle_y/(1*PI);
     i++;
     angle_y += angle_inc_vertical;
   }
   angle_x += angle_inc_horizontal;
 }

 index_count = i;


 ////// Finn verteksenes indexser
  segs = segments - 2;
  i=0;
  for ( x = 0; x < segments; x++ )
  {
      // Nord pol
      indices[i] = vert_count - 1; i++;
      indices[i] = ((x + 1) % segments ) * segs; i++;
      indices[i] = x * segs; i++;
      for ( y = 0; y < segs; y++ )
      {
          indices[i] = x * segs + y; i++;
          indices[i] = ( ( x + 1 ) % segments ) * segs + y; i++;
          indices[i] = x * segs + y + 1; i++;

          indices[i] = ( ( x + 1 ) % segments ) * segs + y; i++;
          indices[i] = ( ( x + 1 ) % segments ) * segs + y + 1; i++;
          indices[i] = x * segs + y + 1; i++;                          

      }  
      // Soer pol
      indices[i] = vert_count - 2; i++;
      indices[i] = (segs - 1 ) + x * segs; i++;
      indices[i] = (segs - 1 ) + ((x + 1) % segments ) * segs; i++;

  }
  index_count = i;
}

//typeOfPrimitive, index_count, dataTypeIndexes, indices
glDrawElements()
{
  glBegin(GL_TRIANGLES) //POINTS
  for(i=0; i<index_count; i++)
  { 
     vert = indices[i];
     glTexCoord(texcsx[vert], texcsy[vert]);
     glVertex( vertsx[vert], vertsy[vert], vertsz[vert]);

  }
  glEnd();
}

Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...