Эффекты перекраски и пост-рендеринга на QGLView (не QGLWidget) в Qt3D

В настоящее время я пишу игру на C ++ / Qt5 с использованием модуля Qt3D.

Я могу визуализировать сцену (QGLSceneNodes) в QGLView, но теперь я застрял на перекрашивание сцены некоторыми элементами графического интерфейса. Я еще не решил, использовать ли QML или C ++ для определения внешнего вида интерфейса, поэтому я открыт для решений для обоих. (Обратите внимание, что модуль QML называется QtQuick3D, модуль C ++ называется Qt3D, и они оба являются частью Qt5.)

Я очень предпочитаю решение на основе QML.

Как я могу сделать некоторую перекраску?

Следующие вещи должны быть возможными:

  • Нарисуйте 2D-элементы (изображения) с заданными координатами экрана, конечно, с поддержкой альфа-каналов.
  • Рисуйте текст, используя системные шрифты, возможно, сначала нарисовав изображение и используя его в качестве текстуры в контексте Qt3D OpenGL.
  • Реагирование на ввод мыши в экранных координатах (в отличие от выбора объектов 3D-сцены).

Я думаю, что это все возможно, используя просто еще один QGLSceneNode для части 2D GUI. Но я думаю, что позиционирование и ориентация некоторого узла сцены для того, чтобы просто переориентироваться и перемещаться во время рендеринга (с использованием вершинного шейдера), не имеет смысла, вводит числовые ошибки и кажется неэффективным.

Правильно ли использовать другой вершинный шейдер «GUI»?

(Как) я могу сделать эффекты пост-рендеринга и перекраски работать вместе?

Было бы здорово, если бы я мог сделать следующие эффекты пост-рендеринга:

  • Прочитайте ранее визуализированную сцену, примените некоторые эффекты 2D-изображения (я могу представить, чтобы реализовать их как фрагментный шейдер и визуализировать ранее визуализированный буфер на конечном экране, используя этот шейдер эффектов). Это позволяет мне сделать некоторые части GUI похожими на стекло с эффектом размытия.
  • Некоторые более сложные эффекты могут сделать сцену более реалистичной: глубина & размытие в движении, зависящее от тепла мерцание воздуха, эффекты свечения. Все они могут быть описаны с использованием фрагментного шейдера (который не должен быть частью вопроса) и дополнительного буфера во время рендеринга. Их следует поместить между процессом рендеринга основного экрана и перерисовкой графического интерфейса, поэтому я включил его в этот вопрос.

Я вижу проблему при реализации перекраски с помощью специальной программы шейдеров: я не могу получить доступ к пикселям за графическим интерфейсом, чтобы применить некоторые эффекты, такие как размытие по Гауссу, чтобы сделать графический интерфейс похожим на стекло. Мне бы пришлось визуализировать сцену в другой буфер, а не на QGLView. Это моя главная проблема …

12

Решение

У меня были похожие проблемы с созданием игры opengl через qglwidget, и решение, которое я нашел, заключается в использовании ортогональной проекции для рендеринга элементов управления, созданных с нуля. В конце основной функции отрисовки я вызываю отдельную функцию, которая устанавливает вид, а затем рисует 2d биты. Что касается событий мыши, я фиксирую события, отправленные основному виджету, а затем использую комбинацию тестов попадания, рекурсивных смещений координат и псевдо-обратных вызовов, позволяющих вкладывать мои элементы управления. В моей реализации я пересылаю команды элементам управления верхнего уровня (в основном, полям прокрутки) по одному, но вы можете легко добавить структуру связанного списка, если вам нужно добавить элементы управления динамически. В настоящее время я просто использую квадраты для визуализации деталей в виде цветных панелей, но должно быть просто добавить текстуры или даже сделать их трехмерными компонентами, которые реагируют на динамическое освещение. Там много кода, так что я просто соберу соответствующие кусочки:

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++;
}

Вот код, который устанавливает матрицу вида / проекции матрицы для 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();
}

Это основная функция рисования, которая обрабатывает материал hud; большинство функций отрисовки напоминают большой блок «фоновой панели». Вы в основном нажимаете на матрицу вида модели, используете translate / rotate / scale, как в 3D, рисуете элементы управления, затем 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();
}

Теперь о самих элементах управления. Все мои элементы управления, такие как кнопки, ползунки, метки и т. Д., Унаследованы от общего базового класса с пустыми виртуальными функциями, которые позволяют им взаимодействовать друг с другом независимо от типа. База имеет типичные вещи, такие как высота, координаты и т. Д., А также несколько указателей для обработки обратных вызовов и рендеринга текста. Также включена общая функция hittest (x, y), которая сообщает, был ли на ней щелчок. Вы можете сделать это виртуальным, если вы хотите эллиптические элементы управления. Вот базовый класс, самый хитовый и код для простой кнопки:

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);
}
}

Поскольку составные элементы управления, такие как ползунки, имеют несколько кнопок, а поля прокрутки имеют плитки и ползунки, все это должно быть рекурсивным для событий рисования / мыши. Каждый элемент управления также имеет общую функцию обратного вызова, которая передает значение и свой собственный адрес; это позволяет родителям проверить каждого из своих детей, чтобы увидеть, кто звонит, и ответить соответствующим образом (например, кнопки вверх / вниз). Обратите внимание, что дочерние элементы управления добавляются через new / delete в c / dtors. Кроме того, функции draw () для дочерних элементов управления вызываются во время собственного вызова draw () родителей, что позволяет всему быть автономным. Вот соответствующий код из ползунка среднего уровня, который содержит 3 суб кнопки:

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);
}
}

Теперь, когда у нас есть автономные элементы управления, которые отображаются в контексте opengl, все, что нужно, — это интерфейс из элемента управления верхнего уровня, например, события мыши из 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...
}
}

В целом, эта система немного хакерская, и я не добавил некоторые функции, такие как отклик клавиатуры или изменение размера, но их должно быть легко добавить, возможно, требуя «тип события» в обратном вызове (то есть мышь или клавиатура). Вы можете даже создать подкласс glBase в самом верхнем виджете, и он даже позволит ему получить parent-> childcallback. Несмотря на то, что это большая работа, эта система требует только vanilla c ++ / opengl и использует гораздо меньше ресурсов процессора / памяти, чем нативный материал Qt, вероятно, на порядок или два. Если вам нужна дополнительная информация, просто спросите.

1

Другие решения

изменение размера и событий легко реализовать, особенно если вы делаете наследование класса от qglwidget
вот короткое видео, которое должно помочь вам начать

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

Что касается рисования изображений и текста, у Qt есть qpainter, который может помочь вам в этом,
использовать QGLContext (http://qt-project.org/doc/qt-4.8/qglcontext.html) передать ему текущий контекст и визуализировать вашу метку / изображение / виджет в определенном кординате, но убедитесь, что этот вызов функции является последним в вашем событии рисования, иначе он не будет сверху

чтобы отреагировать на событие мыши, включите в свой класс заголовок QMouseEvent, затем вы можете получить координаты мыши внутри виджета и передать их в opengl, перегружая защищенное событие mouseMoveEvent (QMouseEvent * event) {event-> pos ();}

1

По вопросам рекламы [email protected]