Laboratorul 08

Modelarea reflexiei luminii

Va reamintim formulele pentru calculul culorii intr-un punct al unei suprafețe:

$culoarea = c_e + c_a + c_d + c_s$

Emisiva: $c_e = K_e$

Ambientala: $c_a = I_a \cdot K_a$

Difuza: $c_d = K_d \cdot I_{sursă} \cdot max(\vec{N}\cdot \vec{L}, 0)$

Speculara: $c_s = K_s \cdot I_{sursă} \cdot lum \cdot (max(\vec{N}\cdot \vec{H}, 0)^n $, unde $lum = (\vec{N}\cdot \vec{L}>0) ? 1 : 0$

Dacă introducem mai multe lumini în scenă și ținem cont și de factorul de atenuare, atunci culoarea intr-un punct al unei suprafețe este:

$culoarea = K_e + I_a \cdot K_a + \sum{f_{at_i} \cdot I_{sursă_i} (K_d \cdot max(\vec{N}\cdot \vec{L_i}, 0) + K_s \cdot lum_i \cdot (max(\vec{N}\cdot \vec{H_i}, 0)^n})$

La laboratorul de saptamana trecuta, pentru ușurința implementării, am considerat mai multe simplificări:

  • am considerat că toate constantele de material $K_e, K_a, K_d, K_s$, sunt variable de tip float (un singur canal)
  • deoarece constantele de material au fost considerate pe un singur canal, s-a introdus variabila uniformă object_color, o variabilă de tip vec3 care a modelat culoarea obiectului
  • am considerat că intensitatea sursei de lumină este o constantă float (cu valoarea 1)
  • am ignorat culoarea emisă
  • am înlocuit constanta de material $K_a$ cu constanta $K_d$ (pentru a trimite mai putine uniforme)
  • am considerat intensitatea luminii ambientale o constantă float (cu valoarea 0.25)

Totuși, trebuie să menționăm că modelul complet urmărește formula de mai sus, unde constantele de material $K_e, K_a, K_d, K_s$ sunt diferite și au 3 canale $(R,G,B)$, iar intensitatea luminii ambientale și intensitatea sursei de lumină au de asemenea 3 canale. Expresia luminii se evaluează separat pentru cele trei canale.

Iluminare Phong in Fragment Shader

Modelul de iluminare aplicat in cazul implementarii in fragment shader este acelasi cu cel studiat in Laboratorul 07, din punct de vedere matematic. Totusi, exista o diferenta majora intre cele doua implementari prin faptul ca iluminarea nu se mai aplica la nivelul fiecarul vertex ci la nivel de fragment. Rezultatul final este superior calitativ intrucat iluminarea fiecarui fragment nu se va mai calcula pe baza interpolarii luminii calculate la nivel de vertex ci pe baza normalei si pozitiei in spatiu a fiecarui fragment.

Valorile de intrare primite de fragment shader sunt interpolate linar intre valorile vertexilor ce compun primitiva utilizata la desenare.

Imaginea de mai sus este obtinuta prin desenarea unui triunghi avand cele 3 varfuri de culori diferite: rosu, verde, albastru

Prin transmiterea culorii de la Vertex Shader la Fragment Shader culoarea fiecarui fragment de pe suprafata triunghiului este calculata ca o interpolare linara intre culorile vertexilor ce compun primitiva specificata (in acest caz, un triunghi).

Acelasi procedeu se aplica pentru orice alta proprietate, cum ar fi:

  • pozitia in spatiul lume a unui fragment (daca trimitem pozitiile vertexilor)
  • normala in spatiul lume a unui fragment (daca trimitem normalele vertexilor)
  • orice alta valoarea transmisa de la vertex shader la fragment shader
  • etc

Modelul de interpolarea implicit utilizat (smooth) calculeaza interpolarea tinand cont si de perspectiva (se face o interpolare perspectiva).
API-ul OpenGL permite specificarea modelului de interpolare prin utilizarea unor termeni specifici in cadrul Fragment Shaderului:

  • flat​ - valoarea nu va fi interpolata
  • smooth - interpolare perspectiva (implicita)
  • noperspective​ - interpolare liniara in spatiu fereastra

Mai multe detalii despre modelele de interpolare se pot gasi accesind urmatoarele resurse:

Astfel, utilizand valorile interpolate de pozitie si normala (in spatiul lume) putem sa calculam modeul de iluminare Phong pentru fiecare fragment al unei primitive rasterizate, rezultatul final fiind mult superior intrucat prin interpolarea normalelor se obtine o trecere lina intre suprafete adiacente (sunt interpolate normalele de pe muchii), deci si iluminarea finala va oferi impresia unei suprafete netede. Astfel poligoanele componente ale obiectelor nu vor mai aparea vizibil in imagine.

Detalii de implementare

  1. Se calculeaza world_position si world_normal in Vertex Shader ca in Laboratorul 07
  2. Se transmit cele 2 valori catre Fragment Shader
  3. Se aplica calculul luminii (componenta ambientala, difuza, speculara) in Fragment Shader

Pentru a primi valoarea unei variabile de tip uniform este suficient sa declarati respectiva variabila in shaderul in care este necesara. Deci, NU trimiteti valoarea unei variabile de la Vertex Shader la Fragment Shader

// Vertex Shader
uniform vec3 light_position;
// Fragment Shader
uniform vec3 light_position;

Iluminare Spot-light

Nu toate sursele de lumina sunt punctiforme. Daca dorim sa implementam iluminarea folosind o sursa de lumina de tip spot trebuie sa tinem cont de o serie de constrangeri

Asa cum se poate vedea si in poza pentru a implementa o sursa de lumina de tip spot avem nevoie de urmatorii parametri aditionali:

  • orientarea spotului (directia luminii)
  • unghiul de cut-off al spotului ce controleaza deschiderea conului de lumina
  • un model de atenuare unghiular al luminii ce tine cont valoarea de cut-off a spot-ului

Astfel, punctul P se afla in conul de lumina (primeste lumina) daca conditia urmatoare este indepilita:

float cut_off = radians(30.0f);
float spot_light = dot(-L, light_direction);
if (spot_light > cos(cut_off))
{
	// fragmentul este iluminat de spot, deci se calculeaza valoarea luminii conform  modelului Phong
	// se calculeaza atenuarea luminii
}

Pentru a simula corect iluminarea de tip spot este nevoie sa tratam si atenuarea luminii corespunzatoare apropierii unghiului de cut-off. Putem astfel sa utilizam un model de atenuare patratica ce ofera un rezultat convingator.

float cut_off = radians(30.0f);
float spot_light = dot(-L, light_direction);
float spot_light_limit = cos(cut_off);
 
// Quadratic attenuation
float linear_att = (spot_light - spot_light_limit) / (1.0f - spot_light_limit);
float light_att_factor = pow(linear_att, 2);

Iluminarea suprafețelor folosind mai multe lumini

Pentru a simula mai multe lumini, putem scrie un shader care să calculeze contribuția fiecărei lumini în parte. Pentru a fi ușor să scriem codul de shadere, acesta poate fi modularizat.

În GLSL se pot defini funcții similar ca în limbajul C. Putem scrie o funcție pentru a calcula pentru o sursă de lumină culoarea rezultată din componentele difuze și speculare. Un exemplu de funcție ar fi:

vec3 point_light_contribution(vec3 light_pos, vec3 light_color)
{
	vec3 color;
	//calculele componentelor difuze si speculare din modelul Phong de iluminare pentru lumina punctiforma.
	return color;
}

Putem accesa orice variabilă globală din orice funcție din cod, inclusiv uniforme. Astfel putem citi în funcția point_light_contribution normala suprafeței primită ca variabilă de intrare la fragment shader și uniformele cu constante de material, fără să le trimitem ca parametri.

Putem specifica ce tip de parametrii are funcția:

  • in înseamnă că valoarea va fi copiată când se apelează funcția. Funcția poate modifica parametrul cum dorește
  • out înseamnă că valoarea nu fi inițializată de apelant și după ce funcția modifică parametrul valoarea va fi copiată în variabila corespunzătoare apelantului
  • inout le combină pe cele două

Cuvintele cheie in, out si inout se scriu înainte de tipul de data al parametrului funcției. De exemplu, dacă dorim separat contribuția difuză și cea speculară, un exemplu de semnatură de funcție ar fi:

void point_light_contribution(vec3 light_pos, vec3 light_color, out vec3 diffuse_contribution, out vec3 specular_contribution);

Dacă nu se specifică, parametrul este de tip in. Astfel, putem construi funcții ce întorc mai multe valori.

Metoda de declarare și definiție a funcțiilor este similară cu cea din C.

vec3 point_light_contribution(vec3 light_pos, vec3 light_color);
 
void main()
{
	//...
}
 
vec3 point_light_contribution(vec3 light_pos, vec3 light_color)
{
	//...
}

Nu este permisă recursivitate în GLSL.

Pentru a trimite ușor multe surse de lumină, putem defini vectori de uniforme. De exemplu, pentru a trimite mai multe surse de lumină punctiforme se declară în shader un vector de uniforme:

uniform vec3 point_light_pos[9];
uniform vec3 point_light_color[9];

Pe urmă putem trimite uniformele cu un apel în cod:

glm::vec3 point_light_pos[9];
glm::vec3 point_light_color[9];
 
GLuint location = glGetUniformLocation(program, "point_light_pos");
glUniform3fv(location, 9, glm::value_ptr(point_light_pos[0]));
//glm::value_ptr intoarce adresa de memorie unde se gasesc datele unui vector, matrici etc.

O altă variantă este declararea unei structuri în shader

struct light_source
{
   int  type;
   vec3 position;
   vec3 color;
   vec3 direction;
};
 
uniform light_source lights[9];

În GLSL structura este doar o definiție a unei agregări de tipuri de dată. Nu se poate obține locația unei structuri sau a unui vector de structuri. În schimb, putem interoga locația fiecărui membru din fiecare element din vectorul de structuri. De exemplu, se poate găsi locația uniformei lights[0].position.

Următorul cod C++ trimite pozițiile surselor de lumină în shader:

light_source light_sources[9]; //light_source e o structura declarata similar cu cea scrisa in shader.
for (int i = 0;i < 9;++i)
{
    std::string name = std::string("lights[") + std::to_string(i) + std::string("].position");
    GLuint location = glGetUniformLocation(program, name.c_str());
    glUniform3fv(location, 1, glm::value_ptr(light_sources[i].position));
}

Cerinte laborator

tasta F5 - reincarca shaderele in timpul rularii aplicatiei.

In cazul in care ati modificat doar sursele shader nu este nevoie sa opriti aplicatia intrucat shaderele sunt compilate si rulate de catre placa video si nu au legatura cu codul sursa C++ propriu zis, iar framework-ul ofera suport pentru reincarcarea acestora la runtime.

  1. Sa se implementeze iluminarea de tip Phong in Fragment Shader
  2. Atunci cand se apasa tasta F sa se treaca in modul de iluminare Spot-light
    • Directia de ilumiare este transmisa ca uniform vec3 light_direction
    • Nu uitati sa aplicati un model de atenuare al luminii in functie de apropierea fragmentelor de unghiul de cut-off
  3. Sa se adauge în scenă o nouă sursă de lumină si sa se calculeze iluminarea

[Bonus]

  • Sa se modifice directia si unghiul de cut-off al luminii spotlight de la tastatura
    • logica in OnInputUpdate
    • rotirea spotului: sus, jos, stanga, dreapta
    • 2 taste pentru a creste/micsora unghiul de iluminare al spot-ului
egc/laboratoare/08.txt · Last modified: 2023/12/06 12:29 by anca.cristea
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0