13

我目前正在使用 Qt3D 模块在 C++/Qt5 中编写游戏。

我可以在 QGLView 上渲染场景(QGLSceneNodes),但现在我坚持使用一些 GUI 元素对场景进行重绘。我还没有决定是使用 QML 还是 C++ 来定义界面的外观和感觉,所以我对两者的解决方案持开放态度。(请注意,QML 模块称为 QtQuick3D,C++ 模块称为 Qt3D,它们都是 Qt5 的一部分。)

我非常喜欢基于 QML 的解决方案。

我怎样才能做一些过度绘画?

以下事情必须是可能的:

  • 在给定的屏幕坐标处绘制 2D 项目(图像),当然支持 alpha 通道。
  • 使用系统字体绘制文本,可能首先在图像上绘画并将其用作 Qt3D OpenGL 上下文中的纹理。
  • 对屏幕坐标中的鼠标输入做出反应(与 3D 场景对象拾取相反)。

我认为这一切都是可能的,只需将另一个 QGLSceneNode 用于 2D GUI 部分。但我认为定位和定向某些场景节点以便在渲染期间重新定向和重新定位(使用顶点着色器)没有意义,会引入数值错误并且似乎效率低下。

使用另一个“GUI”顶点着色器是否正确?

(如何)我可以使后期渲染效果和重绘一起工作吗?

如果我能做以下渲染后效果,那就太好了:

  • 阅读之前渲染的场景,应用一些 2D 图像效果(我可以想象将它们实现为片段着色器,并使用此效果着色器在最终屏幕上渲染之前渲染的缓冲区)。这使我能够使 GUI 的某些部分看起来像具有模糊效果的玻璃。
  • 一些更高级的效果可以使场景看起来更逼真:深度和运动模糊、热相关的空气闪烁、发光效果。它们都可以使用片段着色器(这不应该是问题的一部分)和渲染期间的附加缓冲区来描述。它们应该放在主屏幕渲染过程和 GUI 重绘之间,这就是我将它包含在这个问题中的原因。

当我使用特殊的着色器程序实现重绘时,我发现了一个问题:我无法访问 GUI 后面的像素以应用高斯模糊等效果来使 GUI 看起来像玻璃。我必须将场景渲染到另一个缓冲区而不是 QGLView。这是我的主要问题...

4

2 回答 2

1

如果您从 qglwidget 继承一个类,则调整大小和事件很容易实现这里是一个简短的视频,可以帮助您入门

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

关于绘制图像和文本 qt 有 qpainter 可以帮助您做到这一点,使用 QGLContext ( http://qt-project.org/doc/qt-4.8/qglcontext.html ) 将当前上下文传递给它并渲染您的标签/图像/小部件在特定坐标处,但请确保此函数调用是您的绘画事件中的最后一个,否则它不会在顶部

要对鼠标事件做出反应,让您的类包含 QMouseEvent 标头,然后您可以在小部件中获取鼠标的坐标,并通过重载受保护的事件 mouseMoveEvent(QMouseEvent *event){event->pos();} 将它们传递给 opengl

于 2013-03-25T21:26:57.807 回答
1

我在通过 qglwidget 制作 opengl 游戏时也遇到过类似的问题,我想出的解决方案是使用正交投影来渲染从头构建的控件。在主绘制函数结束时,我调用了一个单独的函数来设置视图,然后绘制 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 的主要绘图函数;大多数绘图功能类似于大的“背景面板”块。您基本上推送模型视图矩阵,像在 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) 函数,它告诉它是否被点击。如果你想要椭圆控制,你可以把它变成虚拟的。这是基类、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);
  }
}

由于像滑块这样的复合控件有多个按钮,并且滚动框有图块和滑块,因此对于绘图/鼠标事件,它们都需要递归。每个控件还有一个通用的回调函数,它传递一个值和它自己的地址;这允许父母测试它的每个孩子,看看谁在打电话,并做出适当的回应(例如向上/向下按钮)。请注意,子控件是通过 c/dtors 中的 new/delete 添加的。此外,在父控件自己的 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...
  }
}

总的来说,这个系统有点hacky,我没有添加一些功能,如键盘响应或调整大小,但这些应该很容易添加,可能需要回调中的“事件类型”(即鼠标或键盘)。您甚至可以将 glBase 子类化到最顶层的小部件中,它甚至会允许它接收 parent->childcallback。虽然工作量很大,但这个系统只需要 vanilla c++/opengl,而且它使用的 cpu/内存资源比原生 Qt 的东西要少得多,可能是一两个数量级。如果您需要更多信息,请询问。

于 2012-09-17T17:03:51.173 回答