使用glOrtho将视角缩放到鼠标位置

3

目前我正在使用glOrtho来缩放和平移我正在渲染的2D图形。

我已经设置了视口的标准宽度和高度。然后我设置了glOrtho,使我的截锥体使屏幕坐标与世界坐标匹配。

glViewport(0, 0, window_width,window_height);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, window_width,window_height,0 , 100, -100);

在我的鼠标回调函数中进行缩放操作时,我会将视锥边缘乘以缩放因子...

glOrtho( 0 * zoomOut,
window_width * zoomOut,
window_height * zoomOut,
0 * zoomOut, 
100, -100);

我的问题是...如何使用鼠标位置作为中心进行缩放?

我尝试了以下代码...(其中mouseStoreX和mouseStoreY是在第一次点击时存储的位置)

glOrtho( (0 -mouseStoreX )* zoomOut + mouseStoreX,
(window_width - mouseStoreX) * zoomOut + mouseStoreX,
(window_height - mouseStoreY) * zoomOut + mouseStoreY,
(0 - mouseStoreY) * zoomOut + mouseStoreY, 
100, -100);

看起来它正在工作,但是当我进行新的点击时,裁剪锥体会跳来跳去。我认为在存储鼠标位置时,某些地方没有考虑到zoomOut因素。

编辑:这是我的最新代码,我仍然在努力解决问题...

void ZoomOrtho(){ //ON MOUSE CLICK.....

if (zooming == false){
keyStore.LMx = keyStore.Mx;   //store mouse pos for next comparison
keyStore.LMy = keyStore.My;
//get mouse pos
mouseStoreX = keyStore.Mx;//mouse pos at this moment
mouseStoreY = keyStore.My;

//get current projection matrices
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
//flip Y for opengl reasons
winY = (float)viewport[3] - winY;
//get world mouse coordinate
gluUnProject( mouseStoreX, mouseStoreY , 0.0, modelview, projection, viewport, &posX_,&posY_, &posZ_);

// calc difference between mouse world pos and centre of 'camera'
dx = posX_ - FS.centerX;
dy = posY_ - FS.centerY;

}
//ON DRAG......
zooming = true;

//do mouse movement detection and increment zoomOut
//#################################################
int xDiff = keyStore.Mx - keyStore.LMx;  //mouse drag difference in screen space just  for incrementing zoom
int yDiff = keyStore.My - keyStore.LMy;  //

if (xDiff > 0 && (zoomFactor >= 0.5 ) )   {
zoomFactor -= zoomInc;
if  (zoomFactor < 0.5 ) {zoomFactor = 0.5;}
}
else if (xDiff < 0 && (zoomFactor <= 2.0 ))  {
zoomFactor += zoomInc;
if (zoomFactor > 2.0){zoomFactor = 2.0;}
}
//#################################################


//fill structure with clipping plane values. zooms ortho projection and keeps mouse pos anchored.
FS.left =  ((FS.centerX - dx - (window_width/2.0))*zoomFactor) +dx;
FS.right = ((FS.centerX -dx + (window_width/2.0))*zoomFactor)+dx ;
FS.bottom = ((FS.centerY -dy + (window_width/2.0))*zoomFactor)+dy;
FS.top =    ((FS.centerY -dy  - (window_width/2.0))*zoomFactor) +dy;

// store last mouse pos for next comparison.
keyStore.LMx = keyStore.Mx;
keyStore.LMy = keyStore.My;

}

void zoomRelease(){

cout << " releasing" << std::endl;
//set zoom to false so we know we are not draggin mouse anymore.
zooming = false;
keyStore.LMx = 0;
keyStore.LMy = 0;

// recenter by taking midpoint between new left and right clipping planes so dx has a reference point
FS.centreX = (FS.right-FS.left)/2.0;

}

void DrawGui(){

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(FS.left, FS.right,FS.bottom, FS.top, 1, -1);

glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
//do drawing

} 
4个回答

1
尝试一下这个:
// g++ main.cpp -o main -lglut -lGL && ./main
#include <GL/glut.h>

