Gå til innhold

Kort introduksjon OpenGL 3 og nyere


Anbefalte innlegg

OpenGL 3 introduksjon

 

OBS: Kildekode for C vil bli lagt ut senere, ettersom OpenGL 3.2 ikke fungerer på laptopen jeg bruker (Asus Eee 901)

 

Denne guiden vil introdusere OpenGL 3 og de nye funksjonene, og hva som ikke lenger skal brukes. Hovedsaklig skal den bare være som en grunnleggende innføring i OpenGL ettersom de fleste tutorials du finner er for OpenGL 1 og 2.

 

OpenGL 3 har introdusert deprecation model, og det er ingen grunn til å bruke foreldede funksjoner til tross for at mange skjermkortprodusenter har sagt de vil fortsette å støtte disse. OpenGL 3 prøver mer eller mindre å fjerne hele fixed function modellen. Med dette forsvinner altså matrisefunksjonene, glBegin/glEnd og alle tilhørende funksjoner, Display Lists og hele lys-opplegget (glLight funksjonene) Alt i alt er det nå langt færre OpenGL funksjoner du trenger å tenke på, men langt mer du må implementere på egenhånd.

 

Du kan finne OpenGL 3 og 4 headeren på OpenGL.org som har fjernet alle funksjoner som ikke er en del av OpenGL 3.0. eller du kan bruke glew.

 

Initialisering (wgl)

 

Initialisering av OpenGL er blitt mer komplisert, skal du få med deg alt, kan det hende du må lage tre forskjellige OpenGL context, og dersom du skal ha en egen tråd for å laste ressurser, må kanskje dette gjøres dobbelt opp. Jeg skal kun ta for meg OpenGL 3 og ikke ta med multisampling ettersom dette fører til et unødvendig ekstra ledd som du heller får lese på egenhånd.

 

Grunnen til at contextene må lages flere ganger, er fordi du ikke får tilgang til wgl eller gl extensions uten et context (wglGetProcAddress returnerer null uansett dersom ingen kontekster er aktive)

Så derfor må du først lage et OpenGL 1.x/2.x kontekst på samme måte som tidligere. Data som sendes inn er ikke så viktig, bare pass på å sett et format som er støttet av hardware.

 

Dette gjøres ved å fylle inn en PIXELFORMATDESCRIPTOR struktur, utfør en zeromemory eller tilsvarende funksjon for å sette alle felt til null. Deretter settes nVersion = 1, dwFlags til PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW. Sett dwColorBits = 32, og dwDepthBits = 16 (kompaitibilitetsgrunner)

Resten skal ikke spille stor rolle, ettersom vi vil fjerne dette kontekstet senere etter OpenGL 3 er initialisert.

Velg ChoosePixelFormat for å finne det nærmeste pixel formatet som støtter det vi er ute etter. Bruk SetPixelFormat på vinduet med verdiene vi har funnet frem til.

 

Deretter kan vi initialisere OpenGL med wglCreateContext, og wglMakeCurrent. Når vi har gjort dette, må vi hente noen funksjonspekere. Dersom du vil ha multisampling, er dette stedet å hente wglChoosePixelFormat pekeren og utføre det.

 

Nå er vi imidlertidig interessert i wglCreateContextAttribsARB. Disse kan slåes opp enten med glew eller fra wglext.h (www.opengl.org) Hvis wglGetProcAddress("wglCreateContextAttribsARB") returnerer null er ikke OpenGL 3 støttet.

Denne funksjonen tar noen litt snedige parameter, første er et nullterminert array av integer. Dette arrayet setter flagg for OpenGL 3, og vi vil være interessert i OpenGL 3.2 (nyeste nVidia drivere for 8-serie og oppover støtter denne) men vi vil være interessert i å ha det foreward compatibal, og core profile, dette for å fjerne støtte for deprecated funksjoner, og kun bruke OpenGL 3 funksjoner.

Det som er her, er at noen felt vil ha to verdier etterhverandre, den første for å si hvilken egenskap vi vil sette, og andre er verdien. Arrayet er null terminert.

int attribs[] = 
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 2, 
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
WGL_CONTEXT_PROFILE_MASK_ARB, 	WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 
0
};

