Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

Moderne OpenGL 02 – Teksture

U ovom članku bavićemo se dodavanjem tekstura trouglu. Proces će obuhvatati dodavanje novih promenljivih (variables), vertex i fragment šejderima (shaders), kreiranje i upotrebu teksturnih objekata, i učenje o jedinicama i koordinatama koje se koriste za rad sa teksturama.

Kao i u svakom dosadašnjem članku priložićemo malu biblioteku kodova u tdogl imenskom prostoru (namespace).U ovom članku predstavićemo dve nove klase:tdogl::Bitmap i tdogl::Texture. Te nove klase će nam omogućiti da učitamo sliku iz "jpg", "png", ili "bmp" fajla u video memoriju, koju koristimo za šejdere. Takođe tdogl::Program klasa ima nove načine za podešavanje promenljivih šejdera.

Pristupanje kodu

Peruzmite sve lekcije kao "zip" datoteku sa adrese: https://github.com/tomdalling/opengl-series/archive/master.zip

Instrukcije za početna podešavanja su dostupne u prvom članku: Prvi koraci u "Xcode", "Visual C++" i Linux-u(Getting Started in Xcode, Visual C++, and Linux).

Ovaj članak se nadovezuje na kod iz prethodnog članka.

Kod koji se koristi u ovoj seriji članaka je dostupan na "github"-u: https://github.com/tomdalling/opengl-series . Možete preuzeti zip datoteku sa svim fajlovima sa te web strane, ili možete klonirati spremište ukoliko ste upoznati sa "git"-om. Kod koji je korišćen u ovom članku možete naći u windows/02_textures, osx/02_textures, and linux/02_textures direktorijumima.

Uniformne ili Atribut šejder promenljive

Sve promenljive koje smo koristili u prethodnim člancima bile su aatribut promenljive. U ovom članku pretstavićemo vam drugu vrstu promenljivih, a to su uniformne promenljive

Atribut promenljive mogu imati različite vrednosti za svaki vertex. Uniformne promenljive zadržavaju istu vrednost u više vertex-a.

Postoje dve vrste promenljivih koje se koriste za šejdere: uniformne (jednobrazne) i atribut promenljive. Atribut promenljive mogu imati različite vrednosti za svaki vertex. Uniformne promenljive zadržavaju istu vrednost u više vertex-a. Na primer, ako želite da odreditte boju za komletan trougao koristićete uniformnu promenljivu. Ukoliko želite da svaki od uglova trougla ima drugačiju boju, koristićete atribut promenljive. U daljem tekstu nazivaćemo ih samo "atribut" ili "uniform".

Uniformnim se može pristupiti iz bilo kog šejdera, ali atribut vrednosti moraju prvo ući u vertex šejder, a ne u fragment šejder. Vertex šejderi mogu preneti vrednost u fragment šejdere ukoliko je to neophodno.

Uniformnim se može pristupiti iz bilo kog šejdera, ali za atribut promenljive, morate uneti prvo vertex šejder, a ne samo fragment šejder. Vertex šejder može proslediti vrednost u fragment šejder ukoliko je to neophodno. To je iz razloga što su uniformne promenljive vrednosti kao konstantne - one se ne menjaju te stoga im se može pristupiti iz bilo kog šejdera. Međutim "atribut" promenljive nisu konstantne. Vertex šejderi mogu da menjaju vrednosti "atribut" promenljivih pre nego što dođu do fragment šejdera. Izlazna vrednost iz vertex šejdera je ulazna vrednost koju dobija fragment šejder.

Da bi postavili vrednost uniformne promenljive koristićemo jednu od glUniform* funkcija. Da bi postavili vrednost "atribut" promenljive, smestićemo vrednosti u "VBO" (Vertex Buffer Object) i poslaćemo ih u šejder sa "VAO" (Vertex Array Object) i glVertexAttribPointer na način koji ste mogli da vidite u našem prethodnom članku. Takođe je moguće da postavite vrednost "atribut" promenljive korišćenjem jedne od glVertexAttrib* funkcija, ukoliko ne smeštate vrednosti u "VBO".

Teksture

Teksture su u osnovi 2D slike koje primenjujete na 3D objekte.

