Подтвердить что ты не робот

Эффекты overpainting и post-render на QGLView (не QGLWidget) в Qt3D

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

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

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

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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, рисовать элементы управления, а затем попматрикс:

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), которая сообщает, была ли она нажата. Вы можете сделать это виртуальным, если хотите эллиптические элементы управления. Вот базовый класс, hittest и код для простой кнопки.:

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 в самый верхний виджет, и он даже позволит ему получить родительский → childcallback. Хотя для этой работы много работы, для этой системы требуется только ванильный С++/opengl, и он использует намного меньше ресурсов процессора/памяти, чем собственный материал Qt, вероятно, на порядок или два. Если вам нужна дополнительная информация, просто спросите.

Ответ 2

изменение размера и событий легко реализовать специально, если вы наследуете класс от 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- > pos(); }