This is an old revision of the document!


Laboratorul 06

Banda Grafică

Banda Grafică este un lanț de operații executate de procesoarele GPU. Unele dintre aceste operații sunt descrise în programe numite shadere (eng. shaders), care sunt scrise de programator și transmise la GPU pentru a fi executate de procesoarele acestuia. Pentru a le deosebi de alte operații executate în banda grafică, pe care programatorul nu le poate modifica, shaderele sunt numite „etape programabile”. Ele dau o mare flexibilitate în crearea de imagini statice sau dinamice cu efecte complexe redate în timp real (de ex. generarea de apă, nori, foc etc prin funcții matematice).

Folosind OpenGL sunt transmise la GPU: coordonatele vârfurilor, matricile de transformare ale varfurilor (M: modelare, V: vizualizare, P: proiecție, MV: modelare-vizualizare, MVP: modelare-vizualizare-proiecție), topologia primitivelor, texturi și ale date.

1. În etapa programabilă VERTEX SHADER se transformă coordonatele unui vârf, folosind matricea MVP, din coordonate obiect în coordonate de decupare (eng. clip coordinates). De asemenea, pot fi efectuate și calcule de iluminare la nivel de vârf. Programul VERTEX SHADER este executat în paralel pentru un număr foarte mare de vârfuri.

2. Urmează o etapă fixă, în care sunt efectuate următoarele operații:

  • asamblarea primitivelor folosind vârfurile transformate în vertex shader și topologia primitivelor;
  • eliminarea fețelor nevizibile;
  • decuparea primitivelor la frontiera volumului canonic de vizualizare (ce înseamnă?);
  • împărțirea perspectivă, prin care se calculează coordonatele dispozitiv normalizate ale vârfurilor: xd = xc/w; yd = yc/w;zd = zc/w, unde [xc,yc,zc,w] reprezintă coordonatele unui vârf în sistemul coordonatelor de decupare;
  • transformarea fereastră–poartă: din fereastra (-1, -1) – (1, 1) în viewport-ul definit de programator.

3. Următoarea etapă este Rasterizarea. Aceasta include:

  • calculul adreselor pixelilor în care se afișează fragmentele primitivelor (bucățele de primitive de dimensiune egală cu a unui pixel);
  • calculul culorii fiecărui fragment, pentru care este apelat programul FRAGMENT SHADER
  • în etapa programabilă FRAGMENT SHADER se calculează culoarea unui fragment pe baza geometriei și a texturilor; programul FRAGMENT SHADER este executat în paralel pentru un număr mare de fragmente.
  • testul de vizibilitate la nivel de fragment (algoritmul z-buffer);
  • operații raster, de exemplu pentru combinarea culorii fragmentului cu aceea existentă pentru pixelul în care se afișează fragmentul.

Rezultatul etapei de rasterizare este o imagine memorată într-un tablou de pixeli ce va fi afișat pe ecran, numit ^^frame buffer^^.

Începând cu a cincea generație de procesoare grafice integrate și OpenGL 3.x, între etapele 2 și 3 există încă o etapă programabilă, numită Geometry shader.

Shader OpenGL

Pentru implementarea de programe SHADER în OpenGL se folosește limbajul dedicat GLSL (GL Shading Language).

Legarea unui shader la programul care folosețte OpenGL este o operație complicată, de aceea vă este oferit codul prin care se încarcă un shader.

Un VERTEX SHADER e un program care se execută pentru FIECARE vertex trimis către banda grafică. Rezultatul transformărilor, care reprezintă coordonata post-proiecție a vertexului procesat, trebuie scris în variabila standard gl_Position care e folosită apoi de banda grafică. Un vertex shader are tot timpul o funcție numită main. Un exemplu de vertex shader:

#version 330
 
layout(location = 0) in vec3 v_position;
 
// Uniform properties
uniform mat4 Model;
uniform mat4 View;
uniform mat4 Projection;
 
void main()
{
    gl_Position = Projection * View * Model * vec4(v_position, 1.0);
}

