====== Qt e OpenGL - Le texture e la classe QOpenGLTexture ====== In questo articolo parleremo di texture. Dopo una breve introduzione su cosa sono le texture nella programmazione openGL, vedremo come implementarle con gli strumenti standard delle OpenGL che con quelli specifici presenti all'interno delle Qt. Continua così la panoramica sulla programmazione OpenGL e le Qt già cominciata con i precedenti articoli: * [[tutorial_qt:opengl_01|Qt e OpenGL: Introduzione]] * [[tutorial_qt:opengl_01bis|Qt e OpenGL: Intro alla classe QOpenGLWidget]] * [[tutorial_qt:opengl_02_vbo_qopenglbuffer|Qt e OpenGL: i Vertex Buffer Object e la classe QOpenGLBuffer]] * [[tutorial_qt:opengl_03_ibo_qopenglbuffer|Qt e OpenGL: creare un Index Buffer Object con la classe QOpenGLBuffer]] ===== Cosa sono le texture? ===== Partiamo da zero definendo brevemente cosa sono le texture. Se avete avuto precedenti esperienze con la programmazione 3D probabilmente collegherete le texture alle immagini bitmap e sicuramente l'associazione è corretta. Le texture però sono uno strumento che può essere applicato //anche// anche per definire e caricare immagini sulla GPU . Fondamentalmente le texture sono dei vettori che possiamo usare in generale per caricare sulla GPU vettori da 1 a 3 dimensioni con un numero di elementi praticamente arbitrario (l'unico limite è connesso che le risorse hardware che abbiamo a disposizione). ~~READMORE~~ Poiché un immagine bitmap è una vettore bidimensionale di byte contenenti informazioni sui colori di ciascun pixel dell'immagine, si capisce perché la texture si presti agevolmente come strumento per caricare immagini sulla GPU. ===== Le texture con le API OpenGL ===== Per apprendere l'uso delle texture bidimensionali, ridisegniamo il cubo visto nel tutorial precedente, disegnando un'immagine su ciascuna faccia. Per iniziare l'immagine sarà costituita da un numero che creeremo con la funzione drawImage QImage GLWidget::drawImage( const QString & txt ){ int border = 10; int side = qMax( fontMetrics().width(txt) + border , fontMetrics().height() + border ); QPixmap pixmap( side, side ); pixmap.fill(Qt::white); QPainter painter; painter.begin(&pixmap); painter.setRenderHints(QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing); painter.setFont(font()); painter.setPen( Qt::red ); painter.drawText(QRect(0,0,side,side), Qt::AlignCenter, txt ); painter.end(); return pixmap.toImage().convertToFormat( QImage::Format_RGBA8888 ); } Con l'ultima riga di comando abbiamo convertito l'immagine nel formato RGBA, di modo che ad ogni pixel siano associati quattro byte (rosso, verde, blu e trasparenza). Passiamo ora alla programmazione OpenGL vera e propria partendo dagli shader. Per gestire la texture bidimensionale associata all'immagine introduciamo nel fragment shader la variabile texture di tipo sampler2D: si tratta di un vettore bidimensionale di elementi vec4, la cui dimensione verrà definita al momento della creazione della texture nel programma. Dobbiamo inoltre associare ad ogni vertice un vettore bidimensionale contenente le coordinate di mappatura dell'immagine, vale a dire i parametri sulla base dei quali "spalmare" l'immagine sulla faccia. Le coordinate (0, 0) sono associate all'angolo in basso a sinistra, (1, 1) all'angolo in alto a destra. Ecco un semplice vertex shader con le funzionalità necessarie // vertex shader attribute highp vec3 vertex; attribute mediump vec2 texCoord; uniform highp mat4 matrix; varying mediump vec2 outTexCoord; void main() { gl_Position = matrix * highp vec4(vertex, 1.0); outTexCoord = texCoord; } E questo è il corrispondente fragment shader // fragment shader uniform highp sampler2D texture; varying mediump vec2 outTexCoord; void main() { mediump vec2 flippedVertexCoord = vec2(outTexCoord.x, 1.0 - outTexCoord.y); gl_FragColor = texture2D(texture, flippedVertexCoord); } Come abbiamo già visto in precedenza, aggiungiamo un attributo privato di tipo GLuint per individuare la texture che caricheremo in memoria class GLWidget: public QOpenGLWidget { ... private: ... GLuint m_texture; ... }; Diversamente da quanto visto nei precedenti articoli, per ottimizzare le risorse di sistema introduciamo un ulteriore livello di complessità: non creiamo un VBO a parte per le coordinate di mappatura della texture, ma ne utilizziamo uno unico sia per le coordinate nello spazio che per quelle di mappatura. Dopo aver definito una struct VertexData del tipo struct VertexData { QVector3D coord; QVector2D texCoord; }; nel membro initializeGL() scriviamo void GLWidget::initializeGL(){ ... // vettore contenete i dati associati ai vertici double l = 1.0 / 2.0; VertexData vertexData[] = { // faccia 0 {QVector3D(-l, -l, l), QVector2D(0.0f, 0.0f)}, {QVector3D( l, -l, l), QVector2D(1.0f, 0.0f)}, {QVector3D(-l, l, l), QVector2D(0.0f, 1.0f)}, {QVector3D( l, l, l), QVector2D(1.0f, 1.0f)}, // faccia 1 {QVector3D( l, -l, l), QVector2D(0.0f, 0.0f)}, {QVector3D( l, -l, -l), QVector2D(1.0f, 0.0f)}, {QVector3D( l, l, l), QVector2D(0.0f, 1.0f)}, {QVector3D( l, l, -l), QVector2D(1.0f, 1.0f)}, // faccia 2 {QVector3D( l, -l, -l), QVector2D(0.0f, 0.0f)}, {QVector3D(-l, -l, -l), QVector2D(1.0f, 0.0f)}, {QVector3D( l, l, -l), QVector2D(0.0f, 1.0f)}, {QVector3D(-l, l, -l), QVector2D(1.0f, 1.0f)}, // faccia 3 {QVector3D(-l, -l, -l), QVector2D(0.0f, 0.0f)}, {QVector3D(-l, -l, l), QVector2D(1.0f, 0.0f)}, {QVector3D(-l, l, -l), QVector2D(0.0f, 1.0f)}, {QVector3D(-l, l, l), QVector2D(1.0f, 1.0f)}, // faccia 4 {QVector3D(-l, -l, -l), QVector2D(0.0f, 0.0f)}, {QVector3D( l, -l, -l), QVector2D(1.0f, 0.0f)}, {QVector3D(-l, -l, l), QVector2D(0.0f, 1.0f)}, {QVector3D( l, -l, l), QVector2D(1.0f, 1.0f)}, // faccia 5 {QVector3D(-l, l, l), QVector2D(0.0f, 0.0f)}, {QVector3D( l, l, l), QVector2D(1.0f, 0.0f)}, {QVector3D(-l, l, -l), QVector2D(0.0f, 1.0f)}, {QVector3D( l, l, -l), QVector2D(1.0f, 1.0f)} }; Definite le informazioni da caricare, passiamo a creare il VBO // 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 creare la texture diamo invece i comandi // generiamo il Texture Object glGenTextures( 1, &m_texture); // bind del TO glBindTexture(GL_TEXTURE_2D, m_texture); // modifichiamo l'immagine per adattarle QImage img = drawImage("8").convertToFormat(QImage::Format_RGBA8888); // carichiamo i bit che compongono l'immagine glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.width(), img.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img.bits() ); Per la gestione della texture nello shader possiamo definire alcune opzioni // Ripete l'immagine se le coordinate di mappatura sono maggiori di 1 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // quando l'area è più grande della texture, interpola linearmente l'originale glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // quando l'area è più piccola della texture, interpola linearmente l'originale glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); A questo punto dobbiamo definire l'IBO; rimandiamo a quanto già visto nel [[[[tutorial_qt:opengl_03_ibo_qopenglbuffer|precedente articolo]]. Ora disegniamo il cubo nel metodo paintGL(). Per come abbiamo caricato i dati associati ai vertici, dopo aver effettuato il binding del VBO, dovremo fare attenzione ai parametri della funzione glAttribPointer( ... ), definendo stride (distanza tra l'inizio dei dati di un vertice e l'inizio dei dati del successivo) e offset (posizione dei dati che stiamo caricando nella struct VertexData) // Attiviamo il VBO dei vertici glBindBuffer(GL_ARRAY_BUFFER, m_vertexDataVBO); // attiva il VBO glEnableVertexAttribArray( m_vertexPos ); GLsizei stride = sizeof(VertexData); glVertexAttribPointer( m_vertexPos, 3, // numero di elementi per vertice: 3 (x,y,z) GL_FLOAT, GL_FALSE, stride, // distanza inizio-inizio (const void *) 0 ); // posizione dati in VertexData glEnableVertexAttribArray( m_texCoordPos ); glVertexAttribPointer( m_texCoordPos, 2, // numero di elementi per vertice: 2 (x,y) GL_FLOAT, GL_FALSE, stride, // distanza inizio-inizio (const void *) sizeof(QVector3D) ); // posizione dati in VertexData Per attivare la texture sull'unità 0 daremo invece i comandi // attiviamo l'unita' 0 glActiveTexture(GL_TEXTURE0); // binding del TO glBindTexture(GL_TEXTURE_2D, m_texture); // impostiamo il valore nell'unita' 0 m_program->setUniformValue( m_texturePos, 0); Al momento di eliminare la classe ricordiamoci di liberare la memoria occupata dalla texture con la funzione glDeleteTextures GLWidget::~GLWidget(){ ... glDeleteTextures(1, &m_texture); ... } Ed ecco il risultato finale {{ :tutorial_qt:opengltut04-01.png |Alle facce del cubo è stata applicata un'immagine}} Il codice sorgente completo è disponibile al link [[http://ingegnerialibera.altervista.org/blog-file/opengltut04-01.zip|ingegnerialibera.altervista.org/blog-file/opengltut04-01.zip]]. ===== Le texture con QOpenGLTexture ===== Usando la classe QOpenGLTexture, la creazione della texture per un'immagine singola è decisamente agevole. Prima di tutto aggiungiamo un attributo privato di tipo QOpenGLTexture alla classe class GLWidget: public QOpenGLWidget { ... private: QOpenGLTexture * m_texture; ... }; Nel metodo initializeGL() creiamo la texture e carichiamo l'immagine con una sola linea di codice m_texture = new QOpenGLTexture( drawImage("8") ); E' possibile impostare le stesse variabili di stato già viste in precedenza con i comandi // quando l'area è più piccola della texture, interpola linearmente l'originale m_texture->setMinificationFilter(QOpenGLTexture::Nearest); // quando l'area è più grande della texture, interpola linearmente l'originale m_texture->setMagnificationFilter(QOpenGLTexture::Linear); // Ripete l'immagine se le coordinate di mappatura sono maggiori di 1 m_texture->setWrapMode(QOpenGLTexture::Repeat); Disegniamo l'immagine nel metodo paintGL() con i comandi // *** texture *** // attiviamo l'unita' 0 glActiveTexture(GL_TEXTURE0); // attiviamo la texture m_texture->bind(); // impostiamo il valore nell'unita' 0 m_program->setUniformValue( m_texturePos, 0); Il risultato finale è identico a quello del precedente paragrafo. Al link [[http://ingegnerialibera.altervista.org/blog-file/opengltut04-02.zip|ingegnerialibera.altervista.org/blog-file/opengltut04-02.zip]] trovate il codice completo. ===== Articoli correlati ===== blog default tag 'tutorial qt opengl'