Strumenti Utente



tutorial_qt:opengl_04bis_texture_3d

Qt e OpenGL - Le texture tridimensionali

Nel precedente articolo abbiamo cominciato a parlare delle texture OpenGL, analizzando un esempio in cui abbiamo disegnato un cubo rotante con un numero disegnato su ciascuna faccia. Il numero era una texture bidimensionale (sempre la stessa!).

Immagino siate rimasti non poco perplessi nel vedere lo stesso numero su tutte le facce del cubo! Perciò ora ci occuperemo di dare ad ogni faccia un numero diverso. La flessibilità delle API OpenGL ci pemettono di ottenere questo risultato in più modi: il più immediato potrebbe essere la creazione di sei texture. Un altro metodo è descritto nel progetto di esempio Cube OpenGL ES 2.0 disponbile nella documentazione delle Qt, in cui le facce del cubo sono disegnate in successione in un'unica immagine e con le coordinate texture si specifica quale porzione di quest'immagine disegnare su ciascuna faccia.

In questo articolo otterremo lo stesso risultato creando invece un vettore di texture. A tal proposito nelle OpenGL 3 è disponibile uno strumento ad hoc, chiamato texture 2D array. Per evitare problemi di compatibilità, ci serviremo invece di una texture tridimensionale, disponibile anche in versioni precedenti delle API OpenGL: la sostanza è la stessa, cambiano leggermente le funzioni per accedere ai valori delle texture ed i relativi parametri.

Cosa sono le texture tridimensionali

Abbiamo già visto che le texture bidimensionali sono dei vettori di due dimensioni, analoghe come struttura dei dati alle immagini bitmap. Caricando un'immagine bitmap su una texture, ciascun elemento della texture conterrà i byte associati ai colori di un pixel.

Le texture tridimensionali sono invece vettori di tre dimensioni su cui possiamo caricare, ad esempio, più immagini in successione. E questo è quello che faremo nel seguito, caricandovi in successione le sei immagini che vogliamo disegnare sulle facce del cubo.

Cosa cambia negli shader

Partiamo dal codice sorgente del precedente articolo, apportando progressivamente le modifiche necessarie.

Modifichiamo prima di tutto gli shader. Nel vertex shader dovremo aggiungere l'attributo texNum che ci permetterà di definire la texture che vogliamo sovrapporre sulla faccia che stiamo disegnando. Tale attributo viene passato al fragment shader tramite la variabile varying outTexNum.

// vertex shader
attribute highp vec3 vertex;
attribute mediump vec2 texCoord;
attribute mediump float texNum;

uniform highp mat4 matrix;

varying mediump vec2 outTexCoord;
varying mediump float outTexNum;

void main() {
    gl_Position = matrix * highp vec4(vertex, 1.0);
    outTexCoord = texCoord;
    outTexNum = texNum;
}

Nel fragment shader la texture sarà caricata nella variabile texture di tipo sampler3D.

// fragment shader
uniform highp sampler3D texture;

varying mediump vec2 outTexCoord;
varying mediump float outTexNum;

void main() {
    mediump vec2 flippedVertexCoord = vec2(outTexCoord.x, 1.0 - outTexCoord.y);
    gl_FragColor = texture3D( texture, vec3(flippedVertexCoord, outTexNum) );
}

Merita spendere due parole in più sulla funzione texture3D. I tre argomenti sono variabili float il cui valore di norma è compreso tra 0 e 1. Poiché caricheremo nella texture sei immagini, se impostiamo i parametri di interpolazione della texture a GL_NEAREST, la n-esima immagine potrà essere individuata con un valore numerico compreso tra $(n-1)/6$ e $n/6$. Poiché invece utilizzeremo GL_LINEAR (per evitare la sgranatura delle immagini), utilizzeremo i valori centrali di ciascun intervallo, individuando quindi l'n-esima immagine con il valore $(2n - 1) / 12$ (il punto medio degli estremi dell'intervallo visto prima).

Chiamata diretta delle API OpenGL

Il primo passo da compiere è modificare il VBO associato ai vertici, aggiungendo l'attributo texNum

struct VertexData {
    QVector3D coord;
    QVector2D texCoord;
    float texNum;
};

