Utilizzando il nuovo paradigma di programmazione OpenGL, è evidente l'impor- tanza che assumono gli shader nella realizzazione della nuova VR3Lib. In questa sezione presentiamo dunque alcuni dettagli sull'utilizzo degli shader nella versione 3.3 di OpenGL.
Uno shader è un programma scritto in un apposito linguaggio di programmazione che viene compilato in un eseguibile pensato per l'esecuzione su uno degli stadi della
pipeline graca usata per il rendering. Le istruzioni di cui è composto uno shader sono tutte pensate per il rendering e normalmente uno shader fa del processing al solo scopo di ottenere determinati eetti sulla scena visualizzata.
Nonostante non sia sempre vero, attualmente l'oggetto prodotto dalla compila- zione di uno shader viene normalmente eseguito su dei processori propri della GPU (Graphics Processing Unit, parte dell'adattatore graco) e non all'interno della CPU. Le GPU sono infatti ottimizzate per le operazioni che si trovano tipicamente negli shader e per i calcoli in virgola mobile che sono assai comuni durante il rendering. Una GPU è normalmente in grado di eseguire le operazioni specicate da uno shader su diversi core in parallelo, per processare diversi vertici o pixel allo stesso tempo. Solo raramente uno shader può venire eseguito come un programma ordinario sul processore centrale (CPU), qualora ad esempio delle istruzioni utilizzate non siano supportate dal particolare adattatore graco: questa situazione si chiama software fallback e normalmente risulta in bassi frame rate che impediscono la realizzazione di tecniche di rendering complesse per mantenere la responsiveness dell'applicazione ai comandi dell'utente, dunque è normalmente da evitare.
I linguaggi che possono venire usati per la scrittura di shader sono molteplici, in particolare si dividono in due categorie:
• Linguaggi utilizzati nel rendering oine
Equipaggiati con funzionalità speciche per ottenere la migliore qualità pos- sibile senza mirare ad elevate prestazioni (utilizzati ad esempio nel tipo di rendering con cui si ottengono i lm in computer graphics).
• Linguaggi utilizzati nel rendering in tempo reale
Utilizzati in applicazioni grache interattive che devono mantenere un cer- to frame rate per ottenere risultati soddisfacenti, mirano ad ottimizzare le performance e sono progettati per venire eseguiti direttamente sulla GPU. Utilizzando OpenGL per realizzare un motore graco capace di visualizzare ambienti virtuali complessi in modo interattivo, dobbiamo ricorrere alla seconda categoria di linguaggi di shading. Notare che anche in questo caso `tempo reale' si riferisce semplicemente al fatto che come target principale abbiamo le prestazioni.
Alcuni dei linguaggi più conosciuti in questo ambito sono: • OpenGL Shading Language (GLSL)
Questo specico linguaggio è pensato per venire utilizzato con OpenGL e al- cune chiamate dell'API sono appunto previste per gestire shader scritti in GLSL.
CAPITOLO 1. INTRODUZIONE 29 • High Level Shader Language (HLSL)
Linguaggio proprietario Microsoft pensato per venire utilizzato insieme al sistema graco Microsoft Direct3D, analogo a GLSL nei confronti di OpenGL. • C for Graphics (Cg)
Sviluppato da Nvidia in collaborazione con Microsoft, può venire utilizza- to sia nel sistema graco OpenGL che in Direct3D usando librerie apposite distribuite insieme alle speciche del linguaggio.
Lavorando con OpenGL, e dato che tutti gli shader necessari nella nuova libreria graca dovranno essere scritti da zero, la scelta ricade su GLSL (più facile da gestire con l'API OpenGL rispetto a Cg).
Le speciche GLSL, inizialmente approvate dall'OpenGL Architectural Review Board (ARB) come estensione nella versione 1.5 dell'API, sono state inserite nel core OpenGL dalla versione 2.0. Il linguaggio GLSL viene normalmente potenziato e modicato con le nuove revisioni di OpenGL, portando a nuove versioni della specica GLSL.
Lavorando con la versione 3.3 dell'API OpenGL, gli shader scritti per il funziona- mento della VR3Lib useranno la versione 3.30 di GLSL (corrispondente a OpenGL 3.3). Nella versione 3.30, GLSL può venire usato per programmare 3 diversi tipi di processori nella pipeline OpenGL:
1. Vertex Processor
Questo stadio programmabile lavora sui singoli vertici manipolando tutti i dati ad essi associati (ad esempio la posizione). I programmi che possono venire compilati per lavorare su questi processori sono detti vertex shader e quando uno o più vertex shader vengono compilati e collegati insieme si ottiene un vertex shader executable, un oggetto che viene eseguito su un vertex processor. 2. Geometry Processor
Questo stadio programmabile lavora su insiemi di vertici assemblati in una primitiva dopo il processing da parte dei vertex shader. I programmi che possono venire compilati per lavorare su questi processori sono detti geometry shader e quando uno o più geometry shader vengono compilati e collegati insieme si ottiene un geometry shader executable, un oggetto che viene eseguito su un geometry processor.
3. Fragment Processor
rizzazione. I programmi che possono venire compilati per lavorare su questi processori sono detti fragment shader e quando uno o più fragment shader vengono compilati e collegati insieme si ottiene un fragment shader executable, un oggetto che viene eseguito su un fragment processor. La parola `fragment' ha in quest'ottica lo stesso signicato di `pixel', anche se bisogna ricordare che non è detto che i frammenti processati da un fragment processor arrivino nel framebuer (diventando eettivamente pixel sullo schermo).
I 3 diversi tipi di eseguibili vengono normalmente assemblati in un unico oggetto che si dice shader program. Mentre la presenza di un vertex shader executable e di un fragment shader executable è obbligatoria per qualsiasi rendering, non è obbligatorio prevedere un geometry shader executable (dunque si può fare a meno dei geometry shader qualora le primitive assemblate non debbano essere modicate).
Per chiarire il funzionamento, la pipeline OpenGL 3.3 può venire rappresentata come in g. 1.10, dove si ha un livello di dettaglio superiore rispetto alle precedenti illustrazioni, ma abbiamo al solito introdotto alcune semplicazioni.
Figura 1.10: Pipeline OpenGL 3.3 e shader
Notare che non è facile determinare a priori il numero di esecuzioni di ogni tipo di shader executable pur sapendo cosa si vuole disegnare.
CAPITOLO 1. INTRODUZIONE 31 Supponendo infatti di voler disegnare un triangolo come una singola primitiva, sfruttando anche un geometry shader che sposta i 3 vertici dello stesso, abbiamo che:
• il vertex shader executable dovrà venire eseguito 3 volte (una volta per ogni vertice);
• il geometry shader executable dovrà venire eseguito 1 singola volta (una volta per ogni primitiva);
• il fragment shader executable dovrà venire eseguito un numero di volte pari al numero di pixel nel framebuer che risultano interessati dalla primitiva in esame dopo lo stadio di rasterizzazione (potenzialmente migliaia).
Ogni tipo di eseguibile opera su ingressi diversi e inoltra allo stadio successivo il risultato del processing:
• Il vertex shader executable opera sui dati dei vertici in ingresso provenien- ti da buer predisposti dall'utente; in generale si utilizzeranno dei VBO per immagazzinare i dati che verranno processati dal vertex processor. L'esegui- bile inoltre produce in uscita dei dati relativi ai singoli vertici che vengono consumati dal successivo stadio della pipeline.
• Il geometry shader executable opera su dati relativi a intere primitive prove- nienti dallo stadio di assemblaggio delle primitive. In uscita abbiamo invece nuovi dati relativi alle primitive trasformate dal geometry shader.
• Il fragment shader executable opera su dati provenienti dallo stadio di raste- rizzazione sui singoli frammenti. In uscita abbiamo dati relativi ai frammen- ti processati (informazioni di colore, profondità e altro) che subiranno altre operazioni prima di nire eventualmente nel framebuer.
L'unico eseguibile su cui l'utente ha controllo diretto per quanto riguarda i dati di ingresso è allora il vertex shader executable.
Ogni tipo di shader può passare agli shader che lavorano negli stadi successivi delle variabili che prendono il nome di varying variables, queste possono venire trasformate in vario modo passando da uno shader ad un altro. Ad esempio, un vertex shader può denire una variabile varying di uscita che contiene per ogni vertice processato la somma delle 3 componenti spaziali della posizione del vertice; supponendo che lo stadio di geometry shading venga bypassato perché non è previsto
un geometry shader executable, il fragment shader riceverà dei valori su una variabile di ingresso varying corrispondente a quella in uscita dal vertex shader. I valori per i singoli frammenti verranno prodotti a partire da quelli dei vertici nello stadio di rasterizzazione e in un modo dipendente da come l'utente ha scritto gli shader. Le speciche GLSL deniscono anche un set di variabili varying predenite per ogni tipo di shader, che possono venire lette o modicate durante le operazioni (inuenzando magari il comportamento degli stadi successivi nella pipeline).
Un altro tipo di variabili che possono essere accedute in sola lettura durante l'e- secuzione dello shader program sono le uniform variables. Queste variabili vengono impostate dall'utente prima di lanciare in esecuzione il programma e mantengono il valore no a che lo shader program non subisce un nuovo collegamento (linking).
Per quanto riguarda le trasformazioni eettuate automaticamente dal sistema graco OpenGL, le trasformazioni di modeling, viewing e projection sono a ca- rico dell'utente e vanno eseguite all'interno del vertex shader. Le altre trasfor- mazioni (perspective division e viewport transformation) vengono invece eettua- te automaticamente dal sistema graco prima di arrivare allo stadio di fragment processing.