Approfondimenti
di Michael J. Hammel traduzione di Antonio Cartelli


BMRT

Esempio I di Gritz
Immagine gentilmente offerta da Larry Gritz
    Parte II: Ombreggiatori Renderman
  1. Una breve rassegna
  2. Cos'è un ombreggiatore?
  3. Compilazione di ombreggiatori
  4. Tipi di ombreggiatori
  5. Sintassi del linguaggio degli Ombreggiatori
    1. Nomi di ombreggiatori
    2. Variabili e loro visibilità
    3. Tipi di dati ed espressioni
    4. Funzioni
    5. Strutture di controllo del linguaggio
    6. Sistemi di coordinate
  6. Formato di un file ombreggiatore
  7. Qualche parola sulle funzioni di retino
  8. Esempi di riferimento
    1. Modello di croce colorata
    2. Aggiunta di opacità - un ombreggiatore a reticolo
    3. Un semplice ombreggiatore stile foglio di carta
    4. Una lavana mappata con retino
    5. Esempio di applicazione di spostamento
indent
indent

1. Una breve rassegna

      Prima di iniziare con gli ombreggiatori diamo nuovamente una rapida occhiata ai file RIB; si tratta di file di testo in formato ASCII che descrivono scenari 3D ad un rappresentatore conforme alle specifiche RenderMan, quale può essere BMRT. Un file RIB contiene le descrizioni di oggetti - le loro dimensioni, la loro posizione nello spazio tridimensionale, le luci che li illuminano e così via. Gli oggetti hanno superfici che possono essere colorate e retinate, in modo da ottenere effetti di riflettività, opacità (o viceversa trasparenza), rappresentazioni di irregolarità o qualsiasi altro aspetto si voglia.
      Un oggetto è istanziato all'interno di marcatori AttributeBegin/AttributeEnd (o procedure nel collegamento con il C). Tali istanze hanno come effetto il salvataggio dello stato grafico corrente in modo che ogni ulteriore cambiamento di tale stato (a mezzo di colorazione e retinatura dell'istanza dell'oggetto) all'interno del blocco AttributeBegin/AttributeEnd non abbia effetto su ulteriori altri oggetti. Lo stato grafico corrente può essere modificato e gli oggetti possono essere colorati e retinati per mezzo di particolari procedure che prendono il nome di ombreggiatori.
      Nota: Si tenga sempre presente che queste pagine non vogliono né possono sostituirsi ad un vero e proprio corso di formazione e che non saranno pertanto affrontate tutte le possibili problematiche connesse all'uso ed alla progettazione di ombreggiatori. Informazioni dettagliate sui vari aspetti che vengono presentati possono essere reperite nei testi citati nella bibliografia alla fine di questo articolo.

2. Cos'è un ombreggiatore?

      Nei precedenti articoli ho spesso utilizzato i termini ombreggiatura e retinatura in maniera intercambiabile. Darwyn Peachy, nel suo Building Procedural Textures (Come realizzare retini procedurali) capitolo del testo Retinature e Modellizzazione: Un approccio procedurale, asserisce che questi due concetti sono attualmente collegati a processi separati:
Ombreggiatura è il processo che porta al calcolo del colore di un pixel a partire dalle caratteristiche di una superficie specificata dall'utente e dal modello di ombreggiatore. Retinatura, invece, è un metodo con il quale si modificano le proprietà di una superficie da punto a punto in modo che venga restituita una percezione dei dettagli superficiali che non è presente nella geometria della superficie stessa. [1]
Un ombreggiatore è una procedura chiamata dal rappresentatore per applicare colori e retini ad un oggetto. Questo può comprendere la superficie di corpi quali blocchi o sfere, lo spazio all'interno di un oggetto solido, o perfino lo spazio tra oggetti (l'atmosfera). Sebbene accada che, se ci si basa sulla descrizione data da Peachy, gli ombreggiatori risultano essere in grado di gestire soltanto la colorazione delle superfici (o dell'atmosfera. ecc.), nell'ambiente RenderMan essi gestiscono di fatto sia le ombreggiature che le retinature.

3. Compilazione di ombreggiatori

      I file RIB si presentano come file con un'estensione ".rib". In maniera del tutto analoga i file di ombreggiatura hanno l'estensione ".sl", almeno nel caso del codice sorgente di ombreggiatura. A differenza dei file RIB, però, i file di ombreggiatura non possono essere utilizzati direttamente dal rappresentatore nel loro formato sorgente, devono essere compilati da un compilatore di ombreggiature. Nel pacchetto software BMRT il compilatore di ombreggiature è detto slc.
      La compilazione di ombreggiatori è pressoché banale - basta utilizzare il programma slc fornendogli come parametro il nome del file sorgente di ombreggiatura. A titolo di esempio si consideri il caso di un file sorgente di ombreggiatura: myshader.sl, per compilarlo basta dare il seguente comando: slc myshader.sl
Si noti che va scritto il suffisso ".sl" - il file sorgente di ombreggiatura non si può specificare utilizzando esclusivamente il nome del file senza alcuna estensione. Quando il compilatore avrà finito verrà creato il file compilato dell'ombreggiatore: myshader.so, nell'area corrente. Un breve esame del file mostra che si tratta ancora di un file di testo ASCII, anche se il formato di tale file è specifico per quel rappresentatore, dovendo implementare il suo stack degli stati grafici.
Nota: l'estensione del file ".so" utilizzata da BMRT (diversa da quella utilizzata da PRMan) non significa che si ha a che fare con un file oggetto binario, così come accade per le librerie condivise di file oggetto. Il file che si ottiene è un file di testo ASCII. Larry mi ha detto che sta considerando l'opportunità, per il futuro, di modificare il nome dell'estensione al fine di evitare la possibilità di confusione con file oggetto condivisi.
      Si noti che nel file RIB (o in quello analogo allorché si utilizza il collegamento con il C) la chiamata alla procedura di ombreggiatura si fa nella maniera seguente:
	       AttributeBegin
		  Color [0.9 0.6 0.6]
		  Surface "myshader"
		  ReadArchive "object.rib"
	       AttributeEnd
Questo esempio utilizza un ombreggiatore di superficie (parleremo tra poco dei vari tipi di ombreggiatori). Il nome tra doppi apici è il nome della procedura di ombreggiatura e non deve necessariamente essere uguale a quello del file sorgente di ombreggiatura. Poiché gli ombreggiatori sono procedure hanno nomi di procedure. Nell'esempio precedente il nome della procedura è myshader. È un po' come se prendessimo in considerazione solo il nome del file di ombreggiatura, senza considerare il suffisso. In ogni caso il compilatore di ombreggiature non si preoccupa minimamene del nome del file sorgente, se non che per sapere quale file compilare. Il nome di file prodotto in uscita per il file con l'estensione .so è il nome della procedura. Accade allora che se si da alla procedura un nome diverso da quello del file sorgente si otterrà un file compilato .so che si chiama in maniera diversa. Sebbene ciò non rappresenti necessariamente una cosa sbagliata, l'esito che ne risulta è un incremento notevole della difficoltà di tener conto degli effetti dei vari ombreggiatori. In ogni caso il nome della procedura è il nome utilizzato nel RIB (o nel collegamento col C) quando si va a chiamare l'ombreggiatore. Nell'esempio precedente "myshader" è il nome della procedura, non il nome del file sorgente.

4. Tipi di ombreggiatori

      Secondo quanto riportato nella Guida RenderMan [2]
L'interfaccia RenderMan specifica sei tipi di ombreggiatori, distinti per il tipo di ingressi che utilizzano e per il tipo di dati in uscita che producono.
Il testo prosegue con la descrizione dei seguenti tipi di ombreggiatori:
  1. Ombreggiatori di sorgenti luminose
  2. Ombreggiatori di superficie
  3. Ombreggiatori di volume
  4. Ombreggiatori di spostamento
  5. Ombreggiatori di trasformazione
  6. Ombreggiatori di immagine
La maggior parte di questi può avere una sola istanza per volta del tipo di ombreggiatore, attiva nello stato grafico. Ad esempio, ci può essere un solo ombreggiatore di superficie alla volta in uso per ogni oggetto o per vari oggetti. L'eccezione alla regola è rappresentata dagli ombreggiatori luminosi, che possono avere diverse istanze attive per volta, alcune delle quali possono non essere predisposte per taluni oggetti.

Ombreggiatori di sorgenti luminose
      Le sorgenti luminose nel linguaggio degli ombeggiatori RenderMan sono dotate di una posizione ed una direzione e forniscono in risposta il colore della luce emessa da quella sorgente e che colpisce il punto corrente della superficie. Le specifiche RenderMan descrivono un insieme di ombreggiatori luminosi di default molto utili e che probabilmente coprono la maggior parte delle esigenze più comuni che un utilizzatore medio avrà occasione di incontrare. Questi ombreggiatori di default includono la luce ambiente (la stessa quantità di luce irradiata in tutte le direzioni), sorgenti luminose distanti (come il sole), luci puntiformi, riflettori e luci superficiali. Tutte le sorgenti luminose hanno un'intensità che definisce la brillantezza della luce. Le luci possono essere realizzate per proiettare ombre oppure no. Maggiore è il numero delle luci che proiettano ombre all'interno della scena, maggiore sarà la sensazione di gradevolezza nella rappresentazione dell'immagine finale. Durante la progettazione e la verifica della scena è spesso vantaggioso impedire la generazione delle ombre da parte della maggioranza delle luci. Quando la scena è pronta per la sua rappresentazione finale allora conviene riattivare la generazione delle ombre.
      Le luci d'ambiente possono essere utilizzate per rendere più luminosa un'immagine piuttosto scura, anche se l'effetto può essere "ingannevole" ed avere come conseguenza un'immagine slavata che perde tutto il suo realismo. Le luci d'ambiente dovrebbero essere tenute basse in ogni scena, diciamo pure con un livello di intensità non superiore a 0.03. Le sorgenti luminose distanti forniscono una luce che si propaga in una determinata direzione mediante raggi paralleli: il sole è l'esempio più comune di sorgente luminosa distante, ma anche le stelle vengono considerate delle buone sorgenti luminose distanti. Se una scena deve essere illuminata da luce solare è spesso considerata un'ottima soluzione la scelta di sorgenti luminose distanti quali uniche luci in grado di generare ombre. Le sorgenti luminose distanti non hanno posizione, hanno solo direzione.
      I riflettori sono le più familiari sorgenti luminose che hanno una collocazione ben determinata nello spazio e che emettono un fascio luminoso in una determinata direzione coprendo un'area ben precisa delimitata da un cono il cui vertice è il riflettore stesso. L'intensità luminosa all'interno del fascio generato da un riflettore decresce esponenzialmente al variare dell'angolo di apertura del cono. L'angolo, misurato tra l'asse e la direttrice, è espresso in radianti, non in gradi come in POV-Ray. Specificare l'angolo in gradi può avere l'effetto di causare un'eccessiva sovrailluminazione dell'area coperta dal riflettore. La luminosità dei punti luce decresce in intensità al variare della distanza dalla sorgente.
      Un punto luce irradia luce in tutte le direzioni per cui tra i suoi parametri non ha la direzione ma solo la posizione.
Le luci superficiali sono costituite da una serie di punti luce che assumono l'aspetto e la disposizione della superficie dell'oggetto al quale sono collegate. In tal modo la durezza delle ombre proiettate da un punto luce può essere diminuita mediante la creazione di un piano emittente di superficie maggiore. A dir la verità non sono riuscito ad apprendere molto sull'uso delle luci di superficie per cui non sono in grado di fornire ulteriori indicazioni sulle modalità del loro utilizzo.
      Molti ombreggiatori di sorgenti luminose utilizzano una delle due funzioni di illuminazione disponibili: illuminate() e solar(). Entrambe forniscono le modalità di integrazione di sorgenti luminose su una superficie con un cono finito. illuminate() richiede che venga specificata la posizione della sorgente luminosa, mentre solar() è utilizzata per sorgenti luminose distanti, quali potrebbero essere il sole o le stelle. Ritengo che la scrittura di ombreggiatori per sorgenti luminose possa ritenersi un tema avanzato rispetto a quello della creazione di ombreggiatori per sorgenti luminose ordinarie che può ritenersi più che sufficiente per il principiante cui questo articolo è rivolto. I lettori sono invitati a consultare la Guida RenderMan e le Specifiche RenderMan per approfondimenti e dettagli sull'uso degli ombreggiatori di default.

Ombreggiatori di Superficie
      Gli ombreggiatori di superficie sono sicuramente uno dei due tipi di ombreggiatore di cui i neofiti fanno più spesso uso (l'altro tipo sono gli ombreggiatori di spostamento). Gli ombreggiatori di superficie sono utilizzati per determinare il colore della luce riflessa da un determinato punto di una superficie in una particolare direzione. Gli ombreggiatori di superficie sono utilizzati per creare i trucioli di legno o i colori di un bulbo oculare. Essi definiscono anche l'opacità di una superficie, ovvero la quantità di luce che può passare attraverso un punto (trasparenza di un punto). Un punto completamente opaco non consente alla luce di passare mentre un punto completamente trasparente non riflette alcuna luce.
      La maggior parte degli esempi che segue riguarda gli ombreggiatori di superficie. Uno di essi si occuperà di un ombreggiatore di spostamento.

Ombreggiatori di Volume
      Un ombreggiatore di volume coinvolge la luce che si dirige verso la camera da presa allorché passa attraverso ed intorno agli oggetti di una scena.
Gli ombreggiatori di volume interno determinano l'effetto che si ha sulla luce che passa attraverso un oggetto. Gli ombreggiatori di volume esterno riguardano la luce che attraversa lo "spazio vuoto" intorno ad un oggetto. Gli ombreggiatori atmosferici gestiscono lo spazio tra oggetti. Ombreggiatori di volume, sia interni che esterni, differiscono dagli ombreggiatori atmosferici per il fatto che questi ultimi operano su tutti i raggi che partono dalla macchina da ripresa (si ricordi che la tecnica di traccia dei raggi rappresenta i raggi luminosi in maniera inversa rispetto a quanto accade in natura - ovvero dalla camera da ripresa alla sorgente luminosa). Ombreggiatori interni ed esterni operano solo ed esclusivamente su raggi secondari, quei raggi generati dalla funzione traccia negli ombreggiatori. Gli ombreggiatori atmosferici vengono utilizzati per creare effetti come la nebbia e la foschia.
Gli ombreggiatori di volume costituiscono un tema abbastanza avanzato di cui cercherò di occuparmi in un prossimo articolo.

Ombreggiatori di spostamento
      I retini di un oggetto possono presentarsi in vari modi, da perfettamente lisci a molto corrugati, da bernoccoli lisci e arrotondati a bordi frastagliati. Con i normali ombreggiatori superficiali un retino può essere simulato mediante l'uso di una mappa a bernoccoli. Tali applicazioni perturbano lo stato di un punto sulla superficie di un oggetto in modo tale che il punto appare rialzato, abbassato, o comunque spostato dalla sua collocazione originaria. Un'applicazione a bernoccoli descrive le variazioni nell'orientazione di una superficie. Sfortunatamente ciò avviene solo in maniera virtuale ed il punto della superficie, di fatto, non viene spostato. Per alcune superfici tale effetto funziona alla perfezione quando le si guarda da un'angolazione ben precisa, quando però le si va a guardare dal bordo della superficie le variazioni scompaiono - il bordo è liscio. Un esempio molto comune è fornito da un'arancia. Allorché le si applica una funzione a bernoccoli l'arancia appare dalla superficie butterata. Il bordo della sfera è, però, liscio e l'effetto di butterazione va perduto. È a questo punto che entrano in gioco gli ombreggiatori di spostamento.
      Nelle Specifiche dell'interfaccia RenderMan [3] è scritto

L'ambiente dell'ombreggiatore di spostamento è molto simile a quello di un ombreggiatore di superficie, se si eccettua il fatto che esso ha accesso esclusivamente ai parametri della superficie geometrica. [Un ombreggiatore di spostamento] calcola un nuovo [punto] P e/o una nuova [normale per quel punto] N.
Un ombreggiatore di spostamento opera attraverso una superficie, modificando la collocazione fisica di ogni punto. Queste modificazioni sono, in generale, secondarie e di un tipo che avrebbe dovuto essere molto più difficoltoso (e costoso dal punto di vista dei calcoli da fare) da specificare individualmente. Può essere difficile riuscire ad apprezzare questa caratteristica se non se ne vedono gli effetti. Il foglio 9 (Plate 9) in [4] mostra un normale cilindro modificato con l'ombreggiatore di spostamento threads() per creare le venature sulla base di una lampadina. Le Figure 1-3 illustrano un esempio analogo (anche se meno sofisticato). Senza l'applicazione di un ombreggiatore di spostamento ogni venatura dovrebbe essere realizzata con uno o più oggetti individuali. Sebbene dal punto di vista computazionale l'investimento in termini di risorse sia irrisorio, lo sforzo richiesto per modellare questi oggetti in maniera corretta può essere significativo. Gli ombreggiatori di spostamento offrono un controllo procedurale sulla forma di un oggetto.

Normale cilindro Un cilindro ordinario con le normali modificate Un cilindro con spostamenti reali
Un normale cilindro
Lo stesso cilindro con le normali modificate

Si noti che in questo caso gli attributi del rappresentatore non sono stati predisposti. I bordi del cilindro sono piatti, sebbene all'apparenza la superficie non sia piatta.
Lo stesso cilindro con spostamenti reali

In questa immagine sono stati predisposti i parametri del rappresentatore. I bordi del cilindro riflettono la nuova forma del cilindro.
Figura 1 Figura 2 Figura 3

      Quando si usano gli ombreggiatori di spostamento con BMRT è importante ricordare che, normalmente, gli spostamenti non vengono predisposti. Anche se viene chiamato un ombreggiatore di spostamento i punti sulla superficie avranno le loro normali modificate esclusivamente dall'ombreggiatore. Per realizzare un "vero spostamento" devono essere predisposte due opzioni degli attributi del rappresentatore:

     Attribute "render" "truedisplacement" 1
     Attribute "displacementbound" "coordinatesystem" 
	       "object" "sphere" 2
Il primo dei due serve a predisporre l'attributo relativo allo spostamento reale così che gli ombreggiatori di spostamento modifichino l'attuale posizione di un punto della superficie. Il secondo specifica la crescita delle dimensioni del contenitore intorno all'oggetto in maniera che possa racchiudere i punti modificati. In effetti ciò avviene perché l'attributo dice al rappresentatore quanto deve crescere il contenitore dell'oggetto nello spazio dell'oggetto. Il rappresentatore non può sapere con anticipo, quanto un ombreggiatore potrà modificare una superficie, questa riga fornisce pertanto un limite massimo per aiutare il rappresentatore nel delimitare contenitori intorno ad oggetti cui sono state applicate funzioni di spostamento. Si tenga presente che i contenitori di delimitazione sono utilizzati per rendere più veloci i test di illuminazione con raggi-oggetto da parte del rappresentatore. Si noti che il possibile cambiamento causato dallo spostamento può essere calcolato in qualunque altro ambiente, come ad es. il mondo o la camera da ripresa. Si usi quello che si ritiene più conveniente. Il tag "sfera" fa sapere al rappresentatore che il contenitore intorno all'oggetto cresce uniformemente in tutte le direzioni. Allo stato attuale BMRT supporta esclusivamente questo tipo di crescita per cui, per questo parametro, non potranno essere utilizzati altri valori.

Ombreggiatori di Trasformazione e Immagine
      BMRT non supporta ombreggiatori di trasformazione (così come non lo fa, almeno all'apparenza, il PRMan della Pixar). In prima approssimazione si può supporre che gli ombreggiatori di trasformazione operino su coordinate geometriche al fine di realizzare "trasformazioni geometriche non lineari". Secondo quanto riportato in [5]

Un ombreggiatore di trasformazione ha per obiettivo la modificazione del sistema di coordinate.
Esso viene utilizzato per trasformare la geometria di uno scenario senza riguardo ad alcuna superficie particolare. Si comprende allora la differenza con un ombreggiatore di spostamento che opera su una data superficie punto per punto. Gli ombreggiatori di trasformazione modificano la trasformazione corrente, il che significa che essi possono agire su tutti gli oggetti nella scena.
      Gli ombreggiatori di immagine sembrano operare sui colori dei pixel in uscita, che, per quanto mi riguarda, vuol dire che l'ombreggiatore va a correggere il colore o ad effettuare altre manipolazioni dopo che il colore di un pixel è stato calcolato ma prima che il pixel venga inviato definitivamente ad un file o al display. Il tutto sembra abbastanza semplice da capire, resta il dubbio sulle motivazioni che dovrebbero indurre a ricorrere all'uso di tali ombreggiatori. Larry dice che BMRT gestisce ombreggiatori di immagine mentre PRMan non lo fa. In ogni caso egli suggerisce che, con molta probabilità, la funzionalità appena illustrata sia più adatta a pacchetti di post-elaborazione quali possono essere XV, ImageMagick o il Gimp.

5. Sintassi del linguaggio degli Ombreggiatori

      Allora, come deve presentarsi un file di ombreggiatura? Si può dire che è molto simile, in formato, ad una procedura C con poche differenze significative. Quello che segue è un esempio molto semplice di ombreggiatore di superficie:
	surface matte (
		 float Ka = 1;
		 float Kd = 1;
	)
	{
	  point Nf;

	  /*
	   * Calcola la normale che si oppone alla 
	   * direzione che va verso la camera da ripresa.
	   */
	  Nf = faceforward (normalize(N),I);

	  Oi = Os;
	  Ci = Os * Cs * (Ka * ambient() + Kd * diffuse(Nf));
	}
Si tratta dell'ombreggiatore di superficie opaco "matte" fornito con il pacchetto BMRT; appartiene alla schiera degli ombreggiatori necessari, che le specifiche dell'interfaccia RenderMan asserisscono che ogni rappresentatore conforme al RenderMan deve fornire.

Nomi delle procedure degli Ombreggiatori
      La prima cosa da osservare è il tipo ed il nome della procedura. Nel nostro caso l'ombreggiatore è un ombreggiatore di superficie ed il suo nome è "matte". Quando queste righe di programma vengono compilate da slc viene prodotto un ombreggiatore detto "matte" in un file dal nome "matte.so". Un nome di procedura può essere un nome qualsiasi purché non sia un comando o una parola riservata RIB. I nomi di procedure possono contenere lettere, numeri e simboli di sottolineatura, ma non possono contenere spazi.

Variabili e loro visibilità
      Vi sono diversi tipi di variabili che vengono utilizzate con gli ombreggiatori: variabili di istanza, variabili globali e variabili locali. Le variabili di istanza sono variabili utilizzate dall'ombreggiatore come parametri. Quando si chiama un ombreggiatore queste variabili vengono dichiarate (se non lo sono state già) e viene loro assegnato un valore da utilizzare con quella istanza dell'ombreggiatore. Ad esempio, l'ombreggiatore "matte" richiede due parametri che possono assumere valori ben determinati quando l'ombreggiatore è istanziato all'interno del file RIB. Supponiamo di voler ombreggiare una sfera alla quale vogliamo applicare l'ombreggiatore matte, dovremo specificare le variabili di istanza come segue:

 
	AttributeBegin
	   Declare "Kd" "float"
	   Declare "Ka" "float"
	   Surface "matte" "Kd" 0.5 "Ka" 0.5
	   Sphere 1 -.5 .5 360 
	AttributeEnd
I valori specificati per Kd e Ks sono le variabili di istanza ed il rappresentatore li utilizzerà per questa istanza dell'ombreggiatore. Le variabili di istanza, in generale, sono note al solo ombreggiatore al momento della chiamata iniziale dell'istanza corrente.
      Le variabili locali sono definite all'interno dell'ombreggiatore stesso e, come tali, vengono viste esclusivamente al suo interno. Nell'esempio dell'ombreggiatore "matte" la variabile Nf è una variabile di punto ed ha un significato ed un valore soltanto nell'ambito dell'ombreggiatore stesso. Altri ombreggiatori non avranno accesso ai valori di Nf. Le variabili locali sono utilizzate per gestire dei valori temporanei necessari a calcolare i valori da restituire al rappresentatore. I valori di ritorno vengono passati come variabili globali.
      Le variabili globali hanno un ruolo particolare nell'ambiente RenderMan. L'unica maniera con la quale un ombreggiatore può restituire al rappresentatore dei valori è mediante l'uso di variabili globali. Alcune delle variabili globali che un ombreggiatore può manipolare sono i colori superficiali (Cs), l'opacità superficiale (Os), il vettore normale al punto corrente (N) e l'opacità del raggio incidente (Oi). La predisposizione di questi valori nell'ombreggiatore determina le modalità con le quali l'ombreggiatore assegna i colori ai punti della superficie dell'oggetto che deve essere rappresentato. La lista completa delle variabili globali che un particolare tipo di ombreggiatore può leggere o modificare è elencata nelle tabelle delle Specifiche dell'interfaccia RenderMan [6]. Le variabili globali sono tali nel senso che consentono il passaggio di valori tra l'ombreggiatore ed il rappresentatore del punto corrente della superficie, non possono però essere utilizzati per trasferire valori dall'ombreggiatore di un oggetto ad un altro.

Tipi di dati ed espressioni
      Gli ombreggiatori hanno accesso esclusivamente a 4 tipi di dati: un tipo scalare, due tipi vettoriali ed un tipo stringa. Una stringa può essere definita ed utilizzata da un ombreggiatore, ma non può essere modificata. Ne consegue che una variabile d'istanza che prende un valore stringa non può essere modificata dall'ombreggiatore, né una variabile stringa locale può essere modificata una volta che sia stata definita.
      Il tipo scalare utilizzato da un ombreggiatore è detto tipo float Gli ombreggiatori utilizzano variabili di tipo float anche per la gestione di valori interi. Il tipo point è un array di tre elementi float che descrivono la posizione di un punto in un determinato spazio. Per default in BMRT un punto viene a trovarsi nel world space [universo] (per converso PRMan usa la camera di ripresa come spazio di default), ma è possibile convertire un punto in oggetto, mondo, retino o qualche altro spazio all'interno dell'ombreggiatore. Un punto può essere trasformato in uno spazio differente utilizzando l'istruzione transform. Ad esempio:

       float y = ycomp(transform("object",P));
trasformerà il punto corrente in uno spazio oggetto e restituirà la componente Y del nuovo punto nella variabile float y.
L'altro tipo di vettore utilizzato è ancora un array di tre elementi float che specificano un colore. Una variabile di tipo colore può essere definita come segue:
       color Cp = color (0.5, 0.5, 0.5);

      Nel linguaggio degli ombreggiatori le espressioni algebriche seguono le stesse regole di precedenza utilizzate nel linguaggio C. Gli unici due tipi di operazioni che risultano essere specifici per gli ombreggiatori sono il Dot Product (prodotto scalare) ed il Cross Product (prodotto vettoriale). Il primo è utilizzato per misurare l'angolo tra due vettori, è denotato con il punto (.) ed opera su variabili di tipo punto. Il secondo è spesso utilizzato per calcolare il vettore normale ad un punto, assegnati due vettori non paralleli tangenti alla superficie in quel punto. Il prodotto vettoriale opera solo su variabili di tipo punto, è denotato col simbolo di accento circonflesso (^) e restituisce un valore di tipo punto (array tridimensionale).

Funzioni
      Un ombreggiatore non è necessariamente un'entità onnicomprensiva: esso può infatti chiamare routine esterne, meglio note come funzioni. Le Specifiche dell'interfaccia RenderMan predefiniscono un gran numero di funzioni, disponibili per tutti gli autori di ombreggiatori che utilizzino BMRT. La lista che segue fornisce soltanto un esempio di queste funzioni predefinite:

La precedente non è una lista esaustiva ma fornisce un buon esempio delle funzioni di cui può disporre un autore di ombreggiatori. Molte funzioni operano su più di un tipo di dato (come punti e colori). Ognuna di esse può essere utilizzata per calcolare un nuovo valore di colore, punto o float da applicare al punto corrente della superficie.
      Gli ombreggiatori possono utilizzare il loro insieme di funzioni definite localmente, infatti è spesso utile inserire le funzioni in una libreria di funzioni che può essere inclusa in un ombreggiatore utilizzando la direttiva di #include. Ad esempio, il sito web RManNotes fornisce una funzione di libreria detta "rmannotes.sl" che contiene una funzione impulso che può essere utilizzata per creare linee su una superficie. Se avessimo utilizzato una tale funzione nell'esempio dell'ombreggiatore "matte" avremmo ottenuto qualcosa di molto simile a quanto segue:
	#include "rmannotes.sl"

	surface matte (
		 float Ka = 1;
		 float Kd = 1;
	)
	{
	  point Nf;
	  float fuzz = 0.05
	  color Ol;

	  /*
	   * Calcola la normale che si oppone alla 
	   * direzione che va verso la camera da ripresa.
	   */
	  Nf = faceforward (normalize(N),I);

	  Ol = pulse(0.35, 0.65, fuzz, s);
	  Oi = Os*Ol;
	  Ci = Os * Cs * (Ka * ambient() + Kd * diffuse(Nf));
	}
La funzione che abbiamo utilizzato è definita nel file rmannotes.sl come
  #define pulse(a,b,fuzz,x) (smoothstep((a)-(fuzz),(a),(x)) - \
			     smoothstep((b)-(fuzz),(b),(x)))
Un ombreggiatore può, al limite, contenere direttamente il valore #defined senza includere un altro file, ma se la funzione è utile un autore di ombreggiatori vorrà tenerla in una libreria separata analoga a quella appena vista: rmannotes.sl. Nel nostro esempio la variabile s è la componente di sinistra della coordinata di retino corrente. "s" è una componente dello spazio di retino che andremo ad analizzare nella sezione relativa ai sistemi di coordinate. "s" è una variabile globale e per questo motivo non è definita all'interno del codice di esempio.

Nota: Questo particolare esempio può non rivelarsi molto utile. Lo riporto solo ed esclusivamente al fine di illustrare come avviene l'inclusione di funzioni da una libreria di funzioni.

      Le funzioni possono essere chiamate soltanto da un ombreggiatore, mai direttamente da un rappresentatore. Ciò significa che una funzione non può essere utilizzata direttamente in un file RIB o referenziata utilizzando il legame col C dell'interfaccia RenderMan. Le funzioni non possono essere neanche ricorsive ovvero non possono chiamare se stesse. Infine va tenuto sempre presente che tutte le variabili vengono passate alle funzioni per riferimento e non per valore; ciò significa che occorre usare molta attenzione in maniera da evitare che una funzione possa modificare anche inavvertitamente il valore delle variabili, senza che lo si voglia effettivamente.

Strutture di controllo del linguaggio
      Il linguaggio degli ombreggiatori dispone delle seguenti strutture di controllo:

Tutte le precedenti strutture operano alla stessa identica maniera delle loro omologhe in C.

Sistemi di Coordinate
      RenderMan utilizza diversi tipi di sistemi di coordinate. Trovo che alcuni di questi siano abbastanza facili da capire, altri siano invece più difficili - specialmente quando utilizzati con gli ombreggiatori. In un ombreggiatore la superficie di un oggetto è proiettata su un reticolo rettangolare bidimensionale che si estende dal punto di coordinate (0,0) nell'angolo in alto a sinistra al punto (1,1) nell'angolo in basso a destra. Il reticolo è sovrapposto alla superficie per cui su una superficie rettangolare l'applicazione è ovvia. Su una sfera i vertici agli estremi superiori del reticolo vengono trasformati nello stesso punto al vertice della sfera. Tale reticolo è noto come spazio parametrico e ci si riferisce a ciascun punto di tale spazio mediante delle variabili globali u e v (coordinate curvilinee). A titolo di esempio si osservi che un punto che si trovi esattamente al centro del reticolo avrebbe coordinate (u,v) pari a (.5,.5).
      Analogo allo spazio parametrico è lo spazio di retino che risulta essere l'applicazione di una funzione di retino che ancora una volta va da 0 a 1, ma le cui variabili coordinate sono s e t. Normalmente lo spazio di retino è equivalente allo spazio parametrico a meno che variabili vertice (variabili applicate ai vertici di oggetti primitivi come toppe o poligoni) o istruzioni TextureCoordinates non vadano a modificare lo spazio di retino delle primitive da ombreggiare. Se si utilizzano i valori di default l'applicazione di retino di un'immagine avrà il suo vertice superiore sinistro mappato sul vertice superiore sinistro del reticolo dello spazio parametrico che soggiace alla superficie dell'oggetto, mentre il vertice in basso a destra dell'immagine sarà mappato sul vertice in basso a destra del reticolo, per cui, in definitiva, l'immagine coprirà l'intero oggetto. Poiché lo spazio di retino non deve necessariamente essere uguale allo spazio parametrico è possibile anche mappare un'immagine soltanto su una porzione di un oggetto. Sfortunatamente non mi sono spinto così lontano questo mese da poter preparare un esempio del genere, forse lo farò il mese prossimo.
      Esistono comunque altri tipi di spazio: universo, oggetto e ombreggiatore. Come ciascuno di questi agisca sulle caratteristiche di ombreggiatura e retinatura non mi è ancora completamente chiaro. Lo spazio dell'ombreggiatore è lo spazio di default in cui operano gli ombreggiatori anche se i punti di tale spazio possono essere trasformati in quelli di un universo o di un oggetto prima che si vada ad operare su di essi. Non so esattamente cosa ciò significhi o come e perché si debba essere costretti ad utilizzarli.

6. Formato di un file ombreggiatore

      Gli ombreggiatori possono avere una struttura abbastanza libera da vincoli anche se esistono varie metogologie che possono essere utilizzate per realizzare in maniera più agevole gli ombreggiatori e rendere più comprensibile il codice corrispondente. Nelle sue RManNotes [7], Stephen F. May scrive
Una delle tecniche principali nel risolvere il problema è "divide et impera", ovvero: scomporre un problema complesso in sottoproblemi più semplici, risolvere questi sottoproblemi e ricomporre le soluzioni così ottenute al fine di fornire una soluzione al problema originario.

Con gli ombreggiatori, in maniera analoga, scomponiamo complicati modelli di superfici e strati in retini. Ogni strato dovrebbe essere abbastanza facile da scrivere (se non lo è possiamo scomporre lo strato in ulteriori sottostrati). Ciò fatto ricombiniamo gli strati componendoli.

La struttura di base di un ombreggiatore è simile a quella di una procedura C - viene dichiarato il tipo dell'ombreggiatore (superficiale, di spostamento e così via) e vengono assegnati tipi di parametri ben precisi. A differenza del C, comunque, ad un ombreggiatore si richiede di essere dotato di parametri con preassegnati valori di default. In tal modo un ombreggiatore può essere istanziato senza utilizzare alcuna variabile d'istanza. Se, però, con qualche variabile d'istanza si utilizzano dei parametri, allora il valore della variabile istanziata si sovrappone a quello del valore del parametro di default. Nel caso più semplice un ombreggiatore può essere simile a quanto segue:
	surface null ()
	{
	}
Di fatto la precedente è proprio la definizione dell'ombreggiatore nullo. Non chiedetemi quale sia il senso di un tale ombreggiatore; sono sicuro che i suoi autori ne conoscessero la ragione, anche se non so quale sia. Con l'aggiunta di alcuni parametri arriviamo all'ombreggiatore "matte":
	surface matte (
		 float Ka = 1;
		 float Kd = 1;
	)
	{
	}
I parametri Ka e Kb hanno comunque i loro valori di default. Si noti che Ka è normalmente utilizzato negli ombreggiatori presenti nell'archivio di Guido Quaroni per rappresentare fattori di gradazione delle luci ambientali. In maniera analoga Kd è utilizzato per graduare luci diffuse. Questi parametri non sono variabili globali anche se sono ben noti, un po' come lo sono le "i", "j" e "k" utilizzate come contatori nel codice sorgente C (reminiscenza dei giorni fecondi della programmazione Fortran).
      Dopo la dichiarazione di un ombreggiatore e dei suoi parametri viene l'insieme delle variabili locali ed il codice dell'ombreggiatore che si va ad utilizzare. Al riguardo diamo ancora una volta un'occhiata all'ombreggiatore "matte":
	#include "rmannotes.sl"

	surface matte (
		 float Ka = 1;
		 float Kd = 1;
	)
	{
	  point Nf;
	  float fuzz = 0.05
	  color Ol;

	  /*
	   * Calcola la normale che si oppone alla 
	   * direzione che va verso la camera da ripresa.
	   */
	  Nf = faceforward (normalize(N),I);

	  Ol = pulse(0.35, 0.65, fuzz, s);
	  Oi = Os*Ol;
	  Ci = Os * Cs * (Ka * ambient() + Kd * diffuse(Nf));
	}
Come si vede non c'è nulla di particolare, anzi è molto simile ad una banalissima procedura C. Entriamo ora nel merito delle metodologie; May [8] ci mostra come dovrebbe apparire lo pseudo-codice di un ombreggiatore strutturato:
	surface banana(...)
	{
	  /* background (strato 0) */
	  surface_color = yellow-green variations;

	  /* strato 1 */
	  layer = fibers;
	  surface_color = composite layer on surface_color;

	  /* strato 2 */
	  layer = bruises;
	  surface_color = composite layer on surface_color;

	  /* strato 3 */
	  layer = bites;
	  surface_color = composite layer on surface_color;

	  /* illuminazione */
	  surface_color = illumination based on surface_color 
			  and illum params;

	  /* output */
	  Ci = surface_color;
	}
La sequenza degli eventi che hanno luogo con questo programma è la seguente: il livello più basso applica i colori giallo e verde alla superficie, dopo di che un secondo strato compone ai precedenti dei colori tipo fibra (mescolati o sovrapposti). Quest'operazione viene ripetuta per ciascuno dei 4 strati definiti (da 0 a 3) con l'aggiunta di un calcolo per l'illuminazione al fine di determinare la brillantezza del punto corrente. Infine il colore della superficie appena calcolato viene dato in uscita attraverso una variabile globale. Tale metodologia di lavoro rende la scrittura degli ombreggiatori molto più agevole e consente ad altri autori di ombreggiatori di correggere e/o estendere le caratteristiche dell'ombreggiatore stesso. Il file di un ombreggiatore è pertanto una sorta di progetto bottom-up in cui gli strati bassi della superficie sono calcolati per primi e quelli superiori per ultimi.

7. Qualche parola sulle funzioni di retino

      Come accennato qualche riga più su le funzioni di retino sono immagini mappate su una superficie da 0 a 1, da sinistra a destra e dall'alto in basso. Ogni parte dell'immagine è interpolata tra 0 e 1 e l'applicazione stessa non deve necessariamente essere applicata all'intera superficie di un oggetto, ne consegue che se essa è utilizzata insieme allo spazio parametrico della superficie (coordinate u,v) dovrebbe essere possibile mappare un'immagine su una sezione di una superficie.
      Sfortunatamente non sono stato in grado di utilizzare nella maniera migliore queste conoscenze per applicarle all'immagine che ho inviato questo mese all'IRTC. Mi ero riproposto di effettuare qualche prova più in là ed in particolare di mettere delle etichette sui dorsi dei testi nello scaffale presente nella scena descritta. Molto probabilmente riuscirò nell'intento con il prossimo articolo su BMRT in cui mi riprometto di realizzare un esempio relativo al come applicare funzioni di retino a porzioni di superfici.

8. Esempi di applicazioni


      Sono sempre dell'avviso che la maniera migliore di imparare a scrivere un ombreggiatore passi per lo sviscerare approfonditamente qualche esempio. Tutti i riferimenti bibliografici citati in appendice hanno delle ottime spiegazioni degli esempi che sto per descrivere (migliori anche di quelle che potrò dare io), ritengo però che questi siano abbastanza facili da poter essere seguiti anche da un neofita.

Un modello di croce colorata
      Questo esempio è preso così com'è dalle RManNotes di Stephen F. May. L'ombreggiatore crea un modello di croce a due colori. In questo esempio il modello è applicato ad un semplice piano (una toppa romboidale). Si dia un'occhiata al codice sorgente.

	color surface_color, layer_color;
	color surface_opac, layer_opac;
La prima cosa da notare è che questo ombreggiatore definisce due variabili di colore locali: surface_color and layer_color. La seconda variabile è utilizzata per calcolare il colore degli strati correnti. La prima è usata per comporre i vari strati dell'ombreggiatore. Altre due variabili: surface_opacity e layer_opacity hanno le medesime funzioni per quanto concerne l'opacità dello strato corrente.
      Il primo strato è una striscia verticale; l'ombreggiatore definisce innanzitutto il colore di questo strato, poi determina l'opacità del punto corrente utilizzando una funzione detta pulse() [impulso]. Questa funzione è resa disponibile da May nella sua libreria di funzioni "rmannotes.sl" e, nel caso del nostro ombreggiatore, consente ai bordi delle striscie di passare molto dolcemente da un colore ad un altro (si dia un'occhiata ai bordi delle striscie nell'immagine di esempio). La funzione pulse() utilizza la variabile fuzz per determinare quanto saranno sfuocati i bordi. Infine, per ogni strato il colore e l'opacità dello strato stessi sono mescolati insieme per dar luogo al nuovo colore della superficie. La funzione blend() [mescola] è anch'essa parte della rmannotes.sl ed è un'estensione della funzione mix() dell'interfaccia RenderMan, che, a sua volta, mescola i valori dei colori e delle opacità.
modello di croce ricoperta
Figura 4
codice sorgente RIB dell'esempio
      Infine vengono assegnati valori alla variabile globale che rappresenta l'opacità dei raggi incidenti ed al colore corrispondente.
	Oi = surface_opac;
	Ci = surface_opac * surface_color;
Questi due valori vengono utilizzati dal rappresentatore per calcolare il valore dei pixel nell'immagine fornita in output.

L'aggiunta di opacità - un ombreggiatore a reticolo
      Questo esempio è preso integralmente dalla Guida RenderMan e mostra come si possa utilizzare un ombreggiatore per eliminare delle porzioni di una superficie solida. Utilizziamo il primo esempio come immagine speculare per una sfera che sia gestita mediante l'ombeggiatore screen() della Guida RenderMan (il nome dell'ombreggiatore utilizzato in questo esempio è leggermente differente in quanto è stato preso dalla collezione di ombreggiatori di Guido Quaroni, il quale ha modificato i nomi di alcuni ombreggiatori per mettere maggiormente in risalto le loro origini). Si dia innanzitutto un'occhiata all'immagine mediante l'ombreggiatore "plastic" (che viene normalmente fornito nel pacchetto BMRT in distribuzione). La figura 5 mostra come appare la scena corrispondente. La sfera di questo esempio è solida. Il codice RIB di tale esempio contiene le seguenti righe:
	AttributeBegin
	   Color [ 1.0 0.5 0.5 ]
	   Surface "plastic"
	   Sphere 1 -1 1 360 
	AttributeEnd
In Figura 6 la sfera è stata trasformata in una superficie reticolata. La sola differenza tra l'immagine attuale e quella della Figura 5 è l'ombreggiatore di superficie utilizzato. Il codice della Figura 6 si presenta come segue:
	AttributeBegin
	   Color [ 1.0 0.5 0.5 ]
	   Surface "RCScreen"
	   Sphere 1 -1 1 360 
	AttributeEnd
Le rimanenti righe del file RIB sono esattamente le stesse. Ora diamo un'occhiata al codice dell'ombreggiatore screen().
surface 
RCScreen(
  float Ks   = .5, 
  Kd         = .5, 
  Ka         = .1, 
  roughness  = .1,
  density    = .25,
  frequency  = 20;
  color specularcolor = color (1,1,1) )
{
   varying point Nf = 
	   faceforward( normalize(N), I );

   point V = normalize(-I);
Sfera reticolata - senza reticolo
Figura 5
Codice sorgente RIB di questo esempio
Sfera reticolata - con reticolo
Figura 6
Codice sorgente RIB di questo esempio
Sfera reticolata - linee di reticolo sottili
Figura 7
Codice sorgente RIB di questo esempio

   if( mod(s*frequency,1) < density || 
       mod(t*frequency,1) < density )
      Oi = 1.0;

   else 
      Oi = 0.0;

   Ci = Oi * ( Cs * ( Ka*ambient() + Kd*diffuse(Nf) ) + 
	       specularcolor*Ks* specular(Nf,V,roughness));
}

      La variabile locale V è definita come il vettore normalizzato della direzione dei raggi di luce incidenti, quest'ultima è la direzione dalla quale la camera da ripresa vede le coordinate superficiali correnti. Questo valore è utilizzato più avanti per calcolare il fascio di luce speculare da utilizzare sulla porzione della superficie che non verrà asportata dalla sfera.
      Subito dopo l'ombreggiatore procede al calcolo del resto della divisione intera tra la componente s dello spazio del retino e la frequenza delle linee della griglia del reticolo. Il valore che si ottiene è sempre minore di 1 (il modulo della s*frequenza è il resto lasciato per n*1 < s*frequenza per un determinato valore di n). Se tale valore è inferiore anche della densità allora il punto di coordinate correnti sulla superficie č parte del reticolo visibile che attraversa la superficie orizzontalmente. Alla stessa identica maniera si può calcolare un modulo analogo per t*frequenza e se il suo valore è minore anche della densità allora il punto di coordinate correnti si trova su una delle linee delle griglie verticali visibili del reticolo. Ogni punto per il quale il modulo di una delle due grandezze è maggiore della densità viene rappresentato in modo da apparire trasparente. L'ultima riga di programma serve a calcolare le linee della griglia sulla base del colore della superficie corrente e di un modello di illuminazione di tipo leggermente metallico.
      Il valore di default della densità è 0.25, che significa che circa 1/4 della superficie sarà visibile come reticolo. Se tale valore viene modificato con una variabile d'istanza e posto uguale a 0.1 si otterrà che le linee della griglia di reticolo diverranno più sottili. Nella Figura 7 viene mostrato un esempio al riguardo. Se si va a modificare il valore della frequenza prendendo un valore più piccolo si avrà la rappresentazione di un numero inferiore di linee.

Un semplice ombreggiatore stile foglio di carta
      Mentre stavo lavorando agli articoli di Marzo/Aprile 1997 sull'IRTC mi è capitato di scrivere il mio primo ombreggiatore - un ombreggiatore in grado di simulare un foglio di agenda con tre buchi. Questo semplicissimo ombreggiatore presenta alcune delle caratteristiche dei precedenti esempi per quanto concerne la produzione di linee orizzontali e verticali regolarmente spaziate con l'aggiunta della caratteristica della piena trasparenza delle regioni circolari che sono individuate da variabili d'istanza.       Cominciamo col definire i parametri necessari a tale ombreggiatore. Ve ne sono soltanto un po' di più rispetto agli altri ombreggiatori ed il motivo è da ricercare nel fatto che questo ombreggiatore ha delle caratteristiche che non sono poi tanto simmetriche. Penso che il tutto si possa addebitare senza problemi alla mia inesperienza.

   color hcolor       = color "rgb" (0, 0, 1);
   color vcolor       = color "rgb" (1, 0, 0);
   float hfreq        = 34;
   float vfreq        = 6;
   float skip         = 4;
   float paper_height = 11;
   float paper_width  = 8.5;
   float density      = .03125;
   float holeoffset   = .09325;
   float holeradius   = .01975;
   float hole1        = 2.6;
   float hole2        = 18;
   float hole3        = 31.25;
Esaminiamo innanzitutto i colori delle linee orizzontali e verticali. Ci sono 34 linee di default sulla carta, con le prime quattro "omesse" per realizzare il piccolo spazio di intestazione all'inizio del foglio. La frequenza verticale è utilizzata per dividere il foglio di carta in n blocchi verticali uguali distribuiti su tutta la pagina. Ciò è utilizzato per determinare la locazione della singola striscia verticale. Ci ritorniamo su tra un momento.
      L'altezza e la larghezza della carta vengono utilizzate per mappare lo spazio parametrico sulle corrette dimensioni di un ordinario foglio di carta. Il parametro della densità è lo spessore di ciascuna delle linee visibili (sia orizzontali che verticali) sulla carta. Lo spostamento del foro definisce la distanza tra il bordo sinistro della carta ed il centro dei tre fori che devono essere realizzati. Il raggio del foro ` pari al raggio dei vari fori ed i parametri di hole1-hole3 danno la linea orizzontale sulla quale viene a trovarsi il centro di questo stesso foro. Ad esempio, per hole1 il centro del foro si trova a 2.6 bande orizzontali verso il basso. In questo caso le strisce orizzontali sono state create alla sommità di blocchi orizzontali tutti delle stesse dimensioni ed i valori hole1-hole3 rappresentano il numero di blocchi orizzontali sulla carta attraverso i quali passare andando verso il basso per raggiungere il centro del foro. Vediamo ora come vengono create le linee.
          
   surface_color = Cs;
Questa riga inizializza molto semplicemente una variabile locale con il valore del colore corrente della superficie. Utilizzeremo questo valore per il calcolo di un nuovo colore della superficie a seconda che il punto corrente si trovi su una riga orizzontale o verticale.
/*
 * Strato 1 - strisce orizzontali.  
 * C'Š una striscia per ogni blocco orizzontale.
 * La striscia Š di "densit…" spessa ed inizia alla sommit… 
 * di ogni blocco eccettuati i primi che vengono "saltati"
 */
tt = t*paper_height;
for ( horiz=skip; horiz<hfreq; horiz=horiz+1 )
{
   min = horiz*hblock;
   max = min+density;
   val = smoothstep(min, max, tt);
   if ( val != 0 && val != 1 )
      surface_color = mix(hcolor, Cs, val);
}
Questo ciclo che viene eseguito su tutti i blocchi orizzontali del foglio di carta (definito dal parametro hfreq) determina se il punto giace tra la sommità del blocco e la sommità del blocco più l'ampiezza di una linea orizzontale (specificata dal parametro densità).
Carta con 3 fori
Figura 8
Codice sorgente RIB di questo esempio
Carta con 3 fori - linee spesse
Figura 9
La funzione smoothstep() fa parte delle funzioni RenderMan standard e restituisce un valore compreso tra 0 e 1 inclusi, che permette di ricavare ove è situato, tra il minimo ed il massimo, il valore di "tt". Se questo valore non cade su nessuno dei due estremi il punto della superficie corrente giace tra gli estremi di una riga orizzontale e prende il valore "hcolor" mescolato con il colore della superficie. Mescolare i colori fa sì che i bordi delle linee passino gradatamente dal colore delle linee stesse a quello della carta. In altre parole questa operazione corrisponde ad un anti-aliasing delle righe orizzontali. L'unico problema è che non funziona. Mi sembra che si abbia un effetto di aliasing solo su un lato della riga. Come si può vedere dalla figura 8, comunque, il risultato definitivo non si presenta come un solido ed omogeneo insieme di righe.
      Un approccio alternativo potrebbe essere quello di sostituire la chiamata della funzione mix() (funzione standard del linguaggio degli ombreggiatori RenderMan) con qualcosa di più semplice, come un miscuglio del colore della linea con il valore restituito da smoothstep(). Il codice corrispondente dovrebbe essere simile al seguente:
   min = horiz*hblock;
   max = min+density;
   val = smoothstep(min, max, tt);
   if ( val != 0 && val != 1 )
      surface_color = val*hcolor;
Altra alternativa ancora potrebbe essere data dalla scelta di utilizzare un colore di linea autonomo, ovvero senza effettuare miscugli con i valori restituiti dalla funzione smoothstep(). Si ottiene in tal modo una linea molto frastagliata ma anche molto più scura, e che resta tale quando si vanno ad utilizzare densità di linea molto inferiori. L'esito di questo esperimento è dato dalla Figura 9 (ove viene utilizzata una densità di linea inferiore).
   /* Strato 2 - striscia verticale */
   ss = s*paper_width;
   min = vblock;
   max = min+density;
   val = smoothstep(min, max, ss);
   if ( val != 0 && val != 1 )
      surface_color = mix(vcolor, Cs, val);
Quest'ultima porzione di codice fa esattamente la stessa cosa del codice precedente tranne per il fatto che opera su una striscia verticale. Poiché compare una sola riga verticale non c'è alcuna necessità di testare ogni possibile blocco verticale, ci si soffermerà soltanto su quello che contiene la striscia visibile (specificato dal parametro vblock).
      Andiamo a vedere infine la realizzazione dei fori. Il loro centro è calcolato a partire dal bordo sinistro della carta:
   shole = holeoffset*paper_width;
   ss  = s*paper_height;
   tt  = t*paper_height;
   pos = (ss,tt,0);
Si noti che utilizziamo l'altezza della carta per convertire i valori delle variabili ss, tt nella scala della larghezza e dell'altezza della carta. Perché? Perché se avessimo usato l'ampiezza di ss ci saremmo ritrovati con dei fori ellittici. Molto probabilmente esiste una procedura migliore per ottenere il medesimo risultato (fare fori circolari) ma almeno questa funziona.
      Per ciascun foro viene calcolato il valore corrente delle coordinate s, t della distanza dal centro. Se la distanza è inferiore al raggio del foro allora l'opacità del raggio incidente è predisposta in maniera da essere completamente trasparente.
   /* Primo buco */
   thole = hole1*hblock;
   hpos  = (shole, thole, 0);
   Oi = filterstep (holeradius*paper_width, 
		     distance(pos,hpos));

   /* Secondo buco */
   thole = hole2*hblock;
   hpos = (shole, thole, 0);
   Oi *= filterstep (holeradius*paper_width, 
		      distance(pos,hpos));

   /* Terzo buco */
   thole = hole3*hblock;
   hpos = (shole, thole, 0);
   Oi *= filterstep (holeradius*paper_width, 
		      distance(pos,hpos));
Anche qui filterstep() è una funzione standard del pacchetto RenderMan, nonstante non venga riportata né nelle Specifiche dell'interfaccia RenderMan né nella Guida RenderMan.
Secondo Larry Gritz
La funzione filterstep() è identica alla step, eccettuato il fatto che è analiticamente antialias. Analogamente a quanto accade per texture(), filterstep prende il valore della derivata del suo secondo argomento e lo aumenta gradatamente in intensità ad una velocità che dipende dal ritmo di variabilità di quella variabile. In termini tecnici essa restituisce la convoluzione della funzione step con applicato un filtro la cui ampiezza è delle dimensioni di un pixel. Si evitano così frastagliamenti.
Ne consegue che l'uso di filterstep() facilita l'anti-alias dei bordi dei fori (sebbene la cosa non sia così ovvia nelle Figure 8 e 9, viste le ridotte dimensioni delle immagini). Non l'ho provato, ma penso che filterstep() possa essere utilizzata con successo per eliminare i problemi incontrati con le righe orizzontali e verticali.

Una lavagna mappata con un retino
      Questo semplice esempio di applicazione di retino è stato utilizzato nell'immagine Post Detention che ho sottoposto all'IRTC 1997 nel periodo Marzo/Aprile. L'ombreggiatore utilizzato è stato preso dall'archivio-collezione di Guido Quaroni mentre la sua versione originaria è di Larry Knott (che penso lavori alla Pixar). Non ho ritenuto utile aggiungere alcuna immagine poiché tutto ciò che si sarebbe visto sarebbe stata l'immagine originaria mappata su un piano (piatto), che non mostra nulla di veramente utile. Se si vuol dare un'occhiata alla lavagna nella scena completa basta andare a consultare l'articolo gemello nel numero della Musa della Grafica di questo mese.
      Come l'altro esempio di ombreggiatore anche questo è pressoché banale. Il nome di un file immagine viene passato come parametro nella texturename; si noti che il file immagine deve essere di tipo TIFF per poter essere usato con BMRT. Le coordinate di retino sono utilizzate per catturare un valore dal file immagine che viene quindi combinato con la luce ambiente e diffusa dei raggi incidenti. Se viene specificata una luce speculare (il che avviene normalmente con il parametro Ks) allora al raggio incidente viene aggiunta una luce riflessa. Alla fine, il valore calcolato Ci viene combinato con l'opacità delle superfici per determinare il colore finale da attribuire al punto della superficie corrente.

Esempio di applicazione di spostamento
      Abbiamo già visto un esempio di applicazioni di spostamento utilizzando l'ombreggiatore threads() [venature]. Diamo una rapida occhiata al codice sorgente dell'ombreggiatore:

   magnitude = (sin( PI*2*(t*frequency + 
		     s + phase))+offset) * Km;
Qui lo spostamento del punto della superficie è determinato mediante l'utilizzo di una sinusoide in fase. La variabile t determina longitudinalmente sulla superficie la posizione mentre la variabile s è usata per causare l'effetto a spirale. La porzione di codice che segue
   if( t > (1-dampzone)) 
      magnitude *= (1.0-t) / dampzone;
   else if( t < dampzone )
      magnitude *= t / dampzone;
determina sulle estremità della superficie, nel nostro caso un cilindro, il ritorno al modello originario. Per il nostro esempio ciò significa che l'ombreggiatore viene forzato a lasciare le estremità circolari. Il che aiuta a mantenere l'oggetto cui sono state applicate venature in una forma che può essere facilmente ricongiunta ad altri oggetti. Nella Guida RenderMan il cilindro striato viene congiunto con un bulbo di vetro per formare una lampadina. Vediamo infine cosa accade nelle ultime due righe di codice
   P += normalize(N) * magnitude;
   N = calculatenormal(P);
il punto viene spostato e viene calcolata la normale del nuovo punto. In tal modo il punto appare, anche visivamente, spostato, il che è proprio ciò che è accaduto.

Avevo previsto per il prossimo mese di realizzare la terza parte di questo complesso di articoli sul BMRT. Ritengo che il poter fruire di 2 mesi tra i vari numeri abbia giocato a mio favore consentendomi di fruire di un po' più di tempo per poter scavare in profondità. Prevedo che l'articolo conclusivo della serie sul BMRT uscirà col numero di Luglio della Musa della Grafica. Fino ad allora, buon divertimento con i rappresentatori.
indent

    Bibliografia
  1. Ebert, Musgrave, Peachy, Perlin, Worley. Retini e Modelli: Un approccio procedurale, 5-6; AP Professional (Academic Press), 1994
  2. Upstill, Steve. La guida RenderMan - Guida per il programmatore ad una Computer Grafica realistica, 277-278; Addison Wesley, 1989
  3. (Le specifiche dell'interfaccia RenderMan, Ver. 3.1) 112-113; Pixar, Settembre 1989
  4. Upstill, Steve. La guida RenderMan - Guida per il programmatore ad una Computer Grafica realistica, sezione dei fogli colorati; Addison Wesley, 1989
  5. Upstill, Steve. La guida RenderMan - Guida per il programmatore ad una Computer Grafica realistica, 279; Addison Wesley, 1989
  6. The RenderMan Interface Specification, Version 3.1
    (Le specifiche dell'interfaccia RenderMan, Ver. 3.1) 110-114; Pixar, Settembre 1989
  7. RManNotes "Come scrivere ombreggiatori RenderMan - Perché seguire un metodo?"; Stephen F. May, Copyright © 1995, 1996
  8. RManNotes "Come scrivere ombreggiatori RenderMan - l'approccio a strati"; Stephen F. May, Copyright © 1995, 1996
indent

[ La pagina della Musa ] [ Indice ]


Per l'articolo originale: © 1997 Michael J. Hammel - Pubblicato sul n. 17 della Linux Gazette
Per l'edizione italiana: © 1997 A. Cartelli - Pubblicato sul n. 6 di LGEI a cura del LUGBari