Tôi cũng gặp các sự cố tương tự khi tạo trò chơi opengl qua qglwidget và giải pháp mà tôi đưa ra là sử dụng phép chiếu chính xác để hiển thị các điều khiển được tạo bằng đầu. Ở phần cuối của chức năng vẽ chính, tôi gọi một hàm riêng biệt để thiết lập khung nhìn và sau đó vẽ các bit 2d. Đối với các sự kiện chuột, tôi nắm bắt các sự kiện được gửi đến tiện ích chính, sau đó sử dụng kết hợp các lần kiểm tra lượt truy cập, các tọa độ đệ quy và các cuộc gọi lại giả cho phép các điều khiển của tôi được lồng vào nhau. Trong quá trình thực hiện, tôi chuyển tiếp các lệnh tới các điều khiển cấp cao nhất (chủ yếu là các hộp cuộn) một lần, nhưng bạn có thể dễ dàng thêm cấu trúc danh sách liên kết nếu bạn cần thêm các điều khiển động. Tôi hiện đang chỉ sử dụng quads để render các phần như các bảng màu, nhưng nó sẽ được đơn giản để thêm kết cấu hoặc thậm chí làm cho chúng thành phần 3d phản ứng với ánh sáng năng động. Có rất nhiều mã, vì vậy tôi chỉ sẽ ném các bit tương ứng trong phần:
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++;
}
Dưới đây là đoạn code mà thiết lập ma trận modelview/chiếu cho dựng hình 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();
}
này là chức năng vẽ chính xử lý các nội dung hud; hầu hết các chức năng vẽ giống với đoạn 'backround panel' lớn. Về cơ bản bạn đẩy ma trận modelview, sử dụng dịch/xoay/quy mô như bạn sẽ trong 3d, vẽ các điều khiển, sau đó 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();
}
Bây giờ cho các điều khiển bản thân. Tất cả các điều khiển của tôi như nút bấm, thanh trượt, nhãn, v.v. đều không hút thuốc từ một lớp cơ sở chung với các chức năng ảo trống cho phép chúng tương tác với nhau theo kiểu agnostically. Các cơ sở có các công cụ điển hình như chiều cao, tọa độ, vv cũng như một vài gợi ý để xử lý các cuộc gọi lại và hiển thị văn bản. Cũng bao gồm hàm hittest chung (x, y) cho biết nếu nó được nhấp vào. Bạn có thể làm điều này ảo nếu bạn muốn điều khiển elip. Đây là lớp cơ sở, các hitTest, và mã cho một nút đơn giản .:
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);
}
}
Kể từ khi điều khiển kép như thanh trượt có nhiều nút, và scrollboxes có gạch và thanh trượt, tất cả cần phải được đệ quy cho các bản vẽ/sự kiện chuột. Mỗi điều khiển cũng có một hàm gọi lại chung mà chuyển một giá trị và đó là địa chỉ riêng; điều này cho phép phụ huynh kiểm tra từng đứa trẻ của nó để xem ai đang gọi và trả lời thích hợp (ví dụ: nút lên/xuống). Lưu ý rằng điều khiển con được thêm vào thông qua mới/xóa trong c/dtors. Ngoài ra, các hàm draw() cho các điều khiển con được gọi trong cuộc gọi draw() của cha mẹ, cho phép mọi thứ được khép kín. Đây là mã có liên quan từ một slidebar giữa cấp có chứa 3 sub nút:
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);
}
}
Bây giờ chúng ta có kiểm soát khép kín mà làm thành một bối cảnh OpenGL, tất cả những gì cần thiết là giao diện từ việc kiểm soát cấp cao nhất , ví dụ như các sự kiện chuột từ 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...
}
}
Nhìn chung hệ thống này là một chút hacky, và tôi đã không được thêm vào một số tính năng như phản ứng bàn phím hoặc thay đổi kích thước, nhưng những nên dễ dàng để thêm, có thể đòi hỏi một 'loại sự kiện' trong callback (tức là chuột hoặc bàn phím). Bạn thậm chí có thể phân lớp glBase thành tiện ích trên cùng, và nó thậm chí sẽ cho phép nó nhận được parent-> childcallback. Mặc dù có rất nhiều công việc, nhưng hệ thống này chỉ yêu cầu vani C++/opengl và nó sử dụng ít tài nguyên bộ nhớ/cpu nhiều hơn so với các công cụ Qt nguyên bản, có thể bởi một hoặc hai thứ tự. Nếu bạn cần thêm thông tin, chỉ cần hỏi.
Tôi vừa nhận ra rằng tôi chỉ có thể đặt chế độ hiển thị thành trực giao thay vì phối cảnh. Điều này làm cho GUI shader không cần thiết. Nhưng vẫn còn, tôi nghĩ rằng có phải là một cách tích hợp để làm overpainting trong Qt3D (như có một phương pháp trong QGLWidget cũ tốt (mà tôi không muốn sử dụng vì nó trong module widget Qt5 và không thể render bằng QGLPainter)). – leemes
Điều này có vẻ là một khả năng (nghe có vẻ khá hay): http://comments.gmane.org/gmane.comp.lib.qt.3d/264 – leemes