Illuminazione

Purtroppo, la creazione di un vertex shader esclude immediatamente la funzione di illuminazione, che tra le altre cose permette di avere un'idea della profondità dell'oggetto. Perciò, è bene reimplementarla.

Accesso alle variabili

GLSL ha accesso a quasi tutte le variabili di illuminazione di OpenGL. Ciascuna luce è rappresentata da una struttura di questo tipo:

struct gl_LightSourceParameters
{
  vec4 position;           // posizione della luce (xyzw)
  
  vec4 ambient;            // componente ambientale della luce (rgba)
  vec4 diffuse;            // componente diffusiva della luce (rgba)
  vec4 specular;           // componente speculare della luce (rgba)
  vec4 halfVector;
  vec3 spotDirection;      
  float spotExponent;
  float spotCutoff;
  float spotCosCutoff;
  float constantAttenuation;
  float linearAttenuation;
  float quadraticAttenuation;
}

Un vettore di strutture di questo tipo, una per ogni luce definita, è disponibile come variabile globale all'interno di ogni shader.
La costante globale gl_MaxLights contiene il numero massimo di luci che il driver (la scheda video) supporta, perciò questo è anche il numero di elementi del vettore.

gl_LightSourceParameters gl_LightSource[gl_MaxLights]; 

Allo stesso modo, una struttura contiene le caratteristiche del materiale della primitiva corrente:

struct gl_MaterialParameters
{  
  vec4 emission;           // luce emessa dalla primitiva (rgba)
  vec4 ambient;            // componente ambientale del materiale (rgba)
  vec4 diffuse;            // componente diffusiva del materiale (rgba)
  vec4 specular;           // componente speculare del materiale (rgba)
  float shininess;         // tendenza del materiale a riflettere la luce speculare
}

Con questa struttura sono definite due variabili globali:

gl_MaterialParameters gl_FrontMaterial;      // da glMaterialfv(GL_FRONT, ...)
gl_MaterialParameters gl_BackMaterial;       // da glMaterialfv(GL_BACK, ...)

Esempio

Uno shader che replica esattamente l'illuminazione di OpenGL sarebbe molto complesso, perché dovrebbe tenere conto della possibilità che la luce sia direzionale, puntiforme, spot light, abbia attenuazione eccetera.

Tuttavia, uno dei vantaggi degli shader è proprio la possibilità di implementare, della Fixed Function Pipeline, solo le funzionalità che servono.

A titolo di esempio, si suppone che si voglia applicare il modello di Phong a ciascun vertice (Gouraud shading). È leggermente diverso dal comportamento di OpenGL di dafault, che usa il modello di Blinn-Phong, ma può bastare.
C'è una singola luce attiva puntiforme omnidirezionale. Inoltre, il materiale non emette luce e riflette la luce ambientale e diffusiva secondo il suo colore.

Una funzione che applica questo modello è:

vec4 ApplyLight(in vec4 oColor, in vec3 relativepos, in vec3 normal,in int lightIndex)
  {
  vec3 intensity = vec3(0.0,0.0,0.0);

  vec3 lightpos = gl_LightSource[lightIndex].position.xyz / gl_LightSource[lightIndex].position.w;
  vec3 lightrelative = normalize(lightpos - relativepos); // incident light vector
      
  /* ambient */
  intensity += gl_LightSource[lightIndex].ambient.rgb * gl_LightSource[lightIndex].ambient.a * oColor.rgb;

  float dotp = dot(lightrelative,normal);
  if (dotp > 0.0) /* if face is not oriented towards light, skip specular and diffuse */
    {
    /* diffuse */
      {
      vec3 diffuse = gl_LightSource[lightIndex].diffuse.rgb * dotp * gl_LightSource[lightIndex].diffuse.a * oColor.rgb;
      intensity += diffuse;
      }
    /* specular */
      {
      vec3 reflected = reflect(lightrelative,normal);
      vec3 viewr = normalize(relativepos);
      float weight = pow(max(0.0,dot(reflected,viewr)),gl_FrontMaterial.shininess/4.0);
        // divide shininess by 4 to make it similar to Blinn-Phong model
      intensity += gl_LightSource[lightIndex].specular.rgb * max(weight,0.0) * gl_LightSource[lightIndex].specular.a * 
        gl_FrontMaterial.specular.rgb * gl_FrontMaterial.specular.a;
      }
    }

  return vec4(clamp(intensity,0.0,1.0),oColor.a);
  }

Il contenuto del listato sovrastante è rilasciato nel pubblico dominio.

oColor è il colore originale del vertice, relativepos è la posizione del vertice in eye coordinates (dopo aver applicato la matrice di modelview), normal è la normale, anch'essa in eye coordinates.
La funzione restituisce il nuovo colore del vertice, dopo aver applicato l'illuminazione.

Il parametro lightIndex è l'indice della luce che deve essere applicata. Infatti, si nota che nessuna delle strutture elencate in precedenza fornisce l'informazione sull'abilitazione o meno della luce (con glEnable(GL_LIGHTn)).
Inoltre, in molte architetture i parametri di una luce non usata vanno per default quasi tutti a 1.0, non a 0.0, perciò risulta impossibile distinguere tra una luce disattivata e una bianca.

Queste difficoltà sono dovute al fatto che, in realtà, l'uso di queste strutture è deprecato. Esse sono ridondanti: prevedono una dozzina di variabili diverse per ogni luce, di cui poche sono usate ogni volta. È consigliato (e versioni più recenti di GLSL lo impongono) che queste informazioni siano passate allo shader in altri modi, che si vedranno nella prossima sezione.

Un vertex shader che usa questa funzione può essere:

#version 120

vec4 ApplyLight(in vec4 oColor, in vec3 relativepos, in vec3 normal,in int lightIndex);

void main()
  {
  vec3 normal = normalize(gl_NormalMatrix * gl_Normal);    // the normal MUST be normalized again after multiplication 
  vec4 rp = gl_ModelViewMatrix * gl_Vertex;
  rp /= rp.w;
  gl_FrontColor = ApplyLight(gl_Color,rp.xyz,normal,0);
  
  gl_BackColor = vec4(0.0,0.0,0.0,1.0);
  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
  }

Il contenuto del listato sovrastante è rilasciato nel pubblico dominio.

Configurazione per l'esempio

#define SHADER_FOLDER "shaders/lighting/"

#define USE_VERTEX_SHADER true
#define USE_FRAGMENT_SHADER false
#define USE_GEOMETRY_SHADER false
#define USE_NORMAL_SHADER false
#define USE_VERTEX_LIGHT_SHADER true
#define USE_FRAGMENT_LIGHT_SHADER false
#define USE_TESSEVAL_LIGHT_SHADER false
#define USE_TESSELLATION_SHADERS false

#define GEOMETRY_INPUT_TYPE GL_TRIANGLES         // irrilevante
#define GEOMETRY_OUTPUT_TYPE GL_TRIANGLE_STRIP   // irrilevante
#define GEOMETRY_MAX_VERTICES 3                  // irrilevante

#define ENABLE_BACK_FACE_CULLING true
#define LOAD_NORMALS true
#define TIMER_MS 0

Risultato:

Illuminazione con vertex shader

Copyright © 2011 Riccardo Monica
Eccetto ove espressamente indicato altrimenti, il contenuto di questa pagina è disponibile secondo la licenza Creative Commons Attribuzione - Condividi allo stesso modo 3.0.