Maybaygiare.org

Blog Network

shaderek

amint azt a Hello Triangle fejezetben említettük, az shaderek kis programok, amelyek a GPU-n nyugszanak. Ezeket a programokat a grafikus folyamat minden egyes szakaszához futtatják. Alapvető értelemben az árnyékolók nem más, mint a bemeneteket kimenetekké átalakító programok. Az árnyékolók szintén nagyon elszigetelt programok, mivel nem kommunikálhatnak egymással; az egyetlen kommunikáció a bemeneteiken és kimeneteiken keresztül történik.

az előző fejezetben röviden érintettük az árnyékolók felületét, és hogyan kell megfelelően használni őket. Most elmagyarázzuk az árnyékolókat, különösen az OpenGL árnyékoló nyelvet, általánosabb módon.

GLSL

az árnyékolók a C-szerű GLSL nyelven íródnak. A GLSL grafikával való használatra készült, és kifejezetten a vektor és mátrix manipulációra irányuló hasznos funkciókat tartalmaz.

az árnyékolók mindig egy verzió deklarációval kezdődnek, amelyet a bemeneti és kimeneti változók listája, az egyenruhák és annak fő funkciója követ. Minden shader belépési pontja a fő funkciója, ahol feldolgozzuk a bemeneti változókat, és az eredményeket a kimeneti változókban adjuk ki. Ne aggódjon, ha nem tudja, milyen egyenruhák vannak, hamarosan eljutunk hozzájuk.

a shader általában a következő szerkezettel rendelkezik:

#version version_numberin type in_variable_name;in type in_variable_name;out type out_variable_name; uniform type uniform_name; void main(){ // process input(s) and do some weird graphics stuff ... // output processed stuff to output variable out_variable_name = weird_stuff_we_processed;}

amikor kifejezetten a vertex shaderről beszélünk, minden bemeneti változó csúcs attribútumként is ismert. Van egy maximális számú csúcs attribútumok vagyunk szabad deklarálni korlátozott a hardver. Az OpenGL garantálja, hogy mindig legalább 16 4 komponensű csúcs attribútum áll rendelkezésre, de egyes hardverek lehetővé tehetnek többet, amelyeket a GL_MAX_VERTEX_ATTRIBS lekérdezésével lehet letölteni:

int nrAttributes;glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

Ez gyakran a 16 minimális értékét adja vissza, amelynek a legtöbb célra többnek kell lennie.

típusok

a GLSL-nek, mint bármely más programozási nyelvnek, adattípusai vannak annak meghatározására, hogy milyen változóval akarunk dolgozni. A GLSL rendelkezik a legtöbb alapértelmezett alaptípussal, amelyet a C nyelvekből ismerünk: intfloatdoubleuint és bool. A GLSL két tárolótípust is tartalmaz, amelyeket sokat fogunk használni, nevezetesen vectors és matrices. A mátrixokat egy későbbi fejezetben tárgyaljuk.

Vektorok

a GLSL-ben lévő vektor egy 1,2,3 vagy 4 komponensű konténer az imént említett alaptípusok bármelyikéhez. A következő formát ölthetik (n az összetevők számát jelöli):

  • vecn: a n alapértelmezett vektora úszik.
  • bvecn: a n logikai értékek vektora.
  • ivecn: a n egész számok vektora.
  • uvecn: a n alá nem írt egész számok vektora.
  • dvecn: a n kettős komponensek vektora.

legtöbbször az alapvető vecn mivel az úszók a legtöbb célunkhoz elegendőek.

A vektor komponensei a következő címen érhetők el:vec.x aholx A vektor első komponense. A .x.y.z és .w az első, második, harmadik és negyedik komponens eléréséhez használhatja. GLSL is lehetővé teszi, hogy használja rgba a színek vagy stpq a textúra koordinátákat, elérve az azonos összetevőket.

A vektor adattípus lehetővé teszi a swizzling nevű érdekes és rugalmas komponens kiválasztását. A Swizzling lehetővé teszi számunkra, hogy ilyen szintaxist használjunk:

vec2 someVec;vec4 differentVec = someVec.xyxx;vec3 anotherVec = differentVec.zyw;vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