Deretter kaller du wglCreateContextAttribsARB med verdiene vi har. Denne skal returnere et OpenGL kontekst dersom den ikke feilet. Feilet den, er kanskje OpenGL 3.2 ikke støttet, oppgrader skjermkortdrivere.

Nå som du har den nye konteksten, kan du slette den gamle, og gjøre den nye current.

Merk at fremgangsmåten i glx er temmelig lik som i wgl.

 

Eventuelt kan du bruke glut eller lignende biblioteker for dette, det kan spare deg for mye bannskap.

 

Shader

 

Ettersom fixed function pipeline er "fjernet" i OpenGL 3, må vi starte med å lage en shader som vi kan bruke senere. En shader er i bunn og grunn et progam som kjøres på skjermkortet, for hvert pixel (Pixel shader) for hver primitiv (geometry shader og tesselation shader i GL 4) og for hver pixel (fragment shader) Et shader program består av to eller tre shadere, en vertex shader, en fragment shader og eventuelt en geometry shader og tesselation shader. Jeg vil bare gjennomgå vertex shader og fragment shader, siden de andre ikke er like viktige i starten.

Før vi gjør noe som helst, må vi lage en enkel vertex shader og en enkelt fragment shader.

 

vertex shader:

#version 140
uniform mat4 pmv; // Projection * world * view

in vec4 in_position;
in vec3 in_normal;

out vec3 normal;

void main()
{
 gl_Position = in_position * pmv;
 normal = in_normal;  
}

 

fragment shader:

#version 140

in vec4 normal;

out vec4 frag_color;

void main()
{
 frag_color = vec4(normal, 1);
}

 

Først lager vi shader objektene våre, en for vertex shaderen, og en for fragment shaderen. Dette gjør vi med to kall til glCreateShader som tar ett parameter, som kan være GL_VERTEX_SHADER, GL_FRAGMENT_SHADER eller GL_GEOMETRY_SHADER.

Deretter må vi gi koden vi har for shaderne til hvert objekt. Til dette brukes glShaderSource. Denne er litt rar, fordi du kan gi flere tekstkilder. Siden vi bare har 1 kilde, er første parameter 1, neste er en peker til en peker til teksten vår. Siste er en peker til 1 integer som er lengden på shaderen.

 

Når dette er gjort, kan vi kompilere med glCompileShader. Når dette er gjort, må vi bruke glGetShaderiv med GL_COMPILE_STATUS for å se om kompileringen var fullført, altså GL_TRUE. Dersom den ikke var det, kan vi bruke glGetShaderInfoLog for å hente compile loggen.

Når begge shaderne har kompilert fullstendig, må vi lenke dem. Dette gjøres ved å danne et "program" med glCreateProgram. Når dette er gjort, må shaderne lenkes til programmet med glAttachShader. når dette er gjort, kan vi kalle glLinkProgram. Fullføres denne, så vil glGetProgramiv for GL_LINK_STATUS returnere GL_TRUE, og vi må hente loggen med glGetProgramInfoLog. Når alt har fullført, kan vi kalle glUseProgram for å sette programmet som er kompilert og lenket som det gjeldende. Merk at en kan lenke samme shader til flere programmer, og du kan kalle glAttachShader før shaderen er kompilert. Men det er ikke før programmet er lenket at glGetUniformLocation og glGetAttribLocation fungerer, som vil bli svært viktig senere. Kildene mine sier at en må på noen drivere kalle glUseProgram før disse funksjonene kan brukes også, så gjør det i tilfelle.

 

Vertex Buffer Object

 

Dette er nå et viktig poeng, vi har ikke lenger noen glBegin/glEnd, glVertexPointer, glNormalPointer, glTexCoordPointer etc. så vi må bruke Vertex Buffer Object og glAttribPointer istedet. Dette kan virke litt slitsomt, men det er overkommelig. Idéen er ganske enkel, vi kan, og må nå definere hva vertex posisjonene er, med dette så unngår vi flere forskjellige funksjoner, og det er opp til programmereren av shadere og selve programmet hva dataene skal brukes til. Først må vi derimot kunne lage et Vertex Buffer Object som skal lagre vertex data på skjermkortet. I teorien er dette ganske enkelt, vi bruker glGenBuffers for å kunne lage nye Vertex buffer (eller index buffer, mer om det senere) som vi kan jobbe med. Skaff deg noe data å vise, i begynnelsen kan dette være noe enkelt som for eksempel en firkant. Ta med normaler, teksturer og posisjoner, for eksempel slik:

