Előadás Home Introduction Environment setup Best homeworks

Materials és Lighting maps

A való világban minden objektum máshogy reagál a megvilágításra. Egy fém objektum gyakran fényesebb mint egy agyag váza vagy egy fadoboz. Az objektumok a specular komponensre is máshogy reagálnak: egyesek elszórják a fényt, míg mások egy ragyogó pontban tükrözik vissza. Ahhoz, hogy az OpenGL-ben különböző típusú objektumokat tudjunk szimulálni specifikus anyagi tulajdonságokat rendelünk az egyes objektumokhoz.

Az anyag tulajdonságánál külön definiáljuk az ambient, diffuse és specular megvilágítási komponenseket, továbbá definiálunk egy fényesség (shininess) értéket is:
In [ ]:
#version 400 core
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
}; 
  
uniform Material material;
A fragment shaderben létrehozunk egy struktúrát, amiben az anyag tulajdonságokat tároljuk. Ezzel a négy komponenssel, ami az objektumok anyag tulajdonságát definiálja nagyon sok valós anyagot tudunk szimulálni. A devernay.free.fr táblázat számos valós anyag tulajdonságot tartalmaz. A következő kép különböző anyagú objektumok megvilágítását mutatja:

title


Ahogy változtatjuk az objektumok anyag tulajdonságát máshogy érzékeljük az azokat.

Anyag tulajdonság alkalmazása a shaderben


