通常,我们需要在一个复杂的三维场景中绘制简单的注解。如果场景非常复杂,可能需要几秒钟来绘制它。为了避免重复绘制场景,每当注解改变吋,我们可以使用OpenGL内置支持的叠加。
pbuffers和framebuffer对象提供了更方便和更灵活的制作叠加的方法。基本的思路是在离屏表面绘制三维场景,并将其绑定纹理。纹理通过绘制矩形映射到屏幕,注解绘制在上面。当注解改变时,只需重绘矩形和注解。
为了阐述这一技术,我们将介绍如下图所示的Teapots应用程序。该应用程序由单个OpenGL窗口组成,该窗口显示了一组茶壶,用户可以在上面单击并拖动鼠标来绘制橡皮筋选择框。茶壶不以任何方式移动或改变,除非窗口大小发生变化。实现上依靠framebuffer对象来保存茶壶的场景。也以使用pbuffer来实现相同的效果,用QGLPixelBuffer代替QGLFramebufferObject。
Teapots类的定义class Teapots : public QGLWidget
{
Q_OBJECT
public:
Teapots(QWidget *parent = 0);
~Teapots();
protected:
void initializeGL();
void resizeGL(int width, int height);
void paintGL();
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
private:
void createGLTeapotObject();
void drawTeapot(GLfloat x, GLfloat y, GLfloat ambientR,
GLfloat ambientG, GLfloat ambientB,
GLfloat diffuseR, GLfloat diffuseG,
GLfloat diffuseB, GLfloat specularR,
GLfloat specularG, GLfloat specularB,
GLfloat shininess);
void drawTeapots();
QGLFramebufferObject *fbObject;
GLuint glTeapotObject;
QPoint rubberBandCorner1;
QPoint rubberBandCorner2;
bool rubberBandIsShown;
};
Teapots类派生自QGLWidget,实现了OpenGL的高级处理函数initializeGL()、resizeGL()和paintGL()。同时,它还实现了mousePressEvent( )、mouseMoveEvent()和mouseReleaseEvent(),以便可以让用户绘制一个橡皮筋选择框。
私有函数负责创建茶壶对象以及茶壶的绘制。这些私有变量可以保存framebuffer对象、茶壶对象、橡皮筋选择框的各个角,以及橡皮筋选择框的可见属性。
Teapots类的实现首先是构造函数:
Teapots::Teapots(QWidget *parent)
: QGLWidget(parent)
{
rubberBandIsShown = false;
makeCurrent();
fbObject = new QGLFramebufferObject(1024, 1024,
QGLFramebufferObject::Depth);
createGLTeapotObject();
}
Teapots构造函数初始化私有变量,创建framebuffer对象,并创建茶壶对象。我们将跳过createGLTeapotObject()函数,因为它很长而且并不包含与Qt相关的代码。
Teapots::~Teapots()
{
makeCurrent();
delete fbObject;
glDeleteLists(glTeapotObject, 1);
}
在析构函数中,释放与framebuffer对象和茶壶相关的资源。
void Teapots::initializeGL()
{
static const GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
static const GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
static const GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };
static const GLfloat lmodelAmbient[] = { 0.2, 0.2, 0.2, 1.0 };
static const GLfloat localView[] = { 0.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodelAmbient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, localView);
glFrontFace(GL_CW);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
}
initializeGL()函数用来设置光照模式,打开各种OpenGL特性。
void Teapots::resizeGL(int width, int height)
{
fbObject->bind();
glDisable(GL_TEXTURE_2D);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (width <= height) {
glOrtho(0.0, 20.0, 0.0, 20.0 * GLfloat(height) / GLfloat(width),-10.0, 10.0);
} else {
glOrtho(0.0, 20.0 * GLfloat(width) / GLfloat(height), 0.0, 20.0,-10.0, 10.0);
}
glMatrixMode(GL_MODELVIEW);
drawTeapots();
fbObject->release();
}
resizeGL()函数在Teapot窗口部件改变大小时重绘茶壶场景。为了在framebuffer对象上绘制荼壶,在函数的开头调用QGLFramebufferObject::bind()。然后,设置一些OpenGL特性,以及投影和模式视图矩阵。结尾的drawTeapots()调用在framebuffer对象上绘制茶壶。最后,release()调用释放framebuffer对象,确保随后的OpenGL绘制操作不会发生在framebuffer对象上。
void Teapots::paintGL()
{
glDisable(GL_LIGHTING);
glViewport(0, 0, width(), height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, fbObject->texture());
glColor3f(1.0, 1.0, 1.0);
GLfloat s = width() / GLfloat(fbObject->size().width());
GLfloat t = height() / GLfloat(fbObject->size().height());
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(-1.0, -1.0);
glTexCoord2f(s, 0.0);
glVertex2f(1.0, -1.0);
glTexCoord2f(s, t);
glVertex2f(1.0, 1.0);
glTexCoord2f(0.0, t);
glVertex2f(-1.0, 1.0);
glEnd();
if (rubberBandIsShown) {
glMatrixMode(GL_PROJECTION);
glOrtho(0, width(), height(), 0, 0, 100);
glMatrixMode(GL_MODELVIEW);
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(4.0);
glColor4f(1.0, 1.0, 1.0, 0.2);
glRecti(rubberBandCorner1.x(), rubberBandCorner1.y(),
rubberBandCorner2.x(), rubberBandCorner2.y());
glColor4f(1.0, 1.0, 0.0, 0.5);
glLineStipple(3, 0xAAAA);
glEnable(GL_LINE_STIPPLE);
glBegin(GL_LINE_LOOP);
glVertex2i(rubberBandCorner1.x(), rubberBandCorner1.y());
glVertex2i(rubberBandCorner2.x(), rubberBandCorner1.y());
glVertex2i(rubberBandCorner2.x(), rubberBandCorner2.y());
glVertex2i(rubberBandCorner1.x(), rubberBandCorner2.y());
glEnd();
glLineWidth(1.0);
glDisable(GL_LINE_STIPPLE);
glDisable(GL_BLEND);
}
}
在paintGL()函数中,首先重新设置投影和模式视图矩阵。然后,把framebuffer对象绑定到纹理上,绘制一个带该纹理的矩形覆盖整个窗口部件。如果橡皮筋选择框是可见的,我们可以把它绘制在矩形上。代码是标准的openGL。
void Teapots::mousePressEvent(QMouseEvent *event)
{
rubberBandCorner1 = event->pos();
rubberBandCorner2 = event->pos();
rubberBandIsShown = true;
}
void Teapots::mouseMoveEvent(QMouseEvent *event)
{
if (rubberBandIsShown) {
rubberBandCorner2 = event->pos();
updateGL();
}
}
void Teapots::mouseReleaseEvent(QMouseEvent * /* event */)
{
if (rubberBandIsShown) {
rubberBandIsShown = false;
updateGL();
}
}
鼠标事件处理器负责更新用来绘制橡皮筋线的rubberBandCorner1,rubberBandCorner2和rubberBandIsShown变量,并且调用updateGL()重绘场景。重绘场景的速度很快,因为paintGL()仅绘制一个纹理矩阵以及其上的橡皮筋线。只有当用户改变窗口大小时,场景才在resizeGL()中重新绘制。
主程序代码以下是该应用程序的main()函数:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (!QGLFormat::hasOpenGL()) {
std::cerr << "This system has no OpenGL support" << std::endl;
return 1;
}
if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) {
std::cerr << "This system has no framebuffer object support"
<< std::endl;
return 1;
}
Teapots teapots;
teapots.setWindowTitle(QObject::tr("Teapots"));
teapots.resize(400, 400);
teapots.show();
return app.exec();
}
如果系统不支持openGL或者framebuffer对象,将会发送一条错误信息,并且返回该错误代码,结束运行。
Teapots例子告诉我们如何在离屏表面绑定纹理,以及如何使用命令在该表面上绘制图形。但是我们还可以在其他方面进行有益的尝试。例如,可以使QPainter代替OpenGL命令在QGLFramebufferObject或QGLPixelBuffer上绘制。这为在OpenGL场景中绘制变形字提供了一种方法。另一种常用的做法是使用framebuffer对象绘制场景,然后对其结果调用toImage()生成QImage。Qt自带的很多例子都展示了在实际应用中的多种做法,它们都是针对framebuffer对象和pbuffers的。
——————————————————
对于本文实例完整代码有需要的朋友,可关注并在评论区留言!
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved