2012-09-12 13 views
12

Actualmente estoy escribiendo un juego en C++/Qt5 utilizando el módulo Qt3D.Efectos de sobreimpresión y post-render en QGLView (no QGLWidget) en Qt3D

Puedo representar la escena (QGLSceneNodes) en un QGLView pero ahora estoy atascado en sobrepintando la escena con algunos elementos de GUI. Todavía no he decidido si usar QML o C++ para definir el aspecto de la interfaz, por lo que estoy abierto a las soluciones para ambos. (Tenga en cuenta que el módulo QML se llama QtQuick3D, el módulo C++ se llama Qt3D y ambos son parte de Qt5).

Prefiero una solución basada en QML.

¿Cómo puedo hacer una sobreimpresión?

Los siguientes cosas tienen que ser posible:

  • Dibujar elementos 2D (imágenes) en las coordenadas de pantalla concreto, por supuesto, con soporte para canales alfa.
  • Dibuje texto usando las fuentes del sistema, tal vez pintando primero una imagen y usándola como una textura en el contexto de Qt3D OpenGL.
  • Reaccionando sobre la entrada del mouse en coordenadas de pantalla (en contraste con la selección de objeto de escena 3D).

Creo que todo esto es posible usando solo otro QGLSceneNode para la parte de la GUI 2D. Pero creo que posicionar y orientar un nodo de escena para simplemente reorientarlo y reubicarlo durante la renderización (usando el sombreador de vértices) no tiene sentido, introduce errores numéricos y parece ineficiente.

¿Está usando otro sombreador de vértices "GUI" de la manera correcta?

(¿Cómo puedo hacer que los efectos posteriores a la reproducción y la sobreimpresión funcionen juntos?

Sería muy bueno si yo puedo hacer los siguientes efectos de representación mensaje:

  • Lea la escena previamente prestado, aplicar algunos efectos de imagen en 2D (puedo imaginar para poner en práctica los como un fragment shader y renderizar el buffer previamente renderizado en la pantalla final usando este efecto shader). Esto me permite hacer que algunas partes de la GUI se parezcan al vidrio con un efecto de desenfoque.
  • Algunos efectos más avanzados pueden hacer que la escena parezca más realista: profundidad & desenfoque de movimiento, parpadeo de aire dependiente del calor, efectos de resplandor. Todos pueden describirse usando un sombreador de fragmentos (que no debería ser parte de la pregunta) y un búfer adicional durante el tiempo de renderizado. Deben colocarse entre el proceso de representación de la pantalla principal y la sobreimpresión de la GUI, esta es la razón por la que lo incluí en esta pregunta.

Veo un problema cuando implemento la sobreimpresión usando un programa de sombreador especial: No puedo acceder a los píxeles detrás de la GUI para aplicar algunos efectos como desenfoque gaussiano para hacer que la GUI parezca cristal. Tendría que representar la escena en otro búfer en lugar de hacerlo en QGLView. Este es mi principal problema ...

+0

Me acabo de dar cuenta de que puedo simplemente configurar el modo de procesamiento en ortonormal en lugar de perspectiva. Esto hace innecesario el sombreado GUI. Pero aún así, creo que tiene que haber una forma incorporada de hacer overpainting en Qt3D (ya que hay un método en el viejo y viejo QGLWidget (que no quiero usar ya que está en el módulo de widgets Qt5 y no puedo) render usando QGLPainter)). – leemes

+0

Esto parece ser una posibilidad (suena muy bien): http://comments.gmane.org/gmane.comp.lib.qt.3d/264 – leemes

Respuesta

1

He tenido problemas similares al hacer un juego OpenGL también a través de qglwidget, y la solución que se me ocurrió es el uso de proyección ortográfica para generar controles creados con scratch. Al final de la función de dibujo principal, llamo a una función separada que configura la vista y luego dibuja los 2d bits. En cuanto a los eventos del mouse, capturo los eventos enviados al widget principal y luego uso una combinación de pruebas de aciertos, compensaciones de coordenadas recursivas y pseudo-devoluciones de llamada que permiten anidar mis controles. En mi implementación, reenvío comandos a los controles de nivel superior (principalmente cuadros de desplazamiento) de a uno por vez, pero puede agregar fácilmente una estructura de listas enlazadas si necesita agregar controles dinámicamente. Actualmente solo uso cuadrantes para representar las piezas como paneles de colores, pero debería ser simple agregar texturas o incluso hacer que sean componentes en 3D que responden a la iluminación dinámica. Hay una gran cantidad de código, así que sólo voy a tirar los bits correspondientes en las piezas:

void GLWidget::paintGL() { 
    if (isPaused) return; 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

    glPushMatrix(); 

    glLightfv(GL_LIGHT0, GL_POSITION, FV0001); 

    gluLookAt(...); 

    // Typical 3d stuff 

    glCallList(Body::glGrid); 

    glPopMatrix(); 

//non3d bits 
    drawOverlay(); // <---call the 2d rendering 

    fpsCount++; 
} 

Aquí está el código que configura la matriz modelview/proyección para la representación 2d:

void GLWidget::drawOverlay() { 
    glClear(GL_DEPTH_BUFFER_BIT); 

    glPushMatrix(); //reset 
    glLoadIdentity(); //modelview 

    glMatrixMode(GL_PROJECTION);//set ortho camera 
    glPushMatrix(); 
    glLoadIdentity(); 
    gluOrtho2D(0,width()-2*padX,height()-2*padY,0); // <--critical; padx/y is just the letterboxing I use 
    glMatrixMode(GL_MODELVIEW); 

    glDisable(GL_DEPTH_TEST); // <-- necessary if you want to draw things in the order you call them 
    glDisable(GL_LIGHTING); // <-- lighting can cause weird artifacts 

    drawHUD(); //<-- Actually does the drawing 

    glEnable(GL_LIGHTING); 
    glEnable(GL_DEPTH_TEST); 

    glMatrixMode(GL_PROJECTION); glPopMatrix(); 
    glMatrixMode(GL_MODELVIEW); glPopMatrix(); 
} 

Este es la función principal de dibujo que maneja las cosas hud; la mayoría de las funciones de dibujo se parecen al gran trozo de 'panel de fondo'. Es, básicamente, empuja la matriz modelview, utiliza traducir/rotación/escala como lo haría en 3D, pintar los controles, a continuación, popMatrix:

void GLWidget::drawHUD() { 
    float gridcol[] = { 0.5, 0.1, 0.5, 0.9 }; 
    float gridcolB[] = { 0.3, 0.0, 0.3, 0.9 }; 

//string caption 
    QString tmpStr = QString::number(FPS); 
    drawText(tmpStr.toAscii(),120+padX,20+padY); 

//Markers 
    tGroup* tmpGroup = curGroup->firstChild; 

    while (tmpGroup != 0) { 
    drawMarker(tmpGroup->scrXYZ); 
    tmpGroup = tmpGroup->nextItem; 
    } 

//Background panel 
    glEnable(GL_BLEND); 
    glTranslatef(0,height()*0.8,0); 
    glBegin(GL_QUADS); 
    glColor4fv(gridcol); 
    glVertex2f(0.0f, 0.0); 
    glVertex2f(width(), 0); 
    glColor4fv(gridcolB); 
    glVertex2f(width(), height()*0.2); 
    glVertex2f(0.0f, height()*0.2); 
    glEnd(); 
    glDisable(GL_BLEND); 

    glLoadIdentity(); //nec b/c of translate ^^ 
    glTranslatef(-padX,-padY,0); 

//Controls 
    cargoBox->Draw(); 
    sideBox->Draw(); 
    econBox->Draw(); 
} 

ya por los propios controles. Todos mis controles, como botones, controles deslizantes, etiquetas, etc., pertenecen a una clase base común con funciones virtuales vacías que les permiten interactuar de forma independiente entre sí. La base tiene elementos típicos como la altura, las coordenadas, etc., así como algunas sugerencias para gestionar las devoluciones de llamada y la representación del texto. También se incluye la función de hittest genérico (x, y) que indica si se ha hecho clic en ella. Podrías hacer esto virtual si quieres controles elípticos. Esta es la clase base, la hittest, y el código para un simple botón .:

class glBase { 
public: 
    glBase(float tx, float ty, float tw, float th); 

    virtual void Draw() {return;} 

    virtual glBase* mouseDown(float xIn, float yIn) {return this;} 
    virtual void mouseUp(float xIn, float yIn) {return;} 
    virtual void mouseMove(float xIn, float yIn) {return;} 

    virtual void childCallBack(float vIn, void* caller) {return;} 

    bool HitTest(float xIn, float yIn); 

    float xOff, yOff; 
    float width, height; 

    glBase* parent; 

    static GLWidget* renderer; 
protected: 
    glBase* callFwd; 
}; 


bool glBase::HitTest(float xIn, float yIn) { //input should be relative to parents space 
    xIn -= xOff; yIn -= yOff; 
    if (yIn >= 0 && xIn >= 0) { 
    if (xIn <= width && yIn <= height) return true; 
    } 
    return false; 
} 

class glButton : public glBase { 
public: 
    glButton(float tx, float ty, float tw, float th, char rChar); 

    void Draw(); 

    glBase* mouseDown(float xIn, float yIn); 
    void mouseUp(float xIn, float yIn); 
private: 
    bool isPressed; 
    char renderChar; 
}; 

glButton::glButton(float tx, float ty, float tw, float th, char rChar) : 
glBase(tx, ty, tw, th), isPressed(false), renderChar(rChar) {} 

void glButton::Draw() { 
    float gridcolA[] = { 0.5, 0.6, 0.5, 1.0 };//up 
    float gridcolB[] = { 0.2, 0.3, 0.2, 1.0 }; 
    float gridcolC[] = { 1.0, 0.2, 0.1, 1.0 };//dn 
    float gridcolD[] = { 1.0, 0.5, 0.3, 1.0 }; 
    float* upState; 
    float* upStateB; 

    if (isPressed) { 
    upState = gridcolC; 
    upStateB = gridcolD; 
    } else { 
    upState = gridcolA; 
    upStateB = gridcolB; 
    } 

    glPushMatrix(); 
    glTranslatef(xOff,yOff,0); 

    glBegin(GL_QUADS); 
    glColor4fv(upState); 
    glVertex2f(0, 0); 
    glVertex2f(width, 0); 
    glColor4fv(upStateB); 
    glVertex2f(width, height); 
    glVertex2f(0, height); 
    glEnd(); 

    if (renderChar != 0) //center 
    renderer->drawChar(renderChar, (width - 12)/2, (height - 16)/2); 

    glPopMatrix(); 
} 

glBase* glButton::mouseDown(float xIn, float yIn) { 
    isPressed = true; 
    return this; 
} 

void glButton::mouseUp(float xIn, float yIn) { 
    isPressed = false; 
    if (parent != 0) { 
    if (HitTest(xIn, yIn)) parent->childCallBack(0, this); 
    } 
} 

Dado que los controles compuestos como reguladores de tener varios botones, y scrollboxes tienen azulejos y deslizadores, todo tiene que ser recursivo para el dibujo/eventos de mouse. Cada control también tiene una función de devolución de llamada genérica que pasa un valor y su propia dirección; esto le permite al padre probar cada uno de sus hijos para ver quién llama y responder apropiadamente (por ejemplo, botones arriba/abajo). Tenga en cuenta que los controles secundarios se agregan a través de new/delete en los c/dtors. Además, se llama a las funciones draw() para los controles secundarios durante la propia llamada draw() de los padres, que permite que todo sea independiente. Aquí está el código relevante de una barra de desplazamiento de nivel medio que contiene 3 botones sub:

glBase* glSlider::mouseDown(float xIn, float yIn) { 
    xIn -= xOff; yIn -= yOff;//move from parent to local coords 
    if (slider->HitTest(xIn,yIn-width)) { //clicked slider 
    yIn -= width; //localize to field area 
    callFwd = slider->mouseDown(xIn, yIn); 
    dragMode = true; 
    dragY = yIn - slider->yOff; 
    } else if (upButton->HitTest(xIn,yIn)) { 
    callFwd = upButton->mouseDown(xIn, yIn); 
    } else if (downButton->HitTest(xIn,yIn)) { 
    callFwd = downButton->mouseDown(xIn, yIn); 
    } else { 
    //clicked in field, but not on slider 
    } 

    return this; 
}//TO BE CHECKED (esp slider hit) 

void glSlider::mouseUp(float xIn, float yIn) { 
    xIn -= xOff; yIn -= yOff; 
    if (callFwd != 0) { 
    callFwd->mouseUp(xIn, yIn); //ok to not translate b/c slider doesn't callback 
    callFwd = 0; 
    dragMode = false; 
    } else { 
    //clicked in field, not any of the 3 buttons 
    } 
} 

void glSlider::childCallBack(float vIn, void* caller) { //can use value blending for smooth 
    float tDelta = (maxVal - minVal)/(10); 

    if (caller == upButton) {//only gets callbacks from ud buttons 
    setVal(Value - tDelta); 
    } else { 
    setVal(Value + tDelta); 
    } 
} 

Ahora que tenemos los controles independientes que hacen en un contexto OpenGL, lo único que se necesita es la interfaz del control de nivel superior , por ejemplo, eventos de mouse desde el glwidget.

void GLWidget::mousePressEvent(QMouseEvent* event) { 
    if (cargoBox->HitTest(event->x()-padX, event->y()-padY)) { //if clicked first scrollbox 
    callFwd = cargoBox->mouseDown(event->x()-padX, event->y()-padY); //forward the click, noting which last got 
    return; 
    } else if (sideBox->HitTest(event->x()-padX, event->y()-padY)) { 
    callFwd = sideBox->mouseDown(event->x()-padX, event->y()-padY); 
    return; 
    } else if (econBox->HitTest(event->x()-padX, event->y()-padY)) { 
    callFwd = econBox->mouseDown(event->x()-padX, event->y()-padY); 
    return; 
    } 

    lastPos = event->pos(); //for dragging 
} 

void GLWidget::mouseReleaseEvent(QMouseEvent *event) { 
    if (callFwd != 0) { //Did user mousedown on something? 
    callFwd->mouseUp(event->x()-padX, event->y()-padY); 
    callFwd = 0; //^^^ Allows you to drag off a button before releasing w/o triggering its callback 
    return; 
    } else { //local 
    tBase* tmpPick = pickObject(event->x(), event->y()); 
    //normal clicking, game stuff... 
    } 
} 

En general este sistema es un poco hacky, y no he añadido algunas características como la respuesta del teclado o el cambio de tamaño, pero éstos deben ser fáciles de añadir, tal vez requiera un 'tipo de evento' en la devolución de llamada (es decir, del ratón o teclado). Incluso podría subclase el glBase en el widget superior, e incluso le permitirá recibir el parent-> childcallback. Aunque es mucho trabajo, este sistema solo requiere vainilla C++/opengl y usa muchos menos recursos de CPU/memoria que las cosas nativas de Qt, probablemente en un orden de magnitud o dos. Si necesita más información, solo pregunte.

1

de cambio de tamaño y los eventos son fáciles de implementar, especialmente si usted hace una clase herede de qglwidget aquí es un breve vídeo que debería ayudar a empezar

https://www.youtube.com/watch?v=1nzHSkY4K18

en lo que respecta a las imágenes de dibujo y el texto qt tiene El qpainter que puede ayudarlo a hacerlo, use QGLContext (http://qt-project.org/doc/qt-4.8/qglcontext.html) para pasarle el contexto actual y mostrar su etiqueta/imagen/widget en el cordinate específico, pero asegúrese de que esta función sea la última en su evento de pintura. no estará en la parte superior

para reaccionar al evento mouse haga que su clase incluya el encabezado QMouseEvent, luego puede obtener las coordenadas del mouse dentro del widget y pasarlas a opengl al sobrecargar el evento protegido mouseMoveEvent (evento QMouseEvent *) {event-> pos();}

Cuestiones relacionadas