Shader: cosa sono, tipi di shader e come funzionano

Autore: IT Sectr Pubblicato: 2026-06-11 Tempo di lettura: 8 min

Shader è un programma eseguito sulla GPU per elaborare dati grafici: vertici, frammenti o attività di calcolo. A differenza del codice normale sulla CPU, lo shader lavora in parallelo su centinaia di core, elaborando ogni elemento indipendentemente. Secondo Khronos Group (2025), gli shader sono utilizzati nel 100% delle applicazioni 3D moderne — dai giochi mobili agli effetti UI nelle applicazioni di sistema. Gli sviluppatori scrivono shader in linguaggi specializzati: GLSL, HLSL, Metal Shading Language o SPIR-V.

Punti chiave

  • Shader è un programma per GPU eseguito in parallelo su centinaia di core per ogni elemento della scena.
  • Tipi di shader: vertex, fragment, compute, geometry, tessellation e mesh shader.
  • Linguaggi: GLSL (OpenGL), HLSL (DirectX), MSL (Metal), SPIR-V (Vulkan) — tutti compilati in codice macchina GPU.
  • Shader nello sviluppo mobile sono utilizzati per effetti, post-elaborazione, illuminazione e calcoli.
  • Ottimizzazione degli shader: precisione mediump, minimizzazione dei rami e ATF (Adaptive Texture Fetch) per GPU mobili.

Cos'è uno Shader?

Shader è un piccolo programma scritto in un linguaggio specializzato ed eseguito sulla GPU. Ogni shader elabora un elemento di dati — vertice, frammento (pixel) o elemento di lavoro — indipendentemente dagli altri. Il parallelismo della GPU consente di eseguire migliaia di istanze di shader simultaneamente.

Il termine è stato introdotto da Pixar negli anni '80 per descrivere programmi che controllano l'aspetto delle superfici in RenderMan. In tempo reale, gli shader sono apparsi con NVIDIA GeForce 3 (2001) — la prima scheda grafica con pipeline programmabile. Prima degli shader, la grafica veniva configurata con parametri fissi: colore, texture, modalità di blending.

Secondo Unity Technologies (2025), un gioco mobile medio utilizza 50–200 shader. Ogni shader ha varianti per diverse piattaforme, livelli di qualità e configurazioni di illuminazione. Il numero di varianti può raggiungere 10.000 per progetto.

L'architettura di uno shader si divide in dati di input (attributi, variabili uniform, texture), codice del programma e dati di output (colore, posizione). Le variabili uniform sono impostate dalla CPU prima dell'esecuzione dello shader e rimangono invariate per tutti gli elementi del frame. Gli attributi sono dati per elemento (coordinate del vertice, normale, coordinate UV).

Modello di esecuzione SIMT

SIMT (Single Instruction, Multiple Threads) è il modello di esecuzione degli shader sulla GPU. Un'istruzione viene caricata una volta ed eseguita da centinaia di thread su dati diversi. Ad esempio, un Fragment Shader per 1000 pixel esegue 1000 thread con lo stesso codice, ma ogni thread riceve le proprie coordinate UV e attributi interpolati.

Una conseguenza importante del SIMT: i rami negli shader sono costosi. Se all'interno di un blocco if metà dei thread segue un ramo e l'altra metà un altro, la GPU esegue entrambi i rami sequenzialmente per tutti i thread. Le prestazioni scendono al 50%. Evitate i rami dinamici negli shader o minimizzateli tramite calcoli preliminari sulla CPU.

Tipi di shader

Le GPU moderne supportano diversi tipi di shader, ciascuno per la propria fase del pipeline grafico o di calcolo. Esaminiamo i tipi principali: vertex, fragment, compute e i moderni mesh shader.

Tipo di shaderFase del pipelineElaboraLinguaggio
Vertex ShaderVertexOgni verticeGLSL, HLSL
Fragment ShaderFragmentOgni pixelGLSL, HLSL
Compute ShaderComputeDati arbitrariGLSL, HLSL
Geometry ShaderGeometriaPrimitiveGLSL, HLSL
Tessellation ShaderTessellazionePatchGLSL, HLSL
Mesh ShaderPipeline meshTask + MeshHLSL, MSL

Vertex Shader

Vertex Shader elabora ogni vertice della geometria: trasforma le coordinate, calcola l'illuminazione, passa i dati al fragment shader. È una fase obbligatoria del pipeline grafico — senza di essa, i vertici non arrivano sullo schermo. Il Vertex Shader viene chiamato per ogni vertice di ogni frame.