Un FRAGMENT SHADER e un program ce este executat pentru FIECARE fragment generat în urma operației de rasterizare (ce înseamnă?). Fragment shader are în mod obligatoriu o funcție numită main. Un exemplu de fragment shader:

#version 330
 
layout(location = 0) out vec4 out_color;
 
void main()
{
    out_color = vec4(1, 0, 0, 0);
}

Cum legăm un obiect geometric la shader?

Legarea între obiecte (mesh, linii etc.) și shadere se face prin atribute. Datorită multelor versiuni de OpenGL există multe metode prin care se poate face această legare. În laborator vom învăța metoda specifică OpenGL 3.3 și OpenGL 4.1. Metodele mai vechi nu mai sunt utilizate decât atunci când hardware-ul utilizat impune restricții de API.

API-ul OpenGL modern (3.3+) utilizează metoda de legare bazată pe layout-uri. În această metodă se folosesc pipe-uri ce leagă un atribut din OpenGL de un nume de atribut în shader.

glEnableVertexAttribArray(2);	
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)0);

Prima comandă setează pipe-ul cu numărul 2 ca fiind utilizat. A doua comandă descrie structura datelor în cadrul VBO-ului astfel:

  • pe pipe-ul 2 se trimit la shader 3 float-uri (argument 3) pe care nu le normalizăm (argument 4)
  • argumentul 5 numit și stride, identifică pasul de citire (în bytes) în cadrul VBO-ului pentru a obține următorul atribut; cu alte cuvinte, din câți în câți octeți sărim când vrem să găsim un nou grup de câte 3 float-uri care reprezintă același lucru
  • argumentul 6 identifică offsetul inițial din cadrul buffer-ului legat la GL_ARRAY_BUFFER (VBO); cu alte cuvinte, de unde plecăm prima oară

În Vertex Shader vom primi atributul respectiv pe pipe-ul cu indexul specificat la legare, astfel:

layout(location = 2) in vec3 vertex_attribute_name;

Mai multe informații se pot găsi pe pagina de documentație Vertex Shader attribute index.

Pentru mai multe detalii puteți accesa:

Un articol despre istoria complicată a OpenGL și competiția cu Direct3D/DirectX poate fi citit aici.

Cum trimitem date generale la un shader?

La un shader putem trimite date de la CPU prin variabile uniforme. Se numesc uniforme pentru că nu variază pe durata executiei shader-ului. Ca să putem trimite date la o variabilă din shader trebuie să obținem locația variabilei în programul shader cu funcția glGetUniformLocation:

int location = glGetUniformLocation(int shader_program, "uniform_variable_name_in_shader");
  • shader_program reprezintă ID-ul programului shader compilat pe procesorul grafic
  • în cadrul framework-ului de laborator ID-ul se poate obține apelând funcția shader→GetProgramID() sau direct accesând vriabila membru shader→program

Apoi, dupa ce avem locatia (care reprezinta un offset/pointer) putem trimite la acest pointer informatie cu functii de tipul glUniform:

//void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)
glm::mat4 matrix(1.0f);
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(matrix));
 
// void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)
glUniform4f(location, 1, 0.5f, 0.3f, 0);
 
//void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2)
glUniform3i(location, 1, 2, 3);
 
//void glUniform3fv(GLint location, GLsizei count, const GLfloat *value)
glm::vec3 color = glm::vec3(1.0f, 0.5f, 0.8f);
glUniform3fv(location, 1, glm::value_ptr(color));

Functiile glUniform sunt de forma glUniform[Matrix?]NT[v?] (regex) unde:

  • Matrix - in cazul in care e prezent identifica o matrice
  • N - reprezinta numarul de variabile de tipul T ce vor fi trimise:
    • 1, 2, 3, 4 in cazul tipurilor simple
    • pentru matrici mai exista si 2×3, 2×4, 3×2, 3×4, 4×2, 4×3
  • T - reprezinta tipul variabilelor trimise
    • ui - unsigned int
    • i - int
    • f - float
  • v - datele sunt specificate printr-un vector, se da adresa de memorie a primei valori din vector

