Maybaygiare.org

Blog Network

shadery

jak je uvedeno v kapitole Hello Triangle, shadery jsou malé programy, které spočívají na GPU. Tyto programy jsou spuštěny pro každou konkrétní část grafického potrubí. V základním smyslu nejsou shadery nic jiného než programy transformující vstupy na výstupy. Shadery jsou také velmi izolované programy v tom, že spolu nemohou komunikovat; jediná komunikace, kterou mají, je prostřednictvím jejich vstupů a výstupů.

v předchozí kapitole jsme se krátce dotkli povrchu shaderů a jak je správně používat. Nyní vysvětlíme shadery, a konkrétně jazyk stínování OpenGL, obecnějším způsobem.

GLSL

shadery jsou psány v jazyce GLSL podobném C. GLSL je přizpůsoben pro použití s grafikou a obsahuje užitečné funkce speciálně zaměřené na vektorovou a maticovou manipulaci.

shadery vždy začínají deklarací verze, následovanou seznamem vstupních a výstupních proměnných, uniforem a jejich hlavní funkcí. Vstupní bod každého shaderu je ve své hlavní funkci, kde zpracováváme všechny vstupní proměnné a výsledky výstupních proměnných. Nebojte se, pokud nevíte, co jsou uniformy, brzy se k nim dostaneme.

shader má obvykle následující strukturu:

#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;}

Když mluvíme konkrétně o vertex shader každá vstupní proměnná je také známý jako atribut vrchol. Existuje maximální počet atributů vertex, které můžeme deklarovat jako omezené hardwarem. OpenGL zaručuje, že vždy je k dispozici alespoň 16 4-komponentních atributů vertexu, ale některé hardware mohou umožnit více, které můžete získat dotazem GL_MAX_VERTEX_ATTRIBS:

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

to často vrací minimum 16 což by mělo být pro většinu účelů více než dost.

typy

GLSL má, jako každý jiný programovací jazyk, datové typy pro určení, s jakou proměnnou chceme pracovat. GLSL má většina z výchozí základní typy známe z jazyků jako je C: intfloatdoubleuintbool. GLSL také obsahuje dva typy kontejnerů, které budeme hodně používat, a to vectors a matrices. O maticích budeme diskutovat v další kapitole.

vektory

vektor v GLSL je 1,2,3 nebo 4 komponentní kontejner pro některý ze základních typů právě zmíněných. Mohou mít následující podobu (n představuje počet složek):

  • vecn: výchozí vektor n plováky.
  • bvecn: vektor n booleans.
  • ivecn: vektor n celá čísla.
  • uvecn: vektor n nepodepsaná celá čísla.
  • dvecn: vektor n dvojité komponenty.

Většinu času budeme používat základní vecn protože plováky jsou dostatečné pro většinu našich potřeb.

Komponenty vektoru lze přistupovat pomocí vec.xx je první složkou vektoru. Můžete použít .x.y.z.w přístup k jejich první, druhé, třetí a čtvrté komponenty, resp. GLSL také umožňuje použít rgba pro barvy nebo stpq pro souřadnice textur, přístup do stejných složek.

vektorový datový typ umožňuje zajímavý a flexibilní výběr komponent zvaný swizzling. Swizzling nám umožňuje používat syntaxi, jako je tato:

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

můžete použít libovolnou kombinaci až 4 písmena k vytvoření nového vektoru (stejného typu), tak dlouho, jako původní vektor má tyto součásti; není povolen přístup .z prvek vec2 například. Můžeme také předat vektory jako argumenty pro různé vektorové konstruktor volá, snížení počtu argumentů vyžaduje:

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

Vektory jsou tedy flexibilní datový typ, který můžeme použít pro všechny druhy vstupních a výstupních. V celé knize uvidíte spoustu příkladů, jak můžeme kreativně spravovat vektory.

Vstupy a výstupy

Shadery jsou pěkné malé programy na jejich vlastní, ale jsou součástí celku, a proto chceme mít vstupy a výstupy na jednotlivé shadery tak, že jsme se může pohybovat věci kolem. GLSL definovalin aout klíčová slova speciálně pro tento účel. Každý shader může určit vstupy a výstupy pomocí těchto klíčových slov a všude tam, kde se výstupní proměnná shoduje se vstupní proměnnou další fáze shaderu, jsou předány. Vertex a fragment shader se však trochu liší.

vertex shader by měl obdržet nějakou formu vstupu, jinak by to bylo docela neúčinné. Vertex shader se liší ve svém vstupu tím, že přijímá svůj vstup přímo z vertexových dat. Chcete-li definovat, jak jsou data vertex uspořádána, určíme vstupní proměnné s metadaty umístění, abychom mohli nakonfigurovat atributy vertex na CPU. Viděli jsme to v předchozí kapitole jako layout (location = 0). Vertex shader tedy vyžaduje pro své vstupy další specifikaci rozvržení, abychom jej mohli propojit s vertexovými daty.

je také možné vynechatlayout (location = 0)specifikátor a dotaz pro atribut místech ve vaší OpenGL kódu prostřednictvím glGetAttribLocation, ale já bych raději je na vertex shader. Je snazší pochopit a ušetří vám (a OpenGL) nějakou práci.

další výjimkou je, že fragment shader vyžaduje vec4 barevný výstup proměnné, protože fragment shadery musí generovat výstupní barvu. Pokud se vám nepodaří zadat výstupní barvu ve vašem fragmentu shader, výstup vyrovnávací paměti barev pro tyto fragmenty bude nedefinovaný (což obvykle znamená, že OpenGL je vykreslí buď černé nebo bílé).

takže pokud chceme odesílat data z jednoho shaderu do druhého, musíme deklarovat výstup v odesílajícím shaderu a podobný vstup v přijímajícím shaderu. Když jsou typy a názvy stejné na obou stranách OpenGL propojí tyto proměnné dohromady a pak je možné odesílat data mezi shadery (to se provádí při propojení objektu programu). Ukázat vám, jak to funguje v praxi budeme měnit shadery z předchozí kapitoly nechat vertex shader rozhodnout barvu pro fragment shader.

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;} 

můžete vidět, že jsme vyhlásili vertexColor proměnné jako vec4 výstup, který jsme si stanovili v vertex shader a prohlašujeme, podobné vertexColor vstup do fragment shaderu. Protože oba mají stejný typ a název, vertexColor v fragment shaderu je spojena s vertexColor ve vertex shaderu. Protože jsme ve vertex shader nastavili barvu na tmavě červenou barvu, výsledné fragmenty by měly být také tmavě červené. Následující obrázek ukazuje výstup:

tam jdeme! Právě se nám podařilo poslat hodnotu z vertex shader do fragment shader. Pojďme to trochu okořenit a uvidíme, jestli můžeme poslat barvu z naší aplikace do shaderu fragmentů!

uniformy

uniformy jsou dalším způsobem, jak předat data z naší aplikace na CPU shaderům na GPU. Uniformy se však mírně liší ve srovnání s atributy vertex. Za prvé, uniformy jsou globální. Globální, což znamená, že jednotná proměnná je jedinečná pro objekt programu shader a lze k ní přistupovat z jakéhokoli shader v jakékoli fázi programu shader. Za druhé, ať už nastavíte jednotnou hodnotu na, uniformy si zachovají své hodnoty, dokud nebudou resetovány nebo aktualizovány.

Chcete-li deklarovat uniformu v GLSL, jednoduše přidáme Klíčové slovo uniform do shader s typem a názvem. Od tohoto okamžiku můžeme použít nově deklarovanou uniformu v shaderu. Uvidíme, jestli tentokrát můžeme nastavit barvu trojúhelníku pomocí uniformy:

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

Jsme vyhlásili jednotné vec4 ourColor ve fragment shaderu, a nastavit fragmentu výstupu a barev na obsah této jednotné hodnoty. Protože uniformy jsou globální proměnné, můžeme je definovat v každém shader fázi bychom chtěli, takže není třeba jít přes vertex shader znovu, aby si něco do fragment shaderu. Nepoužíváme tuto uniformu ve vertex shader, takže tam není třeba ji definovat.

Pokud si prohlásit, uniformu, která není použita kdekoliv v GLSL kód kompilátor bude tiše odstranit proměnnou z kompilované verze, která je příčinou několik frustrující chyby; mějte to na paměti!

uniforma je momentálně prázdná; do uniformy jsme zatím nepřidali Žádná data, tak to zkusme. Nejprve musíme najít index / umístění jednotného atributu v našem shaderu. Jakmile budeme mít index / umístění uniformy, můžeme aktualizovat jeho hodnoty. Místo předávání jedné barvy do fragmentu shader, pojďme okořenit věci postupnou změnou barvy v průběhu času:

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);

nejprve načteme dobu běhu v sekundách pomocí glfwGetTime (). Pak budeme měnit barvu v rozmezí 0.01.0 pomocí funkce sin a uložit výsledek v greenValue.

pak se dotazujeme na umístění uniformy ourColor pomocí glGetUniformLocation. Dodáváme shader program a název uniformy (ze které chceme načíst umístění) do funkce dotazu. Pokud glGetUniformLocation vrátí -1, nemohl najít umístění. Nakonec můžeme nastavit jednotnou hodnotu pomocí funkce glUniform4f. Všimněte si, že nalezení jednotné umístění nevyžaduje použití shader programu první, ale aktualizace jednotné vyžaduje, abyste nejprve použít program (zavoláním glUseProgram), protože to stanovuje jednotná v současné době aktivní shader programu.

