【问题标题】:How to pan and zoom properly in 2D?如何在 2D 中正确平移和缩放?
【发布时间】:2013-10-17 13:35:49
【问题描述】:

我要做的就是通过 pyglet 使用 OpenGL 创建一个非常简单的 2D 平移和缩放功能。如您所见,第一次跳转后缩放工作正常:(再一次,拖动(平移)也正常工作,但它也会跳跃(而且跳跃很大)..

这是我的简化代码和显示其行为方式的视频 (pyglet_test.mp4):

import pyglet
from pyglet.gl import *

# Zooming constants
ZOOM_IN_FACTOR = 1.2
ZOOM_OUT_FACTOR = 1/ZOOM_IN_FACTOR



class App(pyglet.window.Window):

    def __init__(self, width, height, *args, **kwargs):
        # Create GL configuration
        conf = Config(  sample_buffers=1,
                        samples=4,
                        depth_size=16,
                        double_buffer=True )
        # Initialize parent
        super().__init__( width, height, config=conf, *args, **kwargs )

        # Create Group
        self.group = group = pyglet.graphics.Group()
        # Create Batch
        self.batch = batch = pyglet.graphics.Batch()

        # Create QUAD for testing and add it to batch
        batch.add(
            4, GL_QUADS, group,

            ('v2i', ( -50, -50,
                       50, -50,
                       50,  50,
                      -50,  50 )),

            ('c3B', ( 255,   0,   0,
                      255, 255,   0,
                        0, 255,   0,
                        0,   0, 255 ))
        )

        # Initialize OpenGL
        self.init_gl()

        # Initialize camera values
        self.camera_x = 0
        self.camera_y = 0
        self.camera_zoom = 1

    def init_gl(self):
        # Set clear color
        glClearColor(0/255, 0/255, 0/255, 0/255)

        # Set antialiasing
        glEnable( GL_LINE_SMOOTH )
        glEnable( GL_POLYGON_SMOOTH )
        glHint( GL_LINE_SMOOTH_HINT, GL_NICEST )

        # Set alpha blending
        glEnable( GL_BLEND )
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )

        # Set viewport
        glViewport( 0, 0, self.width, self.height )

        # Initialize Projection matrix
        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()

        # Set orthographic projection matrix
        glOrtho( 0, self.width, 0, self.height, 1, -1 )

        # Initialize Modelview matrix
        glMatrixMode( GL_MODELVIEW )
        glLoadIdentity()

        # Save the default modelview matrix
        glPushMatrix()

    def on_resize(self, width, height):
        # Initialize OpenGL for new dimensions
        self.width  = width
        self.height = height
        self.init_gl()

    def camera_matrix(transformations):

        # Create camera setter function
        def set_camera(self):
            # Take saved matrix off the stack and reset it
            glMatrixMode( GL_MODELVIEW )
            glPopMatrix()
            glLoadIdentity()
            # Call wrapped function
            transformations(self)
            # Save default matrix again with camera translation
            glPushMatrix()

        # Return wrapper function
        return set_camera

    @camera_matrix
    def move_camera(self):
        # Move to camera position
        glTranslatef( self.camera_x, self.camera_y, 0 )
        # Scale camera
        glScalef( self.camera_zoom, self.camera_zoom, 1 )

    @camera_matrix
    def zoom_camera(self):
        # Move to camera position
        glTranslatef( self.camera_x, self.camera_y, 0 )
        # Scale camera
        glScalef( self.camera_zoom, self.camera_zoom, 1 )
        # Move back from camera position
        glTranslatef( -self.camera_x, -self.camera_y, 0 )

    def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
        # Move camera
        self.camera_x += dx
        self.camera_y += dy
        self.move_camera()

    def on_mouse_scroll(self, x, y, dx, dy):
        # Get scale factor
        f = ZOOM_IN_FACTOR if dy < 0 else ZOOM_OUT_FACTOR if dy > 0 else 1
        # If zoom_level is in the proper range
        if .2 < self.camera_zoom*f < 5:
            # Zoom camera
            self.camera_x = x
            self.camera_y = y
            self.camera_zoom *= f
            self.zoom_camera()

    def on_draw(self):
        # Clear window with ClearColor
        glClear( GL_COLOR_BUFFER_BIT )

        # Pop default matrix onto current matrix
        glMatrixMode( GL_MODELVIEW )
        glPopMatrix()

        # Save default matrix again
        glPushMatrix()

        # Move to center of the screen
        glTranslatef( self.width/2, self.height/2, 0 )

        # Draw objects
        self.batch.draw()

    def run(self):
        pyglet.app.run()

# Create instance of app and run it
App(500, 500).run()

【问题讨论】:

  • 对我来说,这看起来不像是在平移,而是在围绕相机旋转对象 - 在 3D 中。
  • 旋转?我只使用glScaleglTranslate.. 反正你有什么建议?

标签: python opengl graphics python-3.x pyglet


【解决方案1】:

经过一天的痛苦,我终于找到了一个解决方案:在 2D 中进行基于鼠标坐标(枢轴点)的缩放和右键单击并拖动平移而没有跳跃的最简单方法是使用 @ 更改投影矩阵987654322@函数。

这是我原始代码的简化版本——如果你使用 Pyglet 的数据量很大,你应该考虑使用 Groups 和 Batches,但为了更容易理解,我使用了 glBegin()glColor()、@ 987654325@,glEnd()函数在这里绘制。

import pyglet
from pyglet.gl import *

# Zooming constants
ZOOM_IN_FACTOR = 1.2
ZOOM_OUT_FACTOR = 1/ZOOM_IN_FACTOR

class App(pyglet.window.Window):

    def __init__(self, width, height, *args, **kwargs):
        conf = Config(sample_buffers=1,
                      samples=4,
                      depth_size=16,
                      double_buffer=True)
        super().__init__(width, height, config=conf, *args, **kwargs)

        #Initialize camera values
        self.left   = 0
        self.right  = width
        self.bottom = 0
        self.top    = height
        self.zoom_level = 1
        self.zoomed_width  = width
        self.zoomed_height = height

    def init_gl(self, width, height):
        # Set clear color
        glClearColor(0/255, 0/255, 0/255, 0/255)

        # Set antialiasing
        glEnable( GL_LINE_SMOOTH )
        glEnable( GL_POLYGON_SMOOTH )
        glHint( GL_LINE_SMOOTH_HINT, GL_NICEST )

        # Set alpha blending
        glEnable( GL_BLEND )
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )

        # Set viewport
        glViewport( 0, 0, width, height )

    def on_resize(self, width, height):
        # Set window values
        self.width  = width
        self.height = height
        # Initialize OpenGL context
        self.init_gl(width, height)

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        # Move camera
        self.left   -= dx*self.zoom_level
        self.right  -= dx*self.zoom_level
        self.bottom -= dy*self.zoom_level
        self.top    -= dy*self.zoom_level

    def on_mouse_scroll(self, x, y, dx, dy):
        # Get scale factor
        f = ZOOM_IN_FACTOR if dy > 0 else ZOOM_OUT_FACTOR if dy < 0 else 1
        # If zoom_level is in the proper range
        if .2 < self.zoom_level*f < 5:

            self.zoom_level *= f

            mouse_x = x/self.width
            mouse_y = y/self.height

            mouse_x_in_world = self.left   + mouse_x*self.zoomed_width
            mouse_y_in_world = self.bottom + mouse_y*self.zoomed_height

            self.zoomed_width  *= f
            self.zoomed_height *= f

            self.left   = mouse_x_in_world - mouse_x*self.zoomed_width
            self.right  = mouse_x_in_world + (1 - mouse_x)*self.zoomed_width
            self.bottom = mouse_y_in_world - mouse_y*self.zoomed_height
            self.top    = mouse_y_in_world + (1 - mouse_y)*self.zoomed_height

    def on_draw(self):
        # Initialize Projection matrix
        glMatrixMode( GL_PROJECTION )
        glLoadIdentity()

        # Initialize Modelview matrix
        glMatrixMode( GL_MODELVIEW )
        glLoadIdentity()
        # Save the default modelview matrix
        glPushMatrix()

        # Clear window with ClearColor
        glClear( GL_COLOR_BUFFER_BIT )

        # Set orthographic projection matrix
        glOrtho( self.left, self.right, self.bottom, self.top, 1, -1 )

        # Draw quad
        glBegin( GL_QUADS )
        glColor3ub( 0xFF, 0, 0 )
        glVertex2i( 10, 10 )

        glColor3ub( 0xFF, 0xFF, 0 )
        glVertex2i( 110, 10 )

        glColor3ub( 0, 0xFF, 0 )
        glVertex2i( 110, 110 )

        glColor3ub( 0, 0, 0xFF )
        glVertex2i( 10, 110 )
        glEnd()

        # Remove default modelview matrix
        glPopMatrix()

    def run(self):
        pyglet.app.run()


