Quasi sempre il colore di un oggetto 3D deriva da una texture. In questa lezione vedremo come le texture vengono importate in Unity, impostate, ed assegnate agli oggetti in scena.
In generale, le texture sono dei file di grafica 2D che vengono sovrapposti o avvolti intorno ad un modello 3D per decorarlo. L'aspetto finale del modello dipende da tante cose (la luce, l'ambient light, ecc. ma anche lo specular) e potrebbe essere influenzato anche da un colore di fondo che possiamo decidere, e che lo shader mischia alla texture (come nel caso degli shader Diffuse) in fase di rendering.
Le texture servono a diversi scopi. Le più comuni sono dette diffuse, e rappresentano il colore dell'oggetto. A seconda dello shader, un materiale può utilizzare altre texture per ottenere effetti particolari, come l'autoilluminazione, la specularità, o i rilievi (mediante le Normal Maps di cui parleremo più avanti).
I pixel che compongono una texture si chiamano texel (texture pixel). Facciamo questa distinzione perché, quando la texture viene applicata al modello, quasi mai i texel corrispondono ai pixel a schermo. Immaginiamo una texture di 64x64 texel, su un qualunque modello 3D: a schermo il modello potrebbe occupare una manciata di pixel se lontano, o riempire tutto uno schermo full HD se molto vicino (in questo caso vedremo la texture sgranata).
Importare le texture, i formati di file
Per creare una texture, è sufficiente inserire un file grafico nella cartella degli Asset. Unity importerà il file, assegnandogli delle caratteristiche di base che potremo modificare. È importante tener conto che Unity non modifica mai l'asset originale ma ne fa una copia interna, permettendoci di importarlo più e più volte senza perdita di qualità.
Unity supporta diversi tipi di formati file per le texture: .gif
, .jpg
, .png
, .tiff
, .tga
, .psd
e altri. Idealmente, si potrebbe pensare di usare il .jpg
per produrre un file leggero, ma non è necessario: in fase di importazione Unity ricomprime il file, generando l'asset che poi finirà nel gioco. Per questo motivo, in generale è consigliabile esportare da Photoshop (o altri) in PNG 24, per conservare tutti i dettagli inclusa la trasparenza.
Alternativamente, si può lavorare in .psd ed importare direttamente quello. Unity si occuperà di compattare i livelli e creare una versione ottimizzata della texture, senza modificare il .psd originale dove quindi i livelli rimangono editabili.
Questo dà la massima flessibilità e rapidità (perché non bisogna esportare, solo modificare il file e salvare), ma genera file più grandi da gestire durante il flusso di lavoro, specialmente se si usano sistemi di versioning o bisogna passare le texture agli altri membri del team via internet.
Proprietà di importazione
Quando una texture viene aggiunta a Unity, viene importata con le impostazioni di default. Nell'Inspector vedremo:
Proprietà | Descrizione |
---|---|
Texture Type | È la modalità di importazione. Ne esistono altre oltre a Texture, le vedremo sotto. |
Alpha from Grayscale | Dice a Unity di ricavare un canale alpha dai valori di luminosità della texture (bianco = opaco , nero = trasparente ). In questo modo possiamo avere texture trasparenti anche se non hanno un canale alpha. In realtà, se lavorate con criterio non dovrebbe esserci bisogno di attivare quest'opzione! |
Alpha is Transparency | Serve a migliorare la resa dei contorni in caso di oggetti semitrasparenti. Ha a che fare con la premoltiplicazione del colore, e meriterebbe un capitolo a sé. Diciamo che va attivato solo in caso un'immagine con trasparenza presenti dei bordi con artefatti grafici non voluti. |
Del Filter Mode e del Wrap Mode parliamo più avanti in sezioni dedicate.
Nel pannellino in basso possiamo decidere la dimensione alla quale la texture viene importata. Max Size consente di trattare una texture come se fosse più piccola, per cui possiamo avere un file di lavoro a 2048x2048 (magari un .psd, come dicevamo prima) ma far sì che Unity lo importi ad una dimensione di 1024x1024.
Inoltre, usando le tab (le iconcine del mondo, la freccia, l'iPhone… ) possiamo personalizzare questa dimensione per ogni piattaforma. Così facendo, possiamo fare in modo che solo su Android la nostra texture si riduca ancora, diventando 512x512, per alleggerire build e rendering. Tutto questo viene fatto senza modificare l'asset iniziale.
Format invece consente di impostare l'algoritmo di compressione. Gli algoritmi sono tanti, ma si dividono in 3 classi di base: Compressed, 16 bits, e Truecolor (24 o 32 bit). Ognuno di questi ha la sua qualità, ed il suo peso in memoria, e saremo noi a dover scegliere ad occhio il miglior compromesso qualità/memoria usata.
Selezionandone uno e premendo Apply, in basso possiamo vedere il risultato visivo, e quanto spazio occuperà nella memoria video la texture una volta caricata (1 KB nell'esempio sotto):
Anche qui possiamo fare scelte diverse per piattaforma (non sempre necessario).
Opzioni avanzate
Selezionando Advanced nel primo menu a tendina, si aprono tutta una serie di nuove opzioni:
Non Power of 2
Questa proprietà ci fa presente un nuovo problema: alle schede grafiche piacciono le texture che sono potenze di due (2, 4, 8, 16… 32… 128, ecc.), quindi se la texture non lo è Unity la converte, secondo la modalità scelta qui. Come detto per l'opzione Alpha from Grayscale, un'adeguata pianificazione in fase di disegno della texture è la soluzione migliore.
Generate Cubemap
Indica a Unity di creare una cubemap a partire da una semplice texture. Una cubemap è una texture che non è piana, ma a forma di cubo. Le cubemap di solito vengono usate per descrivere l'ambiente circostante ad un oggetto, ad esempio per mostrare cosa l'oggetto riflette, o per creare le skybox.
Di fatto, le cubemap spesso contengono una deformazione prospettica che permette di usare le sei facce del cubo come fossero una sfera, per fare in modo che non si vedano gli spigoli del cubo. Per fare un esempio comune, una cubemap è quello che viene visualizzato in Street View all'interno di Google Maps: un'immagine sferica che avvolge l'utente da tutti i punti di vista.
Attivando Generate Cubemap, Unity cerca di creare una cubemap a partire dalla texture, ma questa deve essere creata appositamente. Per maggiori informazioni, si può guardare la reference sulle Cubemap.
Read/Write Enabled
Se attivo, permette agli script di modificare la texture in maniera permanente, utile ad esempio quando viene usata la webcam per catturare una texture o se vogliamo scrivere del testo su un'immagine, o modificarne i colori. Di fatto, questo è l'unico caso in cui Unity modifica l'asset originale ed è anche potenzialmente distruttivo. Se lo attivate e volete testare uno script, assicuratevi di avere una copia della texture che state modificando.
Import Type
Permette di trattare la texture come Normal Map o Lightmap, che sono formati diversi. Ne parliamo più in dettaglio in altre lezioni.
Bypass sRGB sampling
Determina se Unity debba o meno convertire lo spazio gamma dei colori da sRGB a lineare. È una impostazione che va modificata raramente, ma può tornare utile se vi servono i colori esatti della texture (perché magari sono indicizzati per rappresentare dei valori precisi) e non volete che Unity faccia la conversione.
Filter Mode
L'operazione di prendere un pixel dello schermo, ed andare a vedere a che texel corrisponde sulla texture, si chiama sampling (letteralmente campionare). Per quest'operazione possiamo definire come Unity deciderà di disegnare i pixel a schermo a partire dal file originale selezionando il tipo di filtro che verrà applicato in fase di sampling. Unity fornisce tre scelte di filtri: Point, Bilinear e Trilinear.
Filtro | Descrizione |
---|---|
Point | è l'equivalente di quello che in Photoshop viene chiamato Nearest Neighbour (o "Vicino Più Prossimo" in italiano), ovvero campiona il pixel più vicino senza cambiargli colore. |
Bilinear | corrisponde a Bilineare, e si limita a fare la media fra i pixel vicini. È il metodo più usato e dà buoni risultati quando lo scopo è avere una texture quanto più realistica possibile. |
Trilinear | è un'evoluzione del precedente, che oltre a fare il lavoro di media fra i pixel vicini lo fa anche fra i vari livelli di mip-map (vedi sezione apposita). È il migliore, ma necessariamente anche leggermente più pesante da calcolare - nella maggior parte dei casi Bilinear è sufficiente. |
Ad esempio una texture molto piccola (16x16 texel), se vista molto grande a schermo risulterà in questo modo con un filtering di tipo Point:
Come si può vedere, la texture 16x16 viene trasformata e renderizzata in un'immagine alta fino a quasi 400 pixel. In questo caso, l'immagine non sgrana e i colori vengono preservati, ma possiamo notare che la texture ha dei bordi seghettati (ben visibili sui quadrati color ciano).
Vediamo il risultato con un filtering Bilinear:
L'alone che vediamo intorno ai quadrati color ciano sono dei pixel "inventati" dal filtro, che in mancanza d'altro, fa una media fra il verde e il ciano per renderizzare la texture ad una risoluzione maggiore.
Nota: l'esempio di sopra utilizza una texture molto piccola a scopo dimostrativo. Di conseguenza, il filtro Point sembra dare un risultato più gradevole. In realtà, la texture 16x16 di cui sopra è stata pensata per avere bordi netti e seghettati (per un estetica pixel art in 3D), e quindi è pensata per il filtro Point. In generale però, il filtro Bilinear è quello che dà risultati migliori.
Per maggiori informazioni sui filtri, potete consultare questa pagina di Wikipedia.
Le Mip-Maps
Come detto, quando una texture viene renderizzata il motore grafico deve andare a campionare i texel dalla texture stessa. Se l'oggetto è molto piccolo a schermo, in generale viene caricata una texture inutilmente grande, sprecando memoria video.
Una tecnica comune è utilizzare le mip-maps: sono texture aggiuntive ad una risoluzione più bassa, che vengono utilizzate al posto dell'originale quando l'oggetto è lontano dalla telecamera. Queste texture vengono create automaticamente da Unity in fase di importazione, fino ad otto livelli (vale a dire che l'ultima sarà ⅛ dell'originale).
Ecco un esempio di mip-maps, con l'originale + 7 livelli progressivamente più piccoli (qui zoomati):
Oltre al vantaggio di un'utilizzo di memoria inferiore, le mip-maps sono anche già ricampionate, permettendo di preservare i dettagli in maniera migliore rispetto al ricampionamento al volo che farebbe il motore di rendering, che creerebbe l'effetto di aliasing. Ad esempio:
In questo esempio in cui le mip-maps non sono attive, il cubo piccolo presenta degli artefatti grafici (le linee beige sono pixellose e presentano aliasing) perché il renderer deve ricampionare i dettagli al volo.
In questa seconda immagine, il cubo piccolo è più "morbido" e le scanalature meno pronunciate.
Utilizzare una texture in un materiale
Dopo aver deciso come importare la texture, possiamo assegnarla ad un materiale. Creiamo un materiale nel pannello Project, oppure da Assets > Create > Material
. A seconda del tipo di materiale (ovvero dello shader che utilizza) potremo assegnare una o più texture. Selezioniamo un semplice Diffuse
.
Nell'esempio sopra, come texture Base abbiamo assegnato una texture ripetibile di mattoni.
Offset, Tiling e Wrap Mode
I due parametri Tiling e Offset permettono di controllare come e quante volte la texture viene ripetuta. Sopra abbiamo l'esempio di Tiling ad 1, ovvero la texture viene ripetuta una sola volta su ogni faccia del quadrato.
Se selezioniamo il Tiling a 3, avremo questo risultato:
La texture viene ripetuta 3 volte in verticale e 3 volte in orizzontale nello stesso spazio di prima, e come risultato i mattoni risultano più piccoli.
Offset invece, permette di scegliere da dove iniziare a disegnare la texture: i valori vanno da 0 a 1, dove 0 indica sinistra o in alto, e 1 indica destra o giù.
Se Offset è 0 e 0, la texture viene disegnata semplicemente come abbiamo visto finora:
Ma se impostiamo i valori di Offset su 0.3 e 0.3, la texture sarà disegnata a partire da un terzo della sua ampiezza. Nell'esempio di prima:
Quindi, Unity inizia a disegnare a partire da 0.3 lungo la texture, ed arrivato alla fine della texture, la ripete.
Se torniamo alle proprietà di importazione delle texture, possiamo ora provare ad impostare il Wrap Mode su Clamp piuttosto che Repeat (come visto fin'ora).
Cosa fa Clamp? Quando Unity disegna una texture ed arriva al bordo di questa, invece di ripetere i pixel ripartendo dal lato sinistro, continuerà a ripetere i pixel del bordo. Se mettiamo Offset su 0.3 e 0.3 e decidiamo di importare la texture con Clamp, questo è il risultato:
Come si può vedere, la texture viene disegnata a partire da un terzo, ed arrivato alla fine del file, Unity continua a disegnare i pixel del bordo (ovvero un verde scuro).
Clamp è molto utile quando abbiamo ad esempio la texture di un marchio, e vogliamo che non venga ripetuto su una superficie, che per il resto avrà un colore a tinta unita.