Comunicarea intre shadere-le OpenGL

In general pipeline-ul programat este alcatuit din mai multe programe shader. In cadrul cursului de EGC vom utiliza doar Vertex Shader si Fragment Shader. OpenGL ofera posibilitatea de a comunica date intre programele shader consecutive prin intermendiul atributelor in si out

In metoda specifica OpenGL 3.3 numele de atribut attribute_name trebuie sa fie acelasi atat in Vertex Shader cat si in Fragment Shader pentru a se stie legatura intre input/output.

Vertex Shader:

#version 330  // GLSL version of shader (GLSL 330 means OpenGL 3.3 API)
 
out vec3 attribute_name;

Fragment Shader:

in vec3 attribute_name;

In caz ca avem support pentru GLSL 410 (OpenGL 4.1) se poate specifica si locatia attributului astfel, caz in care doar locatiile vor fi folosite pentru a lega iesirea unui Vertex Shader de intrarea la Fragment Shader si nu numele atributului.
Mai multe detalii se pot obtine de la: Program separation linkage

Vertex Shader:

#version 410  // GLSL 410 (OpenGL 4.1 API)
 
layout(location = 0) out vec4 vertex_out_attribute_name;

Fragment Shader:

#version 410
 
layout(location = 0) in vec4 fragment_in_attribute_name;

Cerinte laborator

tasta F5 - reincarca shaderele in timpul rularii aplicatiei. 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.

  1. Completati functia RenderSimpleMesh astfel inca sa trimiteti corect valorile uniform catre Shader
    • Se interogeaza locatia uniformelor “Model”, “View” si “Projection”
    • Folosind glUniformMatrix4fv sa se trimita matricile corespunzatoare catre shader
    • Daca ati completat corect functia, si ati completat gl_Position in vertex shader, ar trebui sa vedeti un cub pe centrul ecranului rottit 45 grade in jurul lui Y si colorat variat
  2. Completati Vertex Shaderul
    1. Se de clara atributele de intrare pentru Vertex Shader folosind layout location
      	layout(location = 0) in vec3 v_position;
      	// same for the rest of the attributes ( check Lab6.cpp CreateMesh() );
    2. Se declara atributele de iesire catre Fragment Shader
      	out vec3 frag_color;
      	// same for other attributes
    3. Se salveza valorile de iesire in main()
      	frag_color = vertex_color;
      	// same for other attributes
    4. Se calculeaza pozitia in clip space a vertexului primit folosind matricile Model, View, Projection
      	gl_Position = Projection * View * Model * vec4(v_position, 1.0);
  3. Completati Fragment Shaderul
    • Se primesc valorile atributelor trimise de la Vertex Shader
    • Valoarea de intrare ale fiecarui atribut e calculata prin interpolare liniara intre vertexii ce formeaza patch-ul definit la desenare (triunghi, linie)
      	in vec3 frag_color;
    • Se calculeaza valoarea fragmentului (pixelului) de output
      	out_color = vec4(frag_color, 1);
  4. Sa se utilizeze normala vertexilor pe post de culoare de output in cadrul Fragment Shader-ului
    • Inspectati de asemenea structura VertexFormat pentru a intelege ceea ce se trimite pe fiecare pipe
  5. Sa se interschimbe pipe-ul 1 cu pipe-ul 3. Trimiteti normala pe pipe-ul 3 si culoarea vertexului pe pipe-ul 1
    • Se inspecteaza rezultatul obtinut
  6. Bonus: sa se trimita timpul aplicatiei (Engine::GetElapsedTime()), si sa se varieze pozitia si culoarea (unul sau mai multe canale de culoare) dupa o functie de timp (trigonometrica etc.)
egc/laboratoare/06.1572684595.txt.gz · Last modified: 2019/11/02 10:49 by andrei.lambru
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