Protože OpenGL je ve svém jádru C knihovna nemá nativní podporu pro funkce přetížení, takže všude tam, kde funkce může být volána s různými typy OpenGL definuje nové funkce pro každý typ vyžaduje; glUniform je dokonalým příkladem. Funkce vyžaduje konkrétní postfix pro typ uniformy, kterou chcete nastavit. Několik možných postfixy jsou:

  • f: funkce očekává float jako jeho hodnotu.
  • i: funkce očekává jako svou hodnotu int.
  • ui: funkce očekává jako svou hodnotu unsigned int.
  • 3f: funkce očekává jako svou hodnotu 3 floats.
  • fv: funkce očekává jako hodnotu float vector / array.

kdykoli chcete nakonfigurovat možnost OpenGL, jednoduše vyberte přetíženou funkci, která odpovídá vašemu typu. V našem případě chceme nastavit 4 plováky jednotných individuálně, takže jsme se projít naše data prostřednictvím glUniform4f (všimněte si, že jsme také mohli použítfvverze).

Nyní, když víme, jak nastavit hodnoty jednotných proměnných, můžeme je použít pro vykreslování. Pokud chceme, aby se barva postupně měnit, chceme aktualizovat tuto uniformu každý rám, jinak trojúhelník by udržovat jediný plná barva, když jsme se nastavit pouze jednou. Takže počítáme greenValue a aktualizace jednotných každý render iterace:

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();}

kód je poměrně jednoduché přizpůsobení předchozí kód. Tentokrát aktualizujeme jednotnou hodnotu každého snímku před nakreslením trojúhelníku. Pokud uniformu aktualizujete správně, měli byste vidět, jak se barva trojúhelníku postupně mění ze zelené na černou a zpět na zelenou.

podívejte se na zdrojový kód zde, pokud jste uvízli.

Jak můžete vidět, uniformy jsou užitečným nástrojem pro nastavení atributů, které se mohou měnit každý snímek, nebo k výměne dat mezi aplikace a shadery, ale co když budeme chtít nastavit barvu pro každý vrchol? V tom případě bychom museli vyhlásit tolik uniforem, kolik máme vrcholů. Lepším řešením by bylo zahrnout více dat do atributů vertex, což nyní uděláme.

více atributů!

v předchozí kapitole jsme viděli, jak můžeme vyplnit VBO, nakonfigurovat ukazatele atributů vertex a uložit je do VAO. Tentokrát chceme také přidat barevná data k vertexovým datům. Do pole vrcholů přidáme barevná data jako 3 floats. Jsme přiřadit červené, zelené a modré barvy na každé z koutů naší trojúhelník v tomto pořadí:

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 }; 

Protože nyní máme více dat k odeslání na vertex shader, je nutné upravit vertex shader také přijímat naše hodnoty barvy jako vrchol atributu input. Všimněte si, že jsme nastavili umístění aColor atribut 1 s rozložením specifikátor:

#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} 

Protože jsme již používají jednotné pro fragment je barva, ale nyní používat ourColor výstupní proměnné budeme muset změnit fragment shader stejně:

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

Protože jsme přidali další atribut vrchol a aktualizované VBO paměti musíme re-konfigurovat atribut vrchol ukazatele. Aktualizovaná data v paměti VBO nyní vypadají trochu takto:

Prokládané údaje o pozici a barvu v rámci VBO být nakonfigurován s functionglVertexAttribPointer/funkce

Znát aktuální rozložení můžeme aktualizovat vertex formátu s glVertexAttribPointer:

// 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);

prvních pár argumentů glVertexAttribPointer jsou poměrně jednoduché. Tentokrát konfigurujeme atribut vertex na umístění atributu 1. Hodnoty barev mají velikost 3float s a hodnoty normalizujeme.

protože nyní máme dva atributy vrcholu, musíme znovu vypočítat hodnotu kroku. Chcete-li získat další hodnotu atributu (např. další x složka polohový vektor) v datové matici musíme přesunout 6floaty vpravo, tři pro hodnoty polohy a tři pro barevné hodnoty. To nám dává hodnotu kroku 6krát větší než float v bajtech (= 24 bajty).
také tentokrát musíme zadat offset. Pro každý vrchol je nejprve atribut position vertex, takže deklarujeme offset 0. Atribut color začíná po datech polohy, takže offset je 3 * sizeof(float) v bajtech (= 12 bajty).

spuštění aplikace by měla vyústit v následující obraz:

Podívejte se na zdrojový kód zde, pokud jste přilepená.