App(500, 500).run()

【讨论】:

  • 这真的很有帮助!在此基础上,我发现有一个名为 gluOrtho2d 的函数专门用于设置二维正交投影的边界。它只需要 l,r,b,t 参数,因此您不必打扰其他 2
  • @dusktreader 这是一篇相当老的帖子——实际上那时我才开始学习 OpenGL。我建议您阅读OpenGL SuperBible 7th edition,因为我们不再使用“立即”(尤其是不是来自Python)。 Pyglet 支持——尽管不完整——支持更现代的 OpenGL 方法!
【解决方案2】:

glTranslatef 之类的函数并非绝对有效。相反,他们将“世界”移动了指定的数量。因此,如果您说glTranslatef(100,100),您最终会从您现在所在的位置上下移动 100 个单位,而不是在100, 100

他们在后台所做的是修改当前的视图矩阵。为了使这项工作,您需要编写如下代码:

glPushMatrix() # save the current matrix somewhere; gives you a new copy to modify

glTranslatef(100,100) # modify your copy; 
                      # you need to do this *every time* before you draw anything
... draw ...

glPopMatrix() # undo all and any change you made to the matrix

【讨论】:

  • 我认为这就是我的 camera_matrix 装饰器所做的。在我调用glTranslateglScale 之前,我将矩阵模式切换为GL_MODELVIEW,然后弹出矩阵,加载一个相同的矩阵,进行转换并将修改后的版本推回.. 那么我到底做错了什么? (如果我将顺序更改为您的版本,您先推送然后弹出 - 什么都没有发生:平移和缩放都不起作用!)
  • 第一个“pop matrix”可能没有任何作用,因为没有要pop的矩阵。您首先需要在堆栈上推送一个矩阵,然后才能弹出任何内容。这意味着在您的代码中,您必须始终先推送,然后弹出,从不反过来。
  • 第一次推送发生在init_gl 方法中——在我初始化矩阵之后,我将它们推送到堆栈中,这就是为什么其他方法调用以pop 开头并以push 结束的原因。所以你说这是错的?
  • 我从未见过在on_draw() 之外进行推送/弹出的代码。我不想打赌init_gl() 的矩阵在调用on_draw() 时仍在堆栈中。从稳定性的角度来看,我会清除堆栈,然后调用on_draw() 以确保简单的错误不会导致应用程序崩溃。
  • 好吧,从技术上讲,它是否在on_draw 之外并不重要,因为on_draw 只是在任何其他事件被触发后被戳的方法——而且由于 OpenGL 是一个状态机,Window 类中的方法只是 managers 提供调用 glFunctions 将以正确的顺序完成。无论如何,我改变了你建议的方式,它仍然在跳跃并且不能正常工作..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 1970-01-01
  • 2015-04-22
  • 1970-01-01
  • 2016-05-18
相关资源
最近更新 更多