Létrehoztunk egy uniform material struktúrát a fragment shaderben, így befolyásolni tudjuk a megvilágítás számítását az anyag tulajdonságokkal:
In [ ]:
void main() {    
    // ambient
    vec3 ambient = lightColor * material.ambient;
  
    // diffuse 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = lightColor * (diff * material.diffuse);
    
    // specular
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = lightColor * (spec * material.specular);  
        
    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);
}
Minden objektum anyag komponense szorozva van az adott megvilágítási komponensel. A fő programban tudjuk a fragment shader uniform material komponenseit beállítani, úgy mintha mindegyik egy külön uniform változó lenne:
In [ ]:
lightingShader.setVec3("material.ambient",  1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.diffuse",  1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightingShader.setFloat("material.shininess", 32.0f);
Az ambient és diffuse komponenst annak függvényében állítjuk be, hogy milyen színt szeretnénk, a specular komponenst pedig közepes értékre állítjuk, hogy ne legyen túl hangsúlyos. Hasonló eredményt kell kapnunk:

title

Megvilágítás tulajdonságai


Látható, hogy az objektum kicsit túl világos. Ennek az az oka, hogy az ambient, diffuse és a specular komponensek teljes intenzitással érvényesülnek az adott fényforrás esetén. Az előző tutorialban egy erősség (strength) komponenst állítottunk be az ambient és diffuse értékek számításánál, most egy intenzitás vektort fogunk definiálni az egyes ambient, diffuse és specular komponensekhez:
In [ ]:
vec3 ambient  = vec3(1.0) * material.ambient;
vec3 diffuse  = vec3(1.0) * (diff * material.diffuse);
vec3 specular = vec3(1.0) * (spec * material.specular);
Látható, hogy minden komponens teljes intenzitással érvényesül. Tudjuk, hogy az ambient komponens a szórt fényt simulálja, ezért nem kellene hogy ilyen nagy szerepet kapjon az objektum végső színének a kialakításában, így egy kisebb értékre állítjuk azt:
In [ ]:
vec3 ambient = vec3(0.1) * material.ambient;
A fényforrás ambient, diffuse és specular intenzitását egy Light struktúra segítségével könnyen befolyásolhatjuk:
In [ ]:
struct Light {
    vec3 position;
  
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;
Egy fényforrásnak különböző intenzitása van az ambient, diffuse és specular komponenseire nézve. Az ambient komponenst általában alacsony értékre állítjuk, mert nem akarjuk, hogy túl domináns legyen. A diffuse komponenst intenzitását általában úgy állítjuk be, hogy meghatározó legyen az objektum végső színében, a specular komponenst viszont általában teljes intenzitáson hagyjuk. A Light struktúra a fényforrás pozícióját is tartalmazni fogja:
In [ ]:
vec3 ambient  = light.ambient * material.ambient;
vec3 diffuse  = light.diffuse * (diff * material.diffuse);
vec3 specular = light.specular * (spec * material.specular);

A uniform változókon keresztül tudjuk beállítani a Light struktúra adattagjait:

In [ ]:
lightingShader.setVec3("light.ambient",  0.2f, 0.2f, 0.2f);
lightingShader.setVec3("light.diffuse",  0.5f, 0.5f, 0.5f); // darken the light a bit to fit the scene
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);

Most, hogy irányítani tudjuk, hogy a fény hogyan befolyásolja az objektumok anyagi megjelenését, valami hasonlót kell kapnunk:

title

Különböző színű fények hatása


Eddig csak a fényforrás egyes komponenseinek az intenzitását változtattuk, de mivel mostmár minden be van állítva a fragment shaderben folyamatosan változtathatjuk a fény színét:

In [ ]:
glm::vec3 lightColor;
lightColor.x = sin(glfwGetTime() * 2.0f);
lightColor.y = sin(glfwGetTime() * 0.7f);
lightColor.z = sin(glfwGetTime() * 1.3f);
  
glm::vec3 diffuseColor = lightColor   * glm::vec3(0.5f); // decrease the influence
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence
  
lightingShader.setVec3("light.ambient", ambientColor);
lightingShader.setVec3("light.diffuse", diffuseColor);
  • A teljes forráskód: Code
  • Vertex shader: Code
  • Fragment shader: Code
  • Lamp Vertex shader: Code
  • Lamp Fragment shader: Code

Lighting maps

Mostmár minden egyes objektumnak tudunk egyedi anyagi tulajdonságokat definiálni és így képes egyedileg reagálni a különböző megvilágításnak. Azonban jelenleg az objektum egész felületének ugyanazt az anyagi tulajdonságot állítjuk be, de a valós világban ez nem így van. Például egy autó tartalmaz fémes, műanyag, gumi, üveg, stb. részeket, amik más módon reagálnak ugyanarra a megvilágításra.

Ahhoz hogy egy ilyen komplex objektum felületet létrehozzunk diffuse és specular map leírókat fogunk használni. Ez lehetőséget ad, hogy az objektum egyes részeinek különböző ambient, diffuse és specular anyagi tulajdonságot definiáljunk.

Diffuse maps


Tehát az objektum minden egyes fragmentjéhez külön szeretnénk állítani a diffuse komponenseket. Ez gyakorlatilag nem más, mint a textúrák koncepciója, csak most diffuse és specular mapnek hívjuk az adott képet, ami a megvilágítási tulajdonságokat megszabja. A diffuse map demonstrálásához a következő képet fogjuk használni, egy fadoboz fém széllel:

title


A diffuse mapet ugyanúgy tudjuk használni a shaderben, mint a textúrákat. Azonban most a sampler2D objektumot a Material struktúrán belül fogjuk tárolni. A korábban definiált vec3 típusú diffuse adattagot fogjuk helyettesíteni a sampler2D objektummal.

Mivel a sampler2D objektum egy ún. opaque típus, ezért nem tudjuk példányosítani csak úgy mint egy uniform változó. Ha a Material struktúrát nem uniform változóként példányosítanánk (pl. egy függvény paraméterként), akkor a GLSL furcsa hibákat dobna.(Ez az összes olyan struktúrára igaz, ami opeque típusokat tartalmaz.)

Az ambient material színt eltávolítjuk a Material struktúrával, mivel az szinte az összes esetben megegyezik a diffuse színnel, így felesleges külön tárolni:

In [ ]:
struct Material {
    sampler2D diffuse;
    vec3      specular;
    float     shininess;
}; 
...
in vec2 TexCoords;
Note: Természetesen meghagyhatjuk az ambient adattagot és akkor az egész objektumhoz ugyanazt az ambient értéket rendeljük, vagy egy ambient mapet is létrehozhatunk és az összes fragmentnek külön állíthatjuk az ambient tulajdonságot.

Mivel most szükségünk lesz textúra koordinátákra a diffuse map mintavételezéséhezm ezért ne felejtsük el azokat definiálni a shaderben és az atrribútum pointereket is frissíteni kell.

In [ ]:
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

Most az ambient material szín meg fog egyezni a diffuse színnel:

In [ ]:
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
Ne felejtsük el, hogy frissítenünk kell a vertex adatot a megfelelő textúra koordinátákkal, frissíteni kell a vertex attribútumokat, be kell tölteni a textúrákat és csatolni kell azokat a megfelelő textúra unitra. Így a vertex adat a pozíció és a normál vektorok mellet a textúra koordinátákat is tartalmazza. Frissítsük a vertex shadert, hogy fogadja a textúra koordinátákat, majd továbbítsa azt a fragment shadernek:
In [ ]:
#version 400 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
...
out vec2 TexCoords;

void main() {
    ...
    TexCoords = aTexCoords;
}
Mielőtt rajzolhatnánk a uniform material.diffuse sampler objekzuomat hozzá kell rendelni a megfelelő textúra unithoz:
In [ ]:
lightingShader.setInt("material.diffuse", 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

A diffuse map használatával mostmár sokkal részletesebb megvilágítást kapunk, mintha csak egy egyszerű anyagi tulajdonságot definiálnánk az objektumhoz:

title

Specular maps


Észrevehetjük, hogy a specular hatás most egy kicsit furán néz ki, mivel az objektumunk nagy része fából áll, de mind a fa mind a kis fém szegély ugyanúgy reagál a specular komponensre. Ha a specular komponenst vec(0)-ra állítanánk, akkor nem lenne ez a fura hatás, azonban a fémes rész sem csillogna. Ennek megoldására egy specular mapet fogunk definiálni, ami megszabja, hogy az objektum mely fragmentjein érvényesülsün a specular hatás. A következő képet fogjuk specular mapként használni, ahol a fekete pixelek jelzik ahol nem lesz specular hatás, minden más pixel esetén viszont érvényesülni fog:

title


A specular hatás az adott pixel "világossága" függvényében fog érvényesülni. A fekete pixel esetén 0.0 a szürke 0.5 míg a fehér pixel esetén 1.0 intenzitással fog érvényesülni. Ezt a shaderben mintavételezzük és az adott értéket fogjuk szorozni a fény specular intenzitásával. Tehát minél fehérebb egy mintavételezett pixel értéke, annál jobban csillogni fog az adott fragment régió.

Mivel a fa részeknek semennyi, vagy nagyon kevés specular hatást kellene megjelenítenie ezt a régiót a specular mapen feketére állítjuk. A fémes szegélynek viszont változó specular intenzitása lesz a szürke pixelek függvényében.

Note: Technikailag a fának is van némi specular hatása, de az sokkal kevesebb mint a fémes tárgyak esetén. Photoshop vagy valamilyen szerkesztő program segítségével könnyen létrehozhatunk hasonló specular mapeket.

Töltsük be a specular mapet és rendeljük hozzá egy textúra unithoz:
In [ ]:
lightingShader.setInt("material.specular", 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);

Majd frissítsük a Material struktúrát, hogy egy sampler2D obhjektumon keresztül kezelje a specular hatást:

In [ ]:
struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float     shininess;
};

Mintavételezzük a specular mapet, hogy kiszámoljuk a megfelelő specular hatást:

In [ ]:
vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
FragColor = vec4(ambient + diffuse + specular, 1.0);
Ha minden rendben ment, akkor valami hasonlót kell látnunk. A fémes szélen specular csillogást tapasztalunk, de ez nem érvényesül a doboz fa részein:

title

  • A teljes forráskód: Code
  • Vertex shader: Code
  • Fragment shader: Code
  • Lamp Vertex shader: Code
  • Lamp Fragment shader: Code