legfeljebb 4 betű bármilyen kombinációját használhatja új (azonos típusú) vektor létrehozásához, amennyiben az eredeti vektor rendelkezik ezekkel az összetevőkkel; nem szabad hozzáférni a .z komponenséhez vec2 például. A vektorokat argumentumként is átadhatjuk különböző vektorkonstruktor hívásoknak, csökkentve a szükséges argumentumok számát:

vec2 vect = vec2(0.5, 0.7);vec4 result = vec4(vect, 0.0, 0.0);vec4 otherResult = vec4(result.xyz, 1.0);

a Vektorok tehát rugalmas adattípusok, amelyeket mindenféle bemenethez és kimenethez használhatunk. A könyvben rengeteg példát láthat arra, hogyan tudjuk kreatívan kezelni a vektorokat.

csínját-bínját

az árnyékolók önmagukban szép kis programok, de egy egész részét képezik, ezért szeretnénk, ha az egyes árnyékolókon bemenetek és kimenetek lennének, hogy a dolgokat mozgathassuk. A GLSL kifejezetten erre a célra definiálta a in és out kulcsszavakat. Minden shader megadhatja bemenetek és kimenetek segítségével ezeket a kulcsszavakat, és ahol egy kimeneti változó megegyezik egy bemeneti változó a következő shader szakaszban ők továbbított mentén. A vertex és a fragment shader azonban kissé eltér.

a vertex shadernek valamilyen bemeneti formát kell kapnia, különben elég hatástalan lenne. A vertex shader különbözik a bemenetétől, mivel a bemenetét közvetlenül a csúcsadatokból kapja. A csúcsadatok rendezésének meghatározásához megadjuk a bemeneti változókat hely metaadatokkal, így konfigurálhatjuk a csúcs attribútumait a CPU-n. Ezt láttuk az előző fejezetben layout (location = 0). A vertex shader tehát extra elrendezési specifikációt igényel a bemeneteihez, így összekapcsolhatjuk a vertex adatokkal.

lehetőség van alayout (location = 0)specifikátor elhagyására és az OpenGL-kód attribútumhelyeinek lekérdezésére a glGetAttribLocation segítségével, de inkább a vertex shaderben szeretném beállítani őket. Könnyebb megérteni és megtakarítani (és OpenGL) néhány munkát.

a másik kivétel az, hogy a fragmentum shader igényel vec4 color output változó, mivel a fragmentum shader kell generálni a végső kimeneti szín. Ha nem ad meg kimeneti színt a fragmentum árnyékolójában, akkor ezeknek a töredékeknek a színpuffer kimenete meghatározatlan lesz (ami általában azt jelenti, hogy az OpenGL fekete vagy fehér színűvé teszi őket).

tehát, ha adatokat akarunk küldeni az egyik árnyékolóról a másikra, akkor egy kimenetet kell deklarálnunk a küldő árnyékolóban, és egy hasonló bemenetet a fogadó árnyékolóban. Ha a típusok és a nevek mindkét oldalon egyenlőek, az OpenGL összekapcsolja ezeket a változókat, majd adatokat küldhet az árnyékolók között (ez egy programobjektum összekapcsolásakor történik). Hogy megmutassuk, hogyan működik ez a gyakorlatban, megváltoztatjuk az előző fejezet árnyékolóit, hogy a vertex shader eldöntse a fragmentum shader színét.

Vertex shader

#version 330 corelayout (location = 0) in vec3 aPos; // the position variable has attribute position 0 out vec4 vertexColor; // specify a color output to the fragment shadervoid main(){ gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color}

Fragment shader

#version 330 coreout vec4 FragColor; in vec4 vertexColor; // the input variable from the vertex shader (same name and same type) void main(){ FragColor = vertexColor;} 

láthatjuk, hogy egy vertexColor változót deklaráltunk vec4 kimenet, amelyet a vertex shader-ben állítottunk be, és hasonló vertexColor bemenetet deklarálunk a fragmentum shader-ben. Mivel mindkettőnek ugyanaz a típusa és neve, a fragmentum shaderben található vertexColor kapcsolódik a vertexcolorhoz a vertex shader-ben. Mivel a színt Sötétvörös színre állítjuk a csúcs árnyékolóban, a kapott töredékeknek is sötétvörösnek kell lenniük. A következő kép a kimenetet mutatja:

tessék! Csak sikerült elküldenünk egy értéket a vertex shader-től a fragmentum shader-hez. Fűszerezzük fel egy kicsit, és lássuk, tudunk-e színt küldeni az alkalmazásunkból a fragment shader-hez!

