Tassellatura di triangoli

Esempio

Questo è un semplice esempio dell'uso dei tessellation shader per suddividere ciascuno dei triangoli dell'icosaedro originario in più triangoli. L'illuminazione è per vertice, perciò l'aumento del numero di vertici porta a un miglioramento dell'effetto.

All'inizio, il vertex shader si limita a passare avanti tutti gli attributi del vertice, perché tutta l'elaborazione per vertice dovrà essere eseguita all'interno del tessellation evaluation shader, quando si sa già quali sono i vertici prodotti dal Tessellator. La posizione, la normale e il colore sono ora variabili attribute, allo stesso modo delle caratteristiche del materiale.

#version 400

in vec3 positionattrib;
in vec3 normalattrib;
in vec4 colorattrib;
in float matshininess;
in vec3 matspecular;

out vec3 vPosition;
out vec3 vNormal;
out vec4 vColor;
out float vmatshininess;
out vec3 vmatspecular;

void main()
  {
  vPosition = positionattrib;
  vNormal = normalattrib;
  vColor = colorattrib;
  vmatshininess = matshininess;
  vmatspecular = matspecular;
  }

Il contenuto del listato sovrastante è rilasciato nel pubblico dominio.

Il tessellation control shader passa avanti gli attributi che provengono dal vertex shader. Inoltre, imposta gl_TessLevelInner e gl_TessLevelOuter con i valori provvisti dal programma utente. Li imposta una sola volta, per ID uguale a zero, perché quelle variabili sono condivise tra tutte le istanze.

Come indicato nella direttiva layout, il tessellation control shader è istanziato tre volte. Perciò, anche se non è specificato tra le parentesi quadre, tutti gli array in uscita hanno dimensione 3.

#version 400

layout(vertices = 3) out;

in vec3 vPosition[];
in vec3 vNormal[];
in vec4 vColor[];
in float vmatshininess[];
in vec3 vmatspecular[];

out vec3 tcPosition[];
out vec3 tcNormal[];
out vec4 tcColor[];
out float tcmatshininess[];
out vec3 tcmatspecular[];

uniform float tesslevelinner;
uniform float tesslevelouter;

#define ID gl_InvocationID

void main()
  {
  tcPosition[ID] = vPosition[ID];
  tcNormal[ID] = vNormal[ID];
  tcColor[ID] = vColor[ID];
  tcmatshininess[ID] = vmatshininess[ID];
  tcmatspecular[ID] = vmatspecular[ID];
  if (ID == 0)
    {
    gl_TessLevelInner[0] = tesslevelinner;
    gl_TessLevelOuter[0] = tesslevelouter;
    gl_TessLevelOuter[1] = tesslevelouter;
    gl_TessLevelOuter[2] = tesslevelouter;
    }
  }

Il contenuto del listato sovrastante è derivato dal codice rilasciato nel pubblico dominio da The Little Grasshopper ed è rilasciato nuovamente da me nel pubblico dominio.

Il tessellation evaluation shader calcola la posizione del punto con la formula vista nella sezione sul tessellation evaluation shader, cioè come media pesata delle variabili tcPosition inizializzate dal tessellation control shader.

Anche la normale è calcolata allo stesso modo. Invece, le caratteristiche del materiale e il suo colore sono mantenute costanti per tutta la faccia e sono ottenute per media dei valori in ingresso. Questa è solo una scelta per rendere in codice un po' più semplice.

La matrice per la trasformazione delle normali (ex gl_NormalMatrix) è ottenuta prendendo la parte in alto a sinistra dell'inversa della trasposta della matrice di modelview. Se il programma potesse usare una libreria matematica, potrebbe essere un'idea migliore precalcolare questa matrice e caricare il risultato sugli shader con un'ulteriore variabile uniform di tipo mat3.

#version 400

layout(triangles, equal_spacing, ccw) in;
in vec3 tcPosition[];

in vec3 tcNormal[];
in vec4 tcColor[];
in float tcmatshininess[];
in vec3 tcmatspecular[];

out vec4 tecolor;
out vec3 tePosition;

uniform mat4 modelviewmatrix;

vec4 ApplyLight(in vec4 oColor, in vec3 relativepos, in vec3 normal,
  in vec3 matspecular,in float matshininess);

void main()
  {
  // calcolo della posizione
  vec3 p0 = gl_TessCoord.x * tcPosition[0];
  vec3 p1 = gl_TessCoord.y * tcPosition[1];
  vec3 p2 = gl_TessCoord.z * tcPosition[2];
  vec3 position = (p0 + p1 + p2);
    
  // calcolo della normale
  p0 = gl_TessCoord.x * tcNormal[0];
  p1 = gl_TessCoord.y * tcNormal[1];
  p2 = gl_TessCoord.z * tcNormal[2];
  vec3 normal = p0 + p1 + p2;
  // calcolo della matrice di trasformazione delle normali
  mat3 normalmatrix = mat3(inverse(transpose(modelviewmatrix)));
  normal = normalize(normalmatrix * normal);
    
  // calcolo del colore medio
  vec4 avgcolor = (tcColor[0] + tcColor[1] + tcColor[2]) / 3.0;
  // caratteristiche medie del materiale
  float avgshini = (tcmatshininess[0] + tcmatshininess[1] + tcmatshininess[2]) / 3.0;
  vec3 avgspec = (tcmatspecular[0] + tcmatspecular[1] + tcmatspecular[2]) / 3.0;
    
  // illuminazione
  vec4 rp4 = modelviewmatrix * vec4(position, 1.0);
  vec3 relativepos = rp4.xyz / rp4.w;
  tecolor = ApplyLight(avgcolor,relativepos,normal,avgspec,avgshini);
    
  tePosition = position;
  }

Il contenuto del listato sovrastante è rilasciato nel pubblico dominio.

Il geometry shader non sarebbe necessario in questo esempio, perché l'illuminazione è già stata eseguita nel tessellation evaluation shader e anche l'applicazione delle trasformazioni di modelview e proiezione potrebbe essere eseguita a quel punto.

Il geometry shader usato qui è simile a quello proposto nella sezione Restringere i triangoli e serve soltanto per creare un po' di spazio tra le primitive, in modo che la suddivisione sia evidente.

#version 400

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

out vec4 gcolor;
in vec4 tecolor[3];
in vec3 tePosition[3];

uniform float scale;

uniform mat4 projectionmatrix;
uniform mat4 modelviewmatrix;

void main()
  {
  vec3 center = vec3(0.0,0.0,0.0);
  for (int i = 0; i < 3; i++)
    center += tePosition[i];

  center *= 1/3.0;
  center *= (1 - scale);
  
  for (int i = 0; i < 3; i++)
    {
    vec4 pos = vec4(tePosition[i] * scale + center,1.0);
    gl_Position = projectionmatrix * modelviewmatrix * pos;
    gcolor = tecolor[i];
    
    EmitVertex();
    }
    
  EndPrimitive();
  }

Infine, il fragment shader non deve fare nulla, siccome l'illuminazione è già stata applicata per vertice nel tessellation evaluation shader.

#version 400

out vec4 FragColor;
in vec4 gcolor;

void main()
  {
  FragColor = gcolor;
  }

Confugurazione per l'esempio

#define SHADER_FOLDER "shaders/triangle_tessellation/"

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

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

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

Risultato, con tesslevelinner e tesslevelouter pari a 6:

risultato 6x6

Portando a 1.0 la variabile uniform scale si disabilita l'effetto del geometry shader. Nel programma di esempio basta modificare la riga:

#define UNIFORM_SCALE 1.0

Il risultato diventa:

risultato 6x6 no geometry

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.