13

現在、Qt3D モジュールを使用して C++/Qt5 でゲームを作成しています。

QGLView でシーン (QGLSceneNodes) をレンダリングできますが、いくつかの GUI 要素でシーンをオーバーペイントすることに行き詰まっています。インターフェイスのルック アンド フィールを定義するために QML と C++ のどちらを使用するかはまだ決めていないので、両方のソリューションを受け入れます。(QML モジュールは QtQuick3D と呼ばれ、C++ モジュールは Qt3D と呼ばれ、どちらも Qt5 の一部であることに注意してください。)

私は QML ベースのソリューションを非常に好みます。

どうすれば上塗りできますか?

次のことが可能でなければなりません。

  • もちろん、アルファチャンネルをサポートして、指定された画面座標で2Dアイテム(画像)を描画します。
  • システム フォントを使用してテキストを描画します。最初に画像にペイントし、これを Qt3D OpenGL コンテキストでテクスチャとして使用します。
  • 画面座標でのマウス入力への反応 (3D シーン オブジェクトのピッキングとは対照的)。

2D GUI 部分に別の QGLSceneNode を使用するだけで、これはすべて可能だと思います。しかし、レンダリング中に(頂点シェーダーを使用して)再配置および再配置するためにシーンノードを配置および方向付けすることは意味がなく、数値エラーが発生し、非効率的だと思います。

別の「GUI」頂点シェーダーを使用するのは正しい方法ですか?

(どのように)ポストレンダリング効果とオーバーペイントを連携させることができますか?

次のポスト レンダリング エフェクトを実行できれば、非常に便利です。

  • 以前にレンダリングされたシーンを読み取り、いくつかの 2D イメージ エフェクトを適用します (これらをフラグメント シェーダーとして実装し、このエフェクト シェーダーを使用して最終画面で以前にレンダリングされたバッファーをレンダリングすることが想像できます)。これにより、GUI の一部をぼかし効果のあるガラスのように見せることができます。
  • より高度な効果により、シーンをよりリアルに見せることができます: 深度とモーション ブラー、熱に依存する空気のちらつき、グロー効果。それらはすべて、フラグメント シェーダー (これは問題の一部であってはなりません) と、レンダリング時に追加のバッファーを使用して記述できます。それらは、メイン画面のレンダリング プロセスと GUI のオーバーペイントの間に配置する必要があります。これが、この質問に含めた理由です。

特別なシェーダー プログラムを使用してオーバーペイントを実装すると問題が発生します。GUI の背後にあるピクセルにアクセスして、ガウス ブラーなどの効果を適用して GUI をガラスのように見せることができません。QGLView ではなく、別のバッファにシーンをレンダリングする必要があります。これが私の主な問題です...

4

2 に答える 2

1

qglwidgetを介してopenglゲームを作成する場合にも同様の問題が発生しました。私が思いついた解決策は、正投影を使用してスクラッチで作成されたコントロールをレンダリングすることです。メインの描画関数の最後に、ビューを設定して2Dビットを描画する別の関数を呼び出します。マウスイベントについては、メインウィジェットに送信されたイベントをキャプチャしてから、ヒットテスト、再帰的な座標オフセット、およびコントロールをネストできる疑似コールバックの組み合わせを使用します。私の実装では、コマンドを一度に1つずつトップレベルのコントロール(主にスクロールボックス)に転送しますが、コントロールを動的に追加する必要がある場合は、リンクリスト構造を簡単に追加できます。現在、クワッドを使用してパーツをカラーパネルとしてレンダリングしていますが、テクスチャを追加したり、動的な照明に応答する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のものを処理するメインの描画関数です。ほとんどの描画関数は、大きな「バックラウンドパネル」チャンクに似ています。基本的に、modelviewマトリックスをプッシュし、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);
  }
}

スライダーのような複合コントロールには複数のボタンがあり、スクロールボックスにはタイルとスライダーがあるため、描画/マウスイベントに対してすべてを再帰的に行う必要があります。各コントロールには、値とそれ自体のアドレスを渡す汎用コールバック関数もあります。これにより、親は子のそれぞれをテストして、誰が呼び出しているかを確認し、適切に応答することができます(たとえば、上/下ボタン)。子コントロールは、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...
  }
}

全体的に、このシステムは少しハッキーで、キーボードの応答やサイズ変更などの機能は追加していませんが、これらは簡単に追加できるはずで、コールバックに「イベントタイプ」(つまり、マウスまたはキーボード)が必要になる場合があります。glBaseを最上位ウィジェットにサブクラス化することもでき、親->子コールバックを受信することもできます。大変な作業ですが、このシステムに必要なのはバニラc ++ / openglだけであり、ネイティブのQtのものよりもはるかに少ないCPU/メモリリソースを使用します。さらに情報が必要な場合は、お問い合わせください。

于 2012-09-17T17:03:51.173 に答える
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 に答える