Quindi, nel metodo initializeGL(), modifichiamo il vettore contenente i dati da caricare sul VBO

  // *** VBO dati vertici ***
  // vettore contenete i dati associati ai vertici
  float l = 1.0 / 2.0;
  float DTextNum = 1.0f / 6.0f;
  float textNum0 = DTextNum * 0.50f;
  VertexData vertexData[] = {
      // faccia 0 - 1
      {QVector3D(-l, -l,  l), QVector2D(0.0f, 0.0f), textNum0},
      {QVector3D( l, -l,  l), QVector2D(1.0f, 0.0f), textNum0},
      {QVector3D(-l,  l,  l), QVector2D(0.0f, 1.0f), textNum0},
      {QVector3D( l,  l,  l), QVector2D(1.0f, 1.0f), textNum0},
      
      // faccia 1 - 2
      {QVector3D( l, -l,  l), QVector2D(0.0f, 0.0f), textNum0 + 1.0f * DTextNum},
      {QVector3D( l, -l, -l), QVector2D(1.0f, 0.0f), textNum0 + 1.0f * DTextNum},
      {QVector3D( l,  l,  l), QVector2D(0.0f, 1.0f), textNum0 + 1.0f * DTextNum},
      {QVector3D( l,  l, -l), QVector2D(1.0f, 1.0f), textNum0 + 1.0f * DTextNum},
      
      // faccia 2 - 6
      {QVector3D( l, -l, -l), QVector2D(0.0f, 0.0f), textNum0 + 5.0f * DTextNum},
      {QVector3D(-l, -l, -l), QVector2D(1.0f, 0.0f), textNum0 + 5.0f * DTextNum},
      {QVector3D( l,  l, -l), QVector2D(0.0f, 1.0f), textNum0 + 5.0f * DTextNum},
      {QVector3D(-l,  l, -l), QVector2D(1.0f, 1.0f), textNum0 + 5.0f * DTextNum},
      
      // faccia 3 - 5
      {QVector3D(-l, -l, -l), QVector2D(0.0f, 0.0f), textNum0 + 4.0f * DTextNum},
      {QVector3D(-l, -l,  l), QVector2D(1.0f, 0.0f), textNum0 + 4.0f * DTextNum},
      {QVector3D(-l,  l, -l), QVector2D(0.0f, 1.0f), textNum0 + 4.0f * DTextNum},
      {QVector3D(-l,  l,  l), QVector2D(1.0f, 1.0f), textNum0 + 4.0f * DTextNum},
      
      // faccia 4 - 3
      {QVector3D(-l, -l, -l), QVector2D(0.0f, 0.0f), textNum0 + 2.0f * DTextNum},
      {QVector3D( l, -l, -l), QVector2D(1.0f, 0.0f), textNum0 + 2.0f * DTextNum},
      {QVector3D(-l, -l,  l), QVector2D(0.0f, 1.0f), textNum0 + 2.0f * DTextNum},
      {QVector3D( l, -l,  l), QVector2D(1.0f, 1.0f), textNum0 + 2.0f * DTextNum},
      
      // faccia 5 - 4
      {QVector3D(-l,  l,  l), QVector2D(0.0f, 0.0f), textNum0 + 3.0f * DTextNum},
      {QVector3D( l,  l,  l), QVector2D(1.0f, 0.0f), textNum0 + 3.0f * DTextNum},
      {QVector3D(-l,  l, -l), QVector2D(0.0f, 1.0f), textNum0 + 3.0f * DTextNum},
      {QVector3D( l,  l, -l), QVector2D(1.0f, 1.0f), textNum0 + 3.0f * DTextNum}
  };

Dopodiché carichiamo i dati contenuti nel vettore come già visto

  // genera il VBO
  glGenBuffers(1, &m_vertexDataVBO);
  // attiva il VBO
  glBindBuffer(GL_ARRAY_BUFFER, m_vertexDataVBO);
  // carica nella memoria della GPU i valori contenuti in vertexData
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
  // disattiva il VBO
  glBindBuffer(GL_ARRAY_BUFFER, 0);

Per quanto riguarda la texture, i primi comandi sono analoghi a quelli già visti, con la sostituzione di GL_TEXTURE_2D con GL_TEXTURE_3D

  // *** texture ***
  // generiamo la texture
  glGenTextures( 1, &m_texture);
  // bind della texture
  glBindTexture(GL_TEXTURE_3D, m_texture);
  
  // Parametri per la gestione della texture negli shader
  // Ripete l'immagine se necessario
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  // quando l'area è più grande della texture, interpola linearmente l'originale
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  // quando l'area è più piccola della texture, interpola linearmente l'originale
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