double centerX = 0, centerY = 0;
double width = 0, height = 0;
void mouse( int button, int state, int mx, int my )
{
    // flip mouse y axis so up is +y
    my = glutGet( GLUT_WINDOW_HEIGHT ) - my;

    // convert mouse coords to (-1/2,-1/2)-(1/2, 1/2) box
    double x = ( mx / (double)glutGet( GLUT_WINDOW_WIDTH ) ) - 0.5;
    double y = ( my / (double)glutGet( GLUT_WINDOW_HEIGHT ) ) - 0.5;

    if( GLUT_UP == state )
    {
        double preX = ( x * width );
        double preY = ( y * height );

        double zoomFactor = 1.5;
        if( button == GLUT_LEFT_BUTTON )
        {
            // zoom in
            width /= zoomFactor;
            height /= zoomFactor;
        }
        if( button == GLUT_RIGHT_BUTTON )
        {
            // zoom out
            width *= zoomFactor;
            height *= zoomFactor;
        }

        double postX = ( x * width );
        double postY = ( y * height );

        // recenter
        centerX += ( preX - postX );
        centerY += ( preY - postY );
    }

    glutPostRedisplay();
}

void display()
{
    glClear( GL_COLOR_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho
        (
        centerX - ( width / 2.0 ),
        centerX + ( width / 2.0 ),
        centerY - ( height / 2.0 ),
        centerY + ( height / 2.0 ),
        -1,
        1     
        );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    glColor3ub( 255, 0, 0 );
    glBegin( GL_TRIANGLES );
    glVertex2i( 0, 0 );
    glVertex2i( 150, 0 );
    glVertex2i( 0, 150 );
    glVertex2i( 0, 0 );
    glVertex2i( -150, 0 );
    glVertex2i( 0, -150 );
    glEnd();

    glutSwapBuffers();
}

int main( int argc, char **argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
    glutInitWindowSize( 600, 600 );
    glutCreateWindow( "GLUT" );
    glutDisplayFunc( display );
    glutMouseFunc( mouse );

    width = glutGet( GLUT_WINDOW_WIDTH );
    height = glutGet( GLUT_WINDOW_HEIGHT );    

    glutMainLoop();
    return 0;
}

谢谢您的回复。您提供的信息很有帮助,但并不完全符合我的需求。我正在尝试在拖动鼠标时进行缩放。我现在正在尝试将屏幕坐标转换为世界坐标,然后在释放鼠标时重新计算中心值。目的是拖动鼠标时,鼠标下方的点成为旋转中心。 - Daniel Dokipen Elliott
我现在已经解决了这个问题。之前我将鼠标坐标反投影到世界空间中,计算偏移量并在鼠标释放后重新居中,但仍然出现了奇怪的跳跃,我无法追踪到原因。我认为这是由于没有连接变换所致。这个解决方案更加直接。不过,我添加了自己的调整,即如果baseY为负,则反转缩放因子(1/zoomfactor)。 - Daniel Dokipen Elliott

0
如果您想采用不同的方法,仅使用glTranslate和gluPerspective也可以实现相同的效果。使用PyOpenGL的滚轮鼠标事件可能如下所示:
def MouseWheelScroll(self, event):
    """Called when the mouse's scroll wheel is scrolled up or down. This modifies the zoomFactor
    which renders the graphics closer or further away on the screen. It also translates the graphics
    slightly based on the position of the mouse. This creates an effect of zooming to the location
    of the mouse on the screen.
    """
    scrolledUp = event.GetWheelRotation() # Returns positive for up, negative for down
    self.x, self.y = event.GetPosition()

    viewport = glGetIntegerv(GL_VIEWPORT)
    width = viewport[2]
    height = viewport[3]
    centerX = width / 2.0
    centerY = height / 2.0

    # Make sure cursor is on the screen
    if ((self.x > 0 and self.x < width) and (self.y > 0 and self.y < height)):
        if (scrolledUp > 0):
            self.zoomFactor -= 2.0
            self.translation[0] -= (self.x - centerX)
            self.translation[1] += (self.y - centerY)
        else:
            self.zoomFactor += 2.0
            self.translation[0] += (self.x - centerX)
            self.translation[1] += (self.y - centerY)

        if (self.zoomFactor > 150.0):
            self.zoomFactor = 150.0
        elif (self.zoomFactor < 0.1):
            self.zoomFactor = 0.1

    self.Refresh(False)

然后您只需要翻译图形,设置透视,然后渲染场景即可。

# Translate graphics
glTranslatef(0.0, 0.0, (self.translation[1]/100.0) * (math.tan(self.cameraPosition[0]/self.cameraPosition[1])))
glTranslatef(0.0, (self.translation[0]/100.0) * (math.tan(self.cameraPosition[0]/self.cameraPosition[1])), 0.0)

# Set Perspective
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(self.zoomFactor, float(width)/float(height), self.nearPlane, self.farPlane)

# Render Scene
glMatrixMode(GL_MODELVIEW)
...Draw stuff here...

0
假设你在第二次点击时将其值存储到mouseStoreXY中。如果是这样,这就导致了跳跃。你是用旧的mouseStoreXY偏移绘画的,然后突然偏移到新的mouseStoreXY。
解决方案是持久地存储投影矩阵输入,然后在每一帧上逐步修改它们。

是的,我现在意识到了。我正在试图跟踪偏移量,但目前还无法精确执行。感谢您的帮助。 - Daniel Dokipen Elliott
我有同样的问题 - "incrementally" 是什么意思?这是什么意思,要逐步进行吗? - paulm

0

试一试这个:

// g++ main.cpp -o main -lglut -lGL && ./main
#include <GL/glut.h>
#include <cmath>

void getMouseCoords( int mx, int my, double& x, double& y )
{
    // flip mouse y axis so up is +y
    my = glutGet( GLUT_WINDOW_HEIGHT ) - my;

    // convert mouse coords to (-1/2,-1/2)-(1/2, 1/2) box
    x = ( mx / (double)glutGet( GLUT_WINDOW_WIDTH ) ) - 0.5;
    y = ( my / (double)glutGet( GLUT_WINDOW_HEIGHT ) ) - 0.5;
}

int btn;
double baseX, baseY;
double baseWidth, baseHeight;

double centerX = 0, centerY = 0;
double width = 0, height = 0;
void mouse( int button, int state, int mx, int my )
{
    baseWidth = width;
    baseHeight = height;
    btn = button;
    getMouseCoords( mx, my, baseX, baseY );
}

void motion( int mx, int my )
{
    if( btn != GLUT_LEFT_BUTTON )
    {
        return;
    }

    double x, y;
    getMouseCoords( mx, my, x, y );

    double preX = ( baseX * width );
    double preY = ( baseY * height );

    double zoomFactor = exp( baseY - y );
    width = baseWidth * zoomFactor;
    height = baseHeight * zoomFactor;

    double postX = ( baseX * width );
    double postY = ( baseY * height );

    // recenter
    centerX += ( preX - postX );
    centerY += ( preY - postY );

    glutPostRedisplay();
}

void display()
{
    glClear( GL_COLOR_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho
        (
        centerX - ( width / 2.0 ),
        centerX + ( width / 2.0 ),
        centerY - ( height / 2.0 ),
        centerY + ( height / 2.0 ),
        -1,
        1     
        );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    glColor3ub( 255, 0, 0 );
    glBegin( GL_TRIANGLES );
    glVertex2i( 0, 0 );
    glVertex2i( 150, 0 );
    glVertex2i( 0, 150 );
    glVertex2i( 0, 0 );
    glVertex2i( -150, 0 );
    glVertex2i( 0, -150 );
    glEnd();

    glutSwapBuffers();
}

int main( int argc, char **argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
    glutInitWindowSize( 600, 600 );
    glutCreateWindow( "GLUT" );
    glutDisplayFunc( display );
    glutMouseFunc( mouse );
    glutMotionFunc( motion );

    width = glutGet( GLUT_WINDOW_WIDTH );
    height = glutGet( GLUT_WINDOW_HEIGHT );    

    glutMainLoop();
    return 0;
}

是的,这正是我想要的行为。非常感谢您。我应该能够在我的代码中让它工作。我几乎已经想出来了,但是您让它变得非常清晰。我会给您买一杯啤酒,先生! - Daniel Dokipen Elliott
实际上,我无法通过平移来使其正常工作。我已经编辑了上面的代码以展示我之前所做的事情。 - Daniel Dokipen Elliott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接