obrázek nemusí být přesně to, co byste očekávali, protože jsme dodali pouze 3 barvy, ne obrovskou paletu barev, kterou právě vidíme. To vše je výsledkem něčeho, čemu se říká interpolace fragmentů v shaderu fragmentů. Při vykreslování trojúhelníku má rasterizační fáze obvykle za následek mnohem více fragmentů než původně specifikovaných vrcholů. Rasterizer pak určuje polohy každého z těchto fragmentů na základě toho, kde se nacházejí ve tvaru trojúhelníku.
Na základě těchto pozic interpoluje všechny vstupní proměnné shaderu fragmentů. Řekněme například, že máme čáru, kde horní bod má zelenou barvu a dolní bod modrou barvu. Pokud fragment shader je spuštěn na fragment, který je umístěn kolem pozice v 70% řádku, jeho výsledná barva vstupní atribut by pak lineární kombinace zelené a modré; chcete-li být přesnější: 30% modrá 70% green.

to je přesně to, co se stalo v trojúhelníku. Máme 3 vrcholy, a tedy 3 barvy, a soudě z trojúhelníku pixelů pravděpodobně obsahuje kolem 50000 fragmenty, kde fragment shader interpolované barvy mezi těmi pixely. Pokud se dobře podíváte na barvy, uvidíte, že to všechno dává smysl: červená až modrá se nejprve dostane do fialové a poté do modré. Interpolace fragmentu je aplikována na všechny vstupní atributy shaderu fragmentu.

naše vlastní třída shader

psaní, kompilace a správa shaderů může být docela těžkopádné. Jako poslední dotek na téma shader uděláme náš život trochu jednodušší vytvořením třídy shader, která čte shadery z disku, kompiluje a propojuje je, kontroluje chyby a snadno se používá. To vám také dává trochu představu, jak můžeme zapouzdřit některé znalosti, které jsme se dosud naučili, do užitečných abstraktních objektů.

třídu shader vytvoříme zcela v hlavičkovém souboru, hlavně pro účely učení a přenositelnost. Pojďme začít tím, že přidá požadované zahrnuje a definuje strukturu třídy:

#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

použili Jsme několik preprocessor direktivy v horní části záhlaví souboru. Použití těchto malých řádků kódu informuje kompilátor zahrnout a zkompilovat pouze tento hlavičkový soubor, pokud ještě nebyl zahrnut, i když více souborů obsahuje hlavičku shader. Tím se zabrání propojení konfliktů.

třída shader obsahuje ID programu shader. Jeho Konstruktor vyžaduje souborové cesty zdrojového kódu Vertexu a fragmentu shader, které můžeme uložit na disk jako jednoduché textové soubory. Chcete-li přidat něco navíc, přidáme také několik obslužných funkcí, které nám trochu ulehčí život: použití aktivuje program shader a vše nastaveno… funkce dotaz jednotného umístění a nastavit jeho hodnotu.

Čtení ze souboru

Jsme pomocí C++ filestreams číst obsah ze souboru do několika string objekty:

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(); 

dále potřebujeme k sestavení a odkaz na shadery. Všimněte si, že také kontrolujeme, zda kompilace/propojení selhaly, a pokud ano, vytiskněte chyby v době kompilace. To je velmi užitečné při ladění (budete potřebovat tyto chybové protokoly nakonec):

// 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);

použití funkce je jednoduché:

void use() { glUseProgram(ID);} 

Podobně pro všechny jednotných setter funkce:

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); } 

A tady to máme, úspěšně absolvováno shader třídy. Pomocí shader třídy je poměrně snadné; jsme se vytvořit shader objekt jednou a od té chvíle prostě začít používat:

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

Tady máme uloženy vertex a fragment shader zdrojového kódu v dva soubory shader.vsshader.fs. Máte možnost pojmenovat své shader soubory, jak se vám líbí; Osobně považuji rozšíření .vs a .fs za poměrně intuitivní.

zdrojový kód najdete zde pomocí naší nově vytvořené třídy shader. Všimněte si, že můžete kliknout na cesty k souboru shader a najít zdrojový kód shaderů.

cvičení

  1. upravte vertex shader tak, aby trojúhelník byl vzhůru nohama: řešení.
  2. Určete vodorovný posun přes uniformu a přesuňte trojúhelník na pravou stranu obrazovky ve vertex shader pomocí této hodnoty offsetu: řešení.
  3. Výstup na vrchol polohy do fragment shaderu pomocí out klíčové slovo a nastavit fragmentu barva rovná tento vrchol polohy (viz jak se i vertex pozice hodnoty jsou interpolovány přes trojúhelník). Jakmile se vám to podařilo; zkuste odpovědět na následující otázku: proč je levá dolní strana našeho trojúhelníku Černá?: řešení.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.