Teksture su u osnovi 2D slike koje možete da primenite na 3D objekte. Teksture mogu imati i druge primene, ali prikaz 2D slike na 3D geometrijskim telima je najčešći način primene. Postoje i 1D, 2D i 3D teksture, ali u ovom članku baziraćemo se samo na 2D teksture. Za detaljniji prikaz tekstura pogledajte poglavlje Teksture nisu slike (Textures are not Pictures) iz knjige Učenje modernog programiranja 3D grafike (Learning Modern 3D Graphics Programing).

Teksture se nalaze u video memoriji. Vi učitavate podatke o teksturi u grafičku karticu pre upotrebe. Kao i u slučaju sa "VBO" iz prethodnog članka - "VBO" se koristi za čuvanje podataka u video memoriji pre upotrebe.

Broj piksela širine i visine teksture se duplira.

Širina i visina teksture se duplira, na primer: 16, 32, 64, 128, 256, 512. U ovom članku koristićemo sliku dimenzija 265x265 “hazard.png” kao teksturu koja je prikazana ispod.

Koristićemo tdogl::Bitmap klasu da učitamo neobrađene podatke o pikselima iz "hazard.png" u memoriju uz pomoć stb_image. a. Nakon toga upotrebićemo tdogl::Texture za unos neobrađenih podataka o pikselima u "OpenGL" teksturni objekt. Na sreću kreiranje teksture u "OpenGL" se nije izmenilo od njegovog nastajanja, tako da ima dosta dobrih tekstova na internetu u kojima je prikazano kako da kreirate teksturu u "OpenGL". Način na koji se kordinate teksture šalju u grafičku karticu se promenio, ali način na koji se kreira tekstura je ostao isti.

Ispod je prikazan kod za tdogl::Texture, koji služi za stvaranje "OpenGL" teksture.

Texture::Texture(const Bitmap& bitmap, GLint minMagFiler, GLint wrapMode) :
    _originalWidth((GLfloat)bitmap.width()),
    _originalHeight((GLfloat)bitmap.height())
{
    glGenTextures(1, &_object);
    glBindTexture(GL_TEXTURE_2D, _object);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minMagFiler);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, minMagFiler);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    glTexImage2D(GL_TEXTURE_2D0, 
                 TextureFormatForBitmapFormat(bitmap.format()),
                 (GLsizei)bitmap.width(), 
                 (GLsizei)bitmap.height(),
                 0, 
                 TextureFormatForBitmapFormat(bitmap.format()), 
                 GL_UNSIGNED_BYTE, 
                 bitmap.pixelBuffer());
    glBindTexture(GL_TEXTURE_2D, 0);
}

Koordinate tekstura

Zanimljiva stvar u vezi koordinata tekstura je da one nisu u pikselima. One se kreću u opsegu od nula do jedan, gde je (0,0) dole levo a (1,1) gore desno.

Koordinate tekstura su kao što i sam naziv kaže koordinate koje opisuju teksturu. Zanimljiva stvar u vezi koordinata tekstura je da one nisu u pikselima. One se kreću u opsegu od nula do jedan, gde je (0,0) dole levo, a (1,1) gore desno. Ako učitate sliku u "OpenGL" okrenutu naopačke, onda (0,0) će biti gore levo, a ne dole levo. Da pretvorite koordinate piksela u koordinate teksture morate ih podeliti po širini i visini teksture. Na primer, kod slike koja je 256x256 piksela, koordinate (128,256) biće (0.5, 1) kada se pretstave kao koordinate teksture.

Koordinate tekstura uobičajno nazivamo "UV" koordinatama. Možete ih nazvati i XY koordinatama, ali XYZ se uobičajno koristi za vertex-e, a ne želimo da mešamo koordinate tekstura sa koordinatama vertex-a.

Jedinice za obradu teksturnih slika

Ne možete samo da pošaljete teksturu u šejder. Prvo morate da povežete teksture sa teksturnim jedinicama, zatim trebate da pošaljete indeks teksturnih jedinica u šejder.

Jedinice za obradu teksturnih slika, ili jednostavno "teksturne jedinice", su pomalo čudni deo "OpenGL"-a. Ne možete samo da pošaljete teksturu u šejder. Prvo morate da povežete teksture sa teksturnim jedininicama, a zatim da ih pošaljete indeks teksturnih jedinica u šejder.