Per la definizione della texture, disegneremo prima di tutto un'immagine, di modo da averne i dati dimensionali.

  // creiamo un'immagini
  QImage img = drawImage("1");
  

Definiamo quindi la texture senza caricare alcun dato per il momento (da notare l'argomento finale NULL)

  
  // carichiamo i bit che compongono l'immagine
  glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, img.width(), img.height(), 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );

Passiamo quindi a definire le singole texture con glTexSubImage3D

  glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, img.width(), img.height(), 1, GL_RGBA, GL_UNSIGNED_BYTE, img.bits() );
  for( int i=1; i < 6; ++i){
      QImage img = drawImage(QString::number(i+1));
      glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, i, img.width(), img.height(), 1, GL_RGBA, GL_UNSIGNED_BYTE, img.bits() );
  }

L'ultima modifica riguarda l'attivazione di texNum nel metodo paintGL()

  glEnableVertexAttribArray( m_texNumPos );
  glVertexAttribPointer( m_texNumPos,
                         1,                   // numero di elementi per vertice nel nostro caso 1
                         GL_FLOAT,            // tipo di elemento
                         GL_FALSE,
                         stride,
                         (const void *) (sizeof(QVector3D) + sizeof(QVector2D)) );

Compiliamo il codice e finalmente il nostro cubo ha tutti i numeri al posto giusto!

Su ogni faccia del cubo è applicata una texture diversa

Trovate il codice riepilogativo all'indirizzo ingegnerialibera.altervista.org/blog-file/opengltut04bis-01.zip.

La classe QOpenGLTexture

Nella creazione dell'istanza QOpenGLTexture specifichiamo che si tratta di una texture tridimensionale con il comando

// *** texture ***
m_texture = new QOpenGLTexture( QOpenGLTexture::Target3D );

Come fatto in precedenza, disegniamo una prima immagine per avere le relative dimensioni.

QImage img = drawImage("1");

Le dimensioni di un'immagine ci servono per definire la dimensione complessiva della texture tridimensionale e allocare la quantità di memoria necessaria

m_texture->setFormat( QOpenGLTexture::RGBA8_UNorm );
m_texture->setSize( img.width(), img.height(), 6);
m_texture->allocateStorage();

La classe QOpenGLTexture non ha metodi che permettano di disegnare porzioni limitate della texture, così come avvenuto nel caso precedente con il comando glTexSubImage3D. Perciò questa volta disegneremo prima le sei immagini in un vettore, e a seguire caricheremo in un'unica soluzione il contenuto del vettore nell'oggetto QOpenGLTexture

// numero di byte che compongono l'immagine
size_t imgSize = img.width()*img.height()*4;
// array di destinazione - uchar -> 1 byte
uchar fullImg[6][imgSize];
memcpy( fullImg, img.bits(), imgSize );
for( int i=1; i < 6; ++i){
    QImage img = drawImage(QString::number(i+1));
    memcpy( fullImg[i], img.bits(), imgSize );
}
// carichiamo i byte che compongono l'immagine
m_texture->setData( 0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, fullImg );

Per impostare i parametri necessari per la gestione della texture non usiamo il comando glTexParameteri, ma ci serviamo di metodo specifici della classe QOpenGLTexture

// Parametri per la gestione della texture negli shader
// Ripete l'immagine se necessario
m_texture->setWrapMode(QOpenGLTexture::Repeat);
// quando l'area è più grande della texture, interpola linearmente l'originale
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
// quando l'area è più piccola della texture, interpola linearmente l'originale
m_texture->setMinificationFilter(QOpenGLTexture::Linear);

Nel metodo paintGL(), per caricare la texture è sufficiente usare il metodo bind()

m_texture->bind();

Il risultato finale è identico a quello già visto in precedenza.

Lo stesso risultato del paragrafo precedente ottenuto usando la classe QOpenGLTexture

Il codice riepilogativo è disponibile all'indirizzo ingegnerialibera.altervista.org/blog-file/opengltut04bis-02.zip.

Potrebbero interessarti anche...

Comments


tutorial_qt/opengl_04bis_texture_3d.txt · Ultima modifica: 2015/05/21 23:27 da mickele

Facebook Twitter Google+ Digg Reddit LinkedIn StumbleUpon Email