struct MyVertex
{
 float x, y, z;
 float tx, ty;
 float nx, ny, nz;
};
MyVertex vb[4] = { ... };

Når vertex buffer objektet er dannet, kan vi fylle det med data. Dette gjøres temmelig enkelt. Bruk glBindBuffer for å gjøre bufferet vi har laget aktivt med parameter target satt til GL_ARRAY_BUFFER_ARB. Deretter kaller vi glBufferData. Her tror jeg de fleste parameterne er selvforklarende, men usage er litt spesiell. I utgangspunktet er usage kun et hint for implementasjonen. I de aller fleste tilfeller er vi interessert i GL_STATIC_DRAW ettersom de fleste animasjoner vil bli gjort av en vertex shader. Etter at glBufferData er blitt gjort, så kan klient arrayet slettes om ønskelig, med mindre data skal oppdateres senere. Dette kan gjøres med glBufferSubData eller med glMapBuffer, men disse vil ikke jeg bruke noe tid på å forklare ettersom det ikke er noe som brukes like ofte, og er noe som kan undersøkes på egenhånd.

 

Deretter vil vi lage et Index buffer, dette gjøres nesten på samme måte som Vertex Bufferet, men target parameteret settes til GL_ELEMENT_ARRAY_BUFFER_ARB. Hvorfor det egentlig er noen forskjell her vites ikke. Men utover det så brukes det på samme måte.

Når vi har et index buffer og et vertex buffer, kan vi gjøre den første delen som kreves for å tegne. Det vi må gjøre, er å finne frem til hva variabelnavnene til vertex, normaler etc. er i shaderen som er i bruk. Når vi har en lenket og aktiv shader, kan vi bruke glGetAtrtribLocation for å hente posisjonen til en attribute i koden, eksempelvis in_position (se vertex shader koden)

Når du har fått en location (som er ulik -1) så kan vi bruke den i glEnableAttribClientArray(location) og glAttribPointer.

Pass på å kalle glBindBuffer med vertex bufferet før du kalle glAttribPointer.

Du må også hente frem uniform lokasjon for matrisen pmv, til dette brukes glGetUniformLocation og glUniformMatrix4fv.

glAttribPointer er litt spesiell. Den skal ha en peker på slutten, men dersom et VBO er aktiv, så kan denne settes til NULL, eller til posisjonen til første element. stride kan alltid være størrelsen til datastrukturen du bruker. Jeg tror ikke det er noe poeng i å sette denne til null, men dette gjøres dersom du eksempelvis kun har vertex posisjoner i arrayet ditt.

Nå må vi sette VBO-en til index bufferet aktivt med glBindBufferObject hvor target er lik GL_ELEMENT_ARRAY_BUFFER_ARB.

glBindFragDataLocationEXT må brukes for å spesifisere at frag_out er det som skal være output fra fragment shaderen.

Nå kan du kalle glDrawElements(GL_TRIANGLE, index_count, GL_UNSIGNED_INT, NULL); Dette vil tegne hele index bufferet med utgagspunkt i vertex bufferet definert med glAttribPointer.

Da skal objektet tegnes med shaderen som er i bruk. Nå må du unbinde GL_ELEMENT_ARRAY_BUFFER_ARB (glBindBUffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); glBindBuffer(GL_ARRAY_BUFFER_ARB, 0)) og deretter skru av alle attribs med glDisableAttribClientArray.

 

Det finnes en rekke andre funksjoner i OpenGL 3.0, men dette er de viktigste, og er noe du kommer svært langt med. Andre ting som er verdt å se på, er Render targets, og selvsagt teksturfunksjonene. Men disse beskrives godt andre steder.

Lenke til kommentar
  • 7 måneder senere...
Videoannonse
Annonse

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...