TPostoji ograničen broj teksturnih jedinica. Na uređajima sa slabijm hardverom kao što su mobilni telefoni, mogu se naći samo dve teksturne jedinice. U tom slučaju čak i kada imamo na desetine različitih tekstura, samo dve teksture možemo da koristimo istovremeno u šejderima. U ovom članku koristićemo samo jednu teksturu, tako da će nam biti potrebna samo jedna teksturna jedinica, ali je zato moguće uklapanje više različitih tekstura unutar fragment šejdera.

Implementacija tekstura

Prvo, hajde da napravimo novo opšte podešavanje za teksturu.

tdogl::Texture* gTexture = NULL;

Napravićemo novu funkciju koju učitavamo u “hazard.png” u opšta podešavanja. Nju pozivamo iz AppMain funkcije.

static void LoadTexture() {
   tdogl::Bitmap bmp = tdogl::Bitmap::bitmapFromFile(ResourcePath("hazard.png"));
   bmp.flipVertically();
   gTexture = new tdogl::Texture(bmp);
}

Zatim, dodaćemo svakom vertex-u trougla kordinate teksture. Ukoliko uporedite UV koordinate sa koordinatama slike koja se nalazi iznad, videćete da koordinate predstavljaju (sredinu, vrh), (levo dole) i (desno, dole), u tom redosledu.

GLfloat vertexData[] = {
    //  X     Y     Z       U     V
     0.0f, 0.8f, 0.0f,   0.5f, 1.0f,
    -0.8f,-0.8f, 0.0f,   0.0f, 0.0f,
     0.8f,-0.8f, 0.0f,   1.0f, 0.0f,
};

Sada trebamo da modifikujemo fragment šejdere tako da uzimaju teksturu i koordinate teksture kao ulazni podatak. Novi fragment šejder bi izgledao ovako:

#version 150
uniform sampler2D tex; //this is the texture
in vec2 fragTexCoord; //this is the texture coord
out vec4 finalColor; //this is the output color of the pixel

void main() {
    finalColor = texture(tex, fragTexCoord);
}

uniform ključni indikator jeste da je tex uniformna promenljiva. Tekstura je uniformna iz razloga što sva temena trougla imaju istu teksturu sampler2D je tip varijable i naznačava da sadrži 2D teksturu.

fragTexCoord je "atribut" varijabla, zato što će svaki vertex trougla imati različite koordinate teksture.

texture funkcija pronalazi boju piksela na zadatim koordinatama u teksturi. U starijim verzijama "GLSL"-a koristili bi ste texture2D funkciju da to uradite.

Ne možemo da prosledimo atribut direktno u fragment šejder, iz razloga što atributi moraju prvo da prođu kroz vertex šejder. Ovde možete videti modifikovani vertex šejder.

#version 150
in vec3 vert;
in vec2 vertTexCoord;
out vec2 fragTexCoord;

void main() {
    // Pass the tex coord straight through to the fragment shader
    fragTexCoord = vertTexCoord;
    
    gl_Position = vec4(vert, 1);
}

Ovaj vertex šejder uzima vertTexCoord akao ulaznu vrednost, i prosleđuje je direktno u fragTexCoord atribut fragment šejdera bez potrebe za modifikacijom.

Šejderi sada imaju dve varijable koje trebamo da unesemo: vertTexCoord "atribut" varijablu i tex uniformnu varijablu. Prvo unosimo vrednost tex varijable. Otvaramo "main.cpp" (main.mm na OSX sistemu) i nalazimo Render() funkciju. Podešavanje vrednosti tex varijable radimo pre iscrtavanja trougla.

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gTexture->;object());
gProgram->;setUniform("tex", 0); //set to 0 because the texture is bound to GL_TEXTURE0

Teksturu ne možemo da koristimo pre nego što je povežemo na teksturnnu jedinicu glActiveTexture komanda govori "OpenGL"-u koju teksturnu jedinicu želimo da koristimo. GL_TEXTURE0 je prva teksturna jedinica koju koristimo.

Zatim, koristimo glBindTexture da povežemo teksturu sa aktivnom teksturnom jedinicom.

Nakon toga podešavamo vrednost tex uniformne varijable šejdera u indeksu teksturne jedinice. Koristićemo teksturnu jedinicu nula tako da podešavamo tex uniformnu varijablu na celobrojnu vrednost 0. setUniform metoda poziva glUniform1i funkciju.