egyenruhák

az egyenruhák egy másik módja annak, hogy adatokat továbbítsunk a CPU-n lévő alkalmazásunkból a GPU árnyékolóinak. Az egyenruhák azonban kissé eltérnek a csúcs tulajdonságaitól. Először is, az egyenruhák globálisak. Globális, ami azt jelenti, hogy egy egységes változó egyedi shader programobjektumonként, és bármely shader-ről elérhető a shader program bármely szakaszában. Másodszor, bármit is állít be az egységes értékre, az egyenruhák megőrzik értékeiket, amíg vissza nem állítják vagy frissítik őket.

a GLSL-ben lévő Egyenruha deklarálásához egyszerűen hozzáadjuk a uniform kulcsszót egy shaderhez, amelynek típusa és neve van. Ettől a ponttól kezdve használhatjuk az újonnan bejelentett egyenruhát az árnyékolóban. Lássuk, hogy ezúttal egyenruhán keresztül beállíthatjuk-e a háromszög színét:

#version 330 coreout vec4 FragColor; uniform vec4 ourColor; // we set this variable in the OpenGL code.void main(){ FragColor = ourColor;} 

egységes vec4 ourColor a fragmentum shaderben, és állítsa be a fragmentum kimeneti színét ennek az egységes értéknek a tartalmára. Mivel az egyenruhák globális változók, bármilyen shader szakaszban meghatározhatjuk őket, így nem kell újra átmennünk a vertex shader-en, hogy valamit kapjunk a fragmentum shader-hez. Nem használjuk ezt az egyenruhát a vertex shaderben, így nincs szükség arra, hogy ott definiáljuk.

ha olyan egyenruhát deklarál, amelyet sehol nem használ a GLSL kódjában, a fordító csendben eltávolítja a változót a lefordított verzióból, ami számos frusztráló hiba oka; ezt tartsa szem előtt!

az egyenruha jelenleg üres; még nem adtunk hozzá semmilyen adatot az egyenruhához, ezért próbáljuk meg. Először meg kell találnunk az egységes attribútum indexét/helyét az árnyékolónkban. Miután megvan az egyenruha indexe/helye, frissíthetjük annak értékeit. Ahelyett, hogy egyetlen színt adna át a fragmentum árnyékolójának, fűszerezzük a dolgokat azáltal, hogy az idő múlásával fokozatosan megváltoztatjuk a színt:

float timeValue = glfwGetTime();float greenValue = (sin(timeValue) / 2.0f) + 0.5f;int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");glUseProgram(shaderProgram);glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

először a glfwgettime () segítségével másodpercben lekérjük a futási időt. Ezután változtatjuk a színt a 0.01.0 tartományban a sin függvény használatával, és az eredményt greenValue-ban tároljuk.

ezután a glGetUniformLocation segítségével lekérdezzük az ourColor Egyenruha helyét. A shader programot és az egyenruha nevét (ahonnan a helyet szeretnénk lekérni) a lekérdezési függvényhez adjuk. Ha a glGetUniformLocation -1 értéket adja vissza, akkor nem találja a helyet. Végül az egységes értéket a glUniform4f függvény segítségével állíthatjuk be. Ne feledje, hogy az egységes hely megtalálásához nem szükséges először a shader programot használni, de az egyenruha frissítése megköveteli, hogy először használja a programot (a glUseProgram hívásával), mert beállítja az egyenruhát az aktuálisan aktív shader programon.

mivel az OpenGL a C könyvtárban van, nem rendelkezik natív támogatással a függvény túlterheléséhez, így bárhol is hívható egy függvény különböző típusokkal, az OpenGL új funkciókat határoz meg minden szükséges típushoz; a glUniform tökéletes példa erre. A funkcióhoz külön postfix szükséges a beállítani kívánt Egyenruha típusához. A lehetséges postfixek közül néhány:

  • f: a függvény értéke float.
  • i: a függvény értékeint.
  • ui: a függvény értéke unsigned int.
  • 3f: a függvény 3float s értéket vár.
  • fv: a függvény értéke float vektor/tömb.

