我目前正在使用 Qt3D 模块在 C++/Qt5 中编写游戏。
我可以在 QGLView 上渲染场景(QGLSceneNodes),但现在卡在 上,用一些 GUI 元素 覆盖场景。我还没有决定是使用 QML 还是 C++ 来定义界面的外观和感觉,所以我对两者的解决方案持开放态度。 (请注意,QML 模块称为 QtQuick3D,C++ 模块称为 Qt3D,它们都是 Qt5 的一部分。)
我非常喜欢基于 QML 的解决方案。
我该如何做一些重绘?
以下事情必须是可能的:
我认为这一切都可以使用另一个 QGLSceneNode 作为 2D GUI 部分。但是我认为定位和定向某些场景节点以便在渲染期间重新定向和重新定位(使用顶点着色器)没有意义,会引入数值错误并且似乎效率低下。
使用另一个“GUI”顶点着色器是正确的方法吗?
(如何)我可以让后期渲染效果和覆盖一起工作吗?
如果我可以做以下渲染后效果,那就太好了:
当我使用特殊的着色器程序实现覆盖时,我看到一个问题:我无法访问 GUI 后面的像素来应用一些效果,例如高斯模糊,使 GUI 看起来像玻璃。 我必须将场景渲染到另一个缓冲区而不是在 QGLView 上。这是我的主要问题...
最佳答案
我在通过 qglwidget 制作 opengl 游戏时也遇到了类似的问题,我想出的解决方案是使用正交投影来渲染临时构建的控件。在主绘制函数的最后,我调用了一个单独的函数来设置 View ,然后绘制 2d 位。至于鼠标事件,我捕获发送到主小部件的事件,然后结合使用 HitTest 、递归坐标偏移和允许嵌套控件的伪回调。在我的实现中,我一次一个地将命令转发到顶级控件(主要是滚动框),但如果需要动态添加控件,则可以轻松添加链接列表结构。我目前只是使用四边形将部件渲染为彩色面板,但添加纹理甚至使它们成为响应动态照明的 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 渲染设置模型 View /投影矩阵的代码:
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 内容的主要绘图函数;大多数绘图功能类似于大的“背景面板”块。您基本上推模型 View 矩阵,像在 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();
}
现在是控件本身。我的所有控件(如按钮、滑块、标签等)都继承自具有空虚函数的公共(public)基类,这些虚函数允许它们以类型不可知的方式相互交互。 base 具有典型的东西,如高度、坐标等,以及一些处理回调和文本渲染的指针。还包括通用的 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);
}
}
由于像滑块这样的复合控件有多个按钮,而滚动框有平铺和滑块,因此对于绘图/鼠标事件,它们都需要递归。每个控件还有一个通用的回调函数,它传递一个值和它自己的地址;这允许 parent 测试它的每个 child ,看看谁在打电话,并做出适当的回应(例如向上/向下按钮)。请注意,子控件是通过 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 上下文中的自包含控件,所有需要的是来自顶级控件的接口(interface),例如来自 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。虽然工作量很大,但这个系统只需要普通的 c++/opengl,而且它使用的 CPU/内存资源比原生 Qt 的东西少得多,可能要少一两个数量级。如果您需要更多信息,请询问。
关于c++ - Qt3D 中 QGLView(不是 QGLWidget)的重绘和渲染后效果,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12397522/