Fragment Shader

Fragment Shader (o Pixel Shader) è la fase che consuma più risorse. Calcola il colore finale di ogni frammento utilizzando texture, illuminazione e materiale. Il Fragment Shader viene eseguito per ogni pixel coperto dalla geometria, considerando tutte le texture e gli effetti applicati. L'ottimizzazione di questo shader offre il maggior guadagno di prestazioni.

Compute Shader

Compute Shader è uno shader universale per calcoli arbitrari sulla GPU. A differenza degli shader grafici, non è legato alla geometria o ai pixel. Il Compute Shader lavora con buffer di dati arbitrari tramite gruppi di lavoro. Viene utilizzato per fisica, simulazione di particelle, post-effetti e inferenza di reti neurali.

Mesh Shader

Mesh Shader è il tipo di shader più recente, apparso con NVIDIA Turing (2018) e Metal 3. Il Mesh Shader sostituisce gli shader vertex, geometry e tessellation con un unico pipeline mesh. Funziona insieme al Task Shader: il Task Shader decide quali gruppi mesh renderizzare, e il Mesh Shader genera la geometria al volo.

Linguaggi per scrivere shader

Gli shader sono scritti in linguaggi specializzati che vengono compilati in codice macchina GPU. La scelta del linguaggio dipende dalla piattaforma e dall'API. Esaminiamo i principali linguaggi: GLSL, HLSL, Metal Shading Language e il formato intermedio SPIR-V.

GLSL (OpenGL Shading Language)

GLSL è il linguaggio di shader per OpenGL, OpenGL ES e WebGL. La sintassi è basata sul C con l'aggiunta di tipi vettoriali (vec2, vec4, mat4) e funzioni integrate (texture, normalize, reflect). GLSL ES è la versione per dispositivi mobili con precisione limitata (lowp, mediump, highp). Secondo Khronos (2025), GLSL rimane il linguaggio di shader più diffuso grazie alla sua natura multipiattaforma.

glsl
#version 300 es
precision mediump float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 fragColor;

void main() {
    fragColor = texture(u_texture, v_texCoord);
}

Un semplice fragment shader legge il colore dalla texture tramite le coordinate UV. precision mediump indica alla GPU di utilizzare mezza precisione per float — accelera l'esecuzione sui dispositivi mobili del 25–40%.

HLSL (High-Level Shader Language)

HLSL è il linguaggio di shader di Microsoft per DirectX. La sintassi è più vicina al C++ con supporto per classi, strutture e template. HLSL viene utilizzato nelle applicazioni Windows e tramite Shader Model 6.7+ supporta Ray Tracing, Mesh Shaders e Sampler Feedback. Per lo sviluppo mobile, HLSL non viene utilizzato direttamente ma viene compilato in SPIR-V per Vulkan.

Metal Shading Language

MSL (Metal Shading Language) è il linguaggio di shader di Apple, basato su C++14. A differenza di GLSL e HLSL, MSL viene compilato insieme all'applicazione, eliminando la compilazione JIT sul dispositivo. MSL supporta puntatori, template e la libreria standard C++. Viene utilizzato su tutti i dispositivi Apple: iPhone, iPad, Mac, Apple TV.

cpp
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
    float4 position [[position]];
    float2 texCoord;
};

fragment float4
myFragment(VertexOut in [[stage_in]],
            texture2d<float> tex [[texture(0)]]) {
    constexpr sampler s = sampler(filter::linear);
    return tex.sample(s, in.texCoord);
}

MSL utilizza gli attributi [[position]], [[stage_in]] e [[texture(N)]] per il binding delle risorse. Il compilatore Apple genera codice ottimizzato per la GPU corrente, considerando il numero di registri e la cache.

SPIR-V

SPIR-V è un formato binario intermedio di shader, standard per Vulkan. Gli shader vengono scritti in GLSL o HLSL, compilati in SPIR-V e caricati nell'applicazione Vulkan. SPIR-V non è legato a un linguaggio specifico — esistono compilatori da Rust, Python, OpenCL C a SPIR-V.

Shader nello sviluppo mobile

Gli shader sulle piattaforme mobili hanno limitazioni rispetto al desktop: meno registri, precisione limitata e mancanza di supporto per alcune istruzioni. Esaminiamo le specificità degli shader su Android (Vulkan/OpenGL ES) e iOS (Metal).