Poslednji korak je da koordinate teksture prosledimo do vertTexCoord atributa. Da bi to učinili moramo da modifikujemo "VOA" unutar LoadTriangle() funkcije. Kod bi izgledao kao na primeru koji je dat ispod.

// Put the three triangle vertices into the VBO
GLfloat vertexData[] = {
    //  X     Y     Z
     0.0f, 0.8f, 0.0f,
    -0.8f,-0.8f, 0.0f,
     0.8f,-0.8f, 0.0f
};

// connect the xyz to the "vert" attribute of the vertex shader
glEnableVertexAttribArray(gProgram->attrib("vert"));
glVertexAttribPointer(gProgram->attrib("vert"), 3, GL_FLOAT, GL_FALSE, 0, NULL);

Potrebno je uradimo sledeće da bi izvršili promene promene u:

// Put the three triangle vertices (XYZ) and texture coordinates (UV) into the VBO
GLfloat vertexData[] = {
    //  X     Y     Z       U     V
     0.0f, 0.8f, 0.0f,   0.5f, 1.0f,
    -0.8f,-0.8f, 0.0f,   0.0f, 0.0f,
     0.8f,-0.8f, 0.0f,   1.0f, 0.0f,
};

// connect the xyz to the "vert" attribute of the vertex shader
glEnableVertexAttribArray(gProgram->attrib("vert"));
glVertexAttribPointer(gProgram->attrib("vert"), 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), NULL);
    
// connect the uv coords to the "vertTexCoord" attribute of the vertex shader
glEnableVertexAttribArray(gProgram->attrib("vertTexCoord"));
glVertexAttribPointer(gProgram->attrib(vertTexCoord"), 2, GL_FLOAT, GL_TRUE,  5*sizeof(GLfloat), (const GLvoid*)(3 * sizeof(GLfloat)));

Dodali smo drugo pozivanje glVertexAttribPointer, ali takođe smo izvršili modifikaciju i prvog pozivnja. Najbitniji argumente na koje trebate da obratite pažnju su poslednja dva.

Drugi "poslednji" argument u glVertexAttribPointer poziva 5*sizeof(GLfloat). To je “stride” argument. Taj argument želi da zna koliko bajta ima između početka prve vrednosti i početka sledeće vrednosti. U oba slučaja, razmak između vrednosti je pet GLfloat-a. Na primer, ukoliko počnemo sa vrednošću "X" i brojimo pet vrednosti unapred, doćićemo do sledeće vrednosti "X". Isto se odnosi ukoliko krenemo sa "U", i brojimo pet vrednosti unapred. Ovaj argument je predstavljen u bajtovima, a ne u brojevima sa pokretnim zarezom (floats), tako da moramo da pomnožimo brojeve sa pokretnim zarezom sa brojem bajtova.

Poslednji argument glVertexAttribPointer je "offset" argument. Ovaj argument želi da zna koliko bajtova ima od početka prve vrednosti. Prva XYZ vrednost je na početku tako da je "offset" postavljen na "NULL" što znači da ima "nula bajtova od početka". Prva UV vrednost se ne nalazi na početku - nalazi se tri broja od početka. Još jednom, ovaj "argument" je u bajtovima, ne u brojevima sa pokretnim zarezom, tako da moramo da pomnožimo brojeve sa brojem bajtova. Moramo da usmerimo broj bajtova u const GLvoid*, iz razloga što starije verzije "OpenGL"-a koriste taj "argument" na drugačiji način nego što se sada koristi "offset"..

Sada, kada pokrenete program trebalo bi ste da vidite trougao sa teksturom, kao ovaj što je prikazan na početku članaka.

Kratak pogled na sledeći članak

U sledećem članku naučićemo malo o matematici matrica, i koristićemo matrice da rotiramo kocku, da pomeramo kameru, i da dodamo prespektivu. Takođe naučićemo o baferovanju dubine i kako tipični programi rade vremenski bazirano ažuriranje kao što su animacije.

Dodatne informacije o "OpenGL" teksturama





Published (Last edited): 01-06-2013 , source: http://tomdalling.com/blog/modern-opengl/02-textures/