amikor OpenGL opciót szeretne beállítani, egyszerűen válassza ki a túlterhelt függvényt, amely megfelel az Ön típusának. Esetünkben az egyenruha 4 úszóját szeretnénk egyedileg beállítani, így adatainkat a glUniform4f-en keresztül továbbítjuk (vegye figyelembe, hogy afvverziót is használhattuk volna).

most, hogy tudjuk, hogyan állítsuk be az egységes változók értékeit, felhasználhatjuk őket renderelésre. Ha azt akarjuk, hogy a szín fokozatosan megváltozzon, akkor ezt az egyenruhát minden képkockán frissíteni akarjuk, különben a háromszög egyetlen egyszínűt tartana fenn, ha csak egyszer állítanánk be. Tehát kiszámítjuk a zöldértéket, és frissítjük az egységes minden render iterációt:

while(!glfwWindowShouldClose(window)){ // input processInput(window); // render // clear the colorbuffer glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // be sure to activate the shader glUseProgram(shaderProgram); // update the uniform color float timeValue = glfwGetTime(); float greenValue = sin(timeValue) / 2.0f + 0.5f; int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor"); glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); // now render the triangle glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); // swap buffers and poll IO events glfwSwapBuffers(window); glfwPollEvents();}

a kód az előző kód viszonylag egyszerű adaptációja. Ezúttal a háromszög rajzolása előtt frissítjük az egyes képkockák egységes értékét. Ha helyesen frissíti az egyenruhát, látnia kell, hogy a háromszög színe fokozatosan zöldről feketére, majd zöldre változik.

nézze meg a forráskódot itt, ha elakadt.

mint látható, az egyenruha hasznos eszköz olyan attribútumok beállításához, amelyek megváltoztathatnak minden keretet, vagy az adatok cseréjéhez az alkalmazás és az árnyékolók között, de mi van, ha minden csúcshoz színt akarunk beállítani? Ebben az esetben annyi egyenruhát kell bejelentenünk, ahány csúcsunk van. Jobb megoldás az lenne, ha több adatot tartalmaznánk a csúcs attribútumokban, amit most meg fogunk tenni.

további attribútumok!

az előző fejezetben láttuk, hogyan tölthetünk ki egy VBO-t, hogyan állíthatjuk be a csúcs attribútummutatókat, és tárolhatjuk mindezt egy VAO-ban. Ezúttal színes adatokat is szeretnénk hozzáadni a csúcsadatokhoz. Színes adatokat fogunk hozzáadni 3 floats a csúcsok tömbjéhez. A háromszögünk minden sarkához piros, zöld és kék színt rendelünk:

float vertices = { // positions // colors 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top }; 

mivel most már több adatot kell küldenünk a vertex shader-nek, módosítani kell a vertex shader-t, hogy a színértékünket csúcs attribútum bemenetként is megkapjuk. Ne feledje, hogy az acolor attribútum helyét 1-re állítottuk a layout specifikátorral:

#version 330 corelayout (location = 0) in vec3 aPos; // the position variable has attribute position 0layout (location = 1) in vec3 aColor; // the color variable has attribute position 1 out vec3 ourColor; // output a color to the fragment shadervoid main(){ gl_Position = vec4(aPos, 1.0); ourColor = aColor; // set ourColor to the input color we got from the vertex data} 

mivel már nem használunk egyenletet a töredék színéhez, de most az ourColor kimeneti változót használjuk, meg kell változtatnunk a töredék árnyékolóját is:

#version 330 coreout vec4 FragColor; in vec3 ourColor; void main(){ FragColor = vec4(ourColor, 1.0);}

mert hozzáadtunk egy másik vertex attribútumot, és frissítettük a VBO-t memória újra kell konfigurálnunk a csúcs attribútum mutatóit. A frissített adatok a VBO memóriájában most egy kicsit így néz ki:

a wtih functionglVertexAttribPointer/function

Az aktuális elrendezés ismeretében frissíthetjük a csúcsformátumot a glVertexAttribPointer segítségével:

// position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));glEnableVertexAttribArray(1);

a glVertexAttribPointer első néhány argumentuma viszonylag egyszerű. Ezúttal a vertex attribútumot konfiguráljuk az attribútum helyén 1. A színértékek mérete 3floats, és nem normalizáljuk az értékeket.

mivel most két csúcs attribútumunk van, újra kell számolnunk a stride értéket. Ahhoz, hogy megkapjuk a következő attribútumértéket (pl. a pozícióvektor következő x komponense) az adattömbben, a 6floats-t jobbra kell mozgatnunk, hármat a pozícióértékekhez és hármat a színértékekhez. Ez 6-szor akkora lépésértéket ad nekünk, mint a float bájtokban (= 24 bájtok).
ezúttal is meg kell adnunk egy eltolást. Minden csúcsnál a pozíciócsúcs attribútum az első, ezért deklaráljuk a 0eltolását. A szín attribútum a pozícióadatok után kezdődik, így az eltolás 3 * sizeof(float) byte-ban (= 12 bytes).

Az alkalmazás futtatásának a következő képet kell eredményeznie:

nézze meg a forráskódot itt, ha elakadt.

lehet, hogy a kép nem pontosan az, amire számíthat, mivel csak 3 színt szállítottunk, nem pedig a hatalmas színpalettát, amelyet most látunk. Ez mind az úgynevezett fragmentum interpoláció eredménye a fragmentum árnyékolóban. Háromszög renderelésekor a raszterizációs szakasz általában sokkal több töredéket eredményez, mint az eredetileg megadott csúcsok. A raszterizáló ezután meghatározza az egyes töredékek helyzetét annak alapján, hogy hol helyezkednek el a háromszög alakján.
ezen pozíciók alapján interpolálja az összes fragmentum shader bemeneti változóját. Tegyük fel például, hogy van egy vonalunk, ahol a felső pont zöld színű, az alsó pedig kék színű. Ha a fragmentum shader egy olyan fragmentumon fut, amely a sor 70% pozíciója körül helyezkedik el, akkor a kapott színbeviteli attribútum a zöld és a kék lineáris kombinációja lenne; pontosabban: 30% kék és 70% zöld.

pontosan ez történt a háromszögnél. 3 csúcsunk van, tehát 3 színünk, és a háromszög pixeleiből ítélve valószínűleg körülbelül 50000 töredéket tartalmaz, ahol a fragmentum shader interpolálta a színeket a pixelek között. Ha jól megnézi a színeket, látni fogja, hogy mindennek van értelme: a vöröstől a kékig először lila, majd kék lesz. A fragmentum interpolációt a fragmentum shader összes bemeneti attribútumára alkalmazzák.

saját shader osztályunk

az árnyékolók írása, összeállítása és kezelése meglehetősen nehézkes lehet. A shader témájának utolsó érintéseként egy kicsit könnyebbé tesszük az életünket egy shader osztály felépítésével, amely beolvassa az árnyékolókat a lemezről, lefordítja és összekapcsolja őket, ellenőrzi a hibákat és könnyen használható. Ez egy kis ötletet ad arra is, hogyan tudjuk az eddig megtanult ismeretek egy részét hasznos absztrakt tárgyakba beépíteni.

a shader osztályt teljes egészében egy fejlécfájlban hozzuk létre, elsősorban tanulási célokra és hordozhatóságra. Kezdjük a szükséges includes hozzáadásával és az osztályszerkezet meghatározásával:

#ifndef SHADER_H#define SHADER_H#include <glad/glad.h> // include glad to get all the required OpenGL headers #include <string>#include <fstream>#include <sstream>#include <iostream> class Shader{public: // the program ID unsigned int ID; // constructor reads and builds the shader Shader(const char* vertexPath, const char* fragmentPath); // use/activate the shader void use(); // utility uniform functions void setBool(const std::string &name, bool value) const; void setInt(const std::string &name, int value) const; void setFloat(const std::string &name, float value) const;}; #endif

több előfeldolgozó direktívát használtunk a fejlécfájl tetején. Ezeknek a kis kódsoroknak a használata tájékoztatja a fordítót, hogy csak akkor vegye fel és fordítsa le ezt a fejlécfájlt, ha még nem került bele, még akkor is, ha több fájl tartalmazza a shader fejlécét. Ez megakadályozza a konfliktusok összekapcsolását.

a shader osztály tartalmazza a shader program azonosítóját. A konstruktor megköveteli a csúcs, illetve a fragmentum shader forráskódjának fájlútvonalait, amelyeket egyszerű szöveges fájlként tárolhatunk a lemezen. Egy kis extra hozzáadásához számos segédprogramot is hozzáadunk, hogy egy kicsit megkönnyítsük az életünket: a használat aktiválja a shader programot, és minden beállítva… a függvények lekérdeznek egy egységes helyet, és beállítják annak értékét.

olvasás fájlból

C++ filestreams segítségével olvassuk a fájl tartalmát több string objektumok:

Shader(const char* vertexPath, const char* fragmentPath){ // 1. retrieve the vertex/fragment source code from filePath std::string vertexCode; std::string fragmentCode; std::ifstream vShaderFile; std::ifstream fShaderFile; // ensure ifstream objects can throw exceptions: vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); try { // open files vShaderFile.open(vertexPath); fShaderFile.open(fragmentPath); std::stringstream vShaderStream, fShaderStream; // read file's buffer contents into streams vShaderStream << vShaderFile.rdbuf(); fShaderStream << fShaderFile.rdbuf(); // close file handlers vShaderFile.close(); fShaderFile.close(); // convert stream into string vertexCode = vShaderStream.str(); fragmentCode = fShaderStream.str(); } catch(std::ifstream::failure e) { std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl; } const char* vShaderCode = vertexCode.c_str(); const char* fShaderCode = fragmentCode.c_str(); 

ezután össze kell fordítanunk és össze kell kapcsolnunk az árnyékolókat. Vegye figyelembe, hogy azt is felülvizsgáljuk, hogy a fordítás/összekapcsolás sikertelen-e, és ha igen, nyomtassa ki a fordítási időt. Ez rendkívül hasznos hibakereséskor (végül szüksége lesz ezekre a hibanaplókra):

// 2. compile shadersunsigned int vertex, fragment;int success;char infoLog; // vertex Shadervertex = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertex, 1, &vShaderCode, NULL);glCompileShader(vertex);// print compile errors if anyglGetShaderiv(vertex, GL_COMPILE_STATUS, &success);if(!success){ glGetShaderInfoLog(vertex, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}; // similiar for Fragment Shader // shader ProgramID = glCreateProgram();glAttachShader(ID, vertex);glAttachShader(ID, fragment);glLinkProgram(ID);// print linking errors if anyglGetProgramiv(ID, GL_LINK_STATUS, &success);if(!success){ glGetProgramInfoLog(ID, 512, NULL, infoLog); std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;} // delete the shaders as they're linked into our program now and no longer necessaryglDeleteShader(vertex);glDeleteShader(fragment);

a használat funkció egyszerű:

void use() { glUseProgram(ID);} 

hasonlóan az egységes szetter funkciók bármelyikéhez:

void setBool(const std::string &name, bool value) const{ glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); }void setInt(const std::string &name, int value) const{ glUniform1i(glGetUniformLocation(ID, name.c_str()), value); }void setFloat(const std::string &name, float value) const{ glUniform1f(glGetUniformLocation(ID, name.c_str()), value); } 

és itt van, egy befejezett shader osztály. A shader osztály használata meglehetősen egyszerű; egyszer létrehozunk egy shader objektumot, és ettől kezdve egyszerűen elkezdjük használni:

Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");while(...){ ourShader.use(); ourShader.setFloat("someUniform", 1.0f); DrawStuff();}

itt tároltuk a csúcs és a töredék shader forráskódját két fájlban, amelyek neve shader.vs és shader.fs. Szabadon nevezheti el shader fájljait, ahogy tetszik; Személy szerint a .vs és .fs kiterjesztéseket elég intuitívnak találom.

a forráskódot itt találja az újonnan létrehozott shader osztályunk segítségével. Ne feledje, hogy az árnyékoló fájl elérési útjaira kattintva megtalálhatja az árnyékolók forráskódját.

gyakorlatok

  1. állítsa be a csúcs árnyékolóját úgy, hogy a háromszög fejjel lefelé legyen: megoldás.
  2. adjon meg egy vízszintes eltolást egy egyenruha segítségével, majd mozgassa a háromszöget a képernyő jobb oldalára a csúcs árnyékolóban ezzel az eltolási értékkel: megoldás.
  3. aout kulcsszóval adja ki a csúcspozíciót a fragmentum árnyékolójának, és állítsa be a fragmentum színét ezzel a csúcspozícióval (lásd, hogy még a csúcspozíció értékei is interpolálódnak a háromszögben). Miután sikerült ezt megtennie; próbáljon válaszolni a következő kérdésre: miért fekete a háromszög bal alsó oldala?: megoldás.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.