Shader su Android

Android utilizza GLSL ES per OpenGL e SPIR-V per Vulkan. OpenGL ES 3.2 supporta shader con mediump per impostazione predefinita. Vulkan richiede la compilazione esplicita da GLSL a SPIR-V tramite glslangValidator. Su Android, gli shader vengono caricati da risorse stringa o da file SPIR-V compilati.

Qualcomm Adreno GPU ottimizza gli shader a livello di driver. Raccomandazioni: utilizzate mediump per colore e UV, highp solo per posizioni; evitate campionamenti di texture nei vertex shader; raggruppate i calcoli in vettori (vec4 invece di 4x float). Secondo Qualcomm (2025), queste ottimizzazioni offrono un guadagno del 30–50%.

Shader su iOS

iOS utilizza esclusivamente Metal Shading Language. Gli shader vengono compilati in codice macchina insieme all'applicazione tramite Xcode. Metal fornisce Shader Debugger e GPU Capture per il profiling degli shader. La GPU Apple (TBDR) ha ottimizzazioni specifiche: early fragment test, memoryless render targets e blending programmabile.

Secondo Apple WWDC 2024, per gli shader iOS è critico utilizzare half invece di float, limitare la pressione sui registri (max 64 registri) ed evitare campionamenti di texture dipendenti. Gli shader iOS devono essere compatti — dimensione ottimale 50–100 istruzioni ALU.

Shader in Unity e Unreal Engine

I motori di gioco Unity e Unreal Engine forniscono editor visivi di shader (Shader Graph, Material Editor) e linguaggi astratti (ShaderLab, USF). Lo sviluppatore scrive lo shader in un linguaggio di alto livello e il motore compila per la piattaforma di destinazione. Unity utilizza HLSL come linguaggio intermedio, Unreal utilizza USF (Unreal Shader Format).

Ottimizzazione degli shader

L'ottimizzazione degli shader è una fase cruciale nello sviluppo grafico per dispositivi mobili. Uno shader mal scritto può ridurre gli FPS da 60 a 20. Esaminiamo le principali regole e tecniche di ottimizzazione.

Precisione dei calcoli

Utilizzate la precisione minima sufficiente: lowp per colore e UV, mediump per normali e illuminazione, highp solo per posizioni e matrici. Secondo ARM (2025), le operazioni mediump sono 2 volte più veloci di highp sulle GPU Mali. In GLSL ES, ciò viene definito tramite qualificatori di precisione.

Minimizzazione dei campionamenti di texture

I campionamenti di texture sono l'operazione più costosa in uno shader (10–30 cicli contro 1–2 per un'istruzione ALU). Riducete il numero di campionamenti: combinate le texture in atlas, archiviate i dati in array uniform invece che in texture, memorizzate nella cache i risultati dei pixel vicini tramite istruzioni di derivata.

Evitate i rami

I rami dinamici (if con variabile uniform) riducono le prestazioni del pipeline SIMT. Sostituite i rami con funzioni matematiche: mix(), step(), smoothstep() e clamp(). Queste funzioni vengono eseguite in 1–2 istruzioni, mentre un ramo può costare 8–16 istruzioni a causa della divergenza dei thread.

glsl
// Male: ramo dinamico nello shader
if (u_enableLight) {
    color *= computeLighting(normal);
}

// Bene: sostituzione matematica del ramo
color *= mix(1.0, computeLighting(normal),
              float(u_enableLight));

mix() esegue un'interpolazione lineare tra due valori. Quando u_enableLight=0, restituisce 1.0 — il colore non cambia. Quando u_enableLight=1, restituisce il risultato dell'illuminazione. Nessun ramo — tutti i thread eseguono lo stesso codice.

Esempi di shader

Esaminiamo esempi pratici di shader per lo sviluppo mobile — dall'ombreggiatura semplice alla generazione procedurale di texture. Ogni esempio dimostra una tecnica specifica.

Ombreggiatura Phong (vertex shader)

Il modello Phong è il modello classico di illuminazione con componenti diffusa e speculare. Il Vertex Shader calcola l'illuminazione per ogni vertice, il Fragment Shader interpola solo il risultato (Gouraud shading). Adatto per dispositivi mobili grazie al basso costo.

glsl
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec3 a_normal;
uniform mat4 u_mvp;
uniform mat4 u_modelView;
out vec3 v_color;

void main() {
    vec3 normal = normalize(mat3(u_modelView) * a_normal);
    vec3 lightDir = normalize(vec3(0.0, 1.0, 1.0));
    float diff = max(dot(normal, lightDir), 0.0);
    v_color = vec3(0.8, 0.2, 0.2) * (0.3 + diff * 0.7);
    gl_Position = u_mvp * a_position;
}

Lo shader calcola l'illuminazione diffusa: prodotto scalare della normale con la direzione della luce. Il risultato viene miscelato con le componenti ambientale (0.3) e diffusa (0.7). Esegue 10–15 istruzioni ALU per vertice — accettabile per 100.000 poligoni a 60 FPS su una GPU mobile.

Texture procedurale (fragment shader)

Le texture procedurali vengono generate nel Fragment Shader senza caricamento da file. Risparmiano memoria video e semplificano l'animazione. Esempio — una scacchiera con dimensione delle celle regolabile. Solo poche istruzioni ALU — carico minimo sulla GPU.

glsl
#version 300 es
precision mediump float;
in vec2 v_uv;
uniform float u_cells;
out vec4 fragColor;

void main() {
    vec2 cell = floor(v_uv * u_cells);
    float isWhite = mod(cell.x + cell.y, 2.0);
    fragColor = mix(vec4(0.1, 0.1, 0.1, 1.0),
                     vec4(0.9, 0.9, 0.9, 1.0),
                     isWhite);
}

8 istruzioni ALU — uno shader ideale per dispositivi mobili. u_cells definisce il numero di celle per asse. floor e mod sono operazioni poco costose, eseguite in 1 ciclo su tutte le GPU mobili.

Domande frequenti

Qual è la differenza tra uno shader e un programma C++ normale?

Shader viene eseguito sulla GPU in modalità SIMT — un'istruzione per centinaia di thread. Un programma C++ normale viene eseguito sulla CPU in modalità MIMD. Lo shader non ha accesso al filesystem, all'I/O o alla memoria dinamica. Riceve dati tramite buffer e texture e restituisce una posizione o un colore.

Quale linguaggio di shader imparare per lo sviluppo mobile?

GLSL — per OpenGL ES e WebGL, multipiattaforma. Metal Shading Language — per iOS. HLSL — base per Unity e Unreal Engine. Iniziate con GLSL — la comprensione dei fondamenti è trasferibile a qualsiasi linguaggio di shader.

Perché gli shader sono lenti sui dispositivi mobili?

Cause principali: alta precisione (highp invece di mediump), campionamenti eccessivi di texture, rami dinamici e superamento del limite di registri. Utilizzate RenderDoc o Xcode GPU Frame Debugger per l'analisi degli shader.

Cos'è un compute shader e a cosa serve?

Compute Shader è uno shader per calcoli arbitrari sulla GPU: fisica delle particelle, elaborazione immagini, simulazione di tessuti. A differenza del Fragment Shader, non è legato ai pixel e può scrivere in buffer arbitrari.

Si possono scrivere shader senza conoscere linguaggi di programmazione?

Sì, tramite editor visivi: Shader Graph in Unity, Material Editor in Unreal Engine o Metal Shader Converter. Generano codice shader basato su uno schema a nodi. Per un'ottimizzazione approfondita, è necessaria la comprensione del linguaggio shader.

Riepilogo

  • Shader è un programma parallelo per GPU che elabora in massa vertici, frammenti o dati arbitrari.
  • Tipi di shader: Vertex, Fragment, Compute, Geometry, Tessellation e Mesh — ciascuno responsabile della propria fase del pipeline.
  • Linguaggi: GLSL (OpenGL), HLSL (DirectX), MSL (Metal), SPIR-V (Vulkan) — compilati in codice macchina GPU.
  • Specificità mobile: precisione mediump, minimi campionamenti di texture, assenza di rami dinamici.
  • Ottimizzazione: mix() invece di if, precisione half, ATF, combinazione di texture in atlas e calcoli preliminari sulla CPU.
  • Applicazioni: illuminazione Phong, texture procedurali, post-effetti, fisica delle particelle ed effetti UI.
  • Strumenti: RenderDoc, Xcode GPU Debugger, Qualcomm Adreno Profiler e Mali Offline Compiler per l'analisi degli shader.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche