Macbook视网膜显示器上的PyOpenGL

5 投票
3 回答
2033 浏览
提问于 2025-04-17 15:19

我有一些代码,用PyOpenGL在一个glut窗口中显示图形。在一台视网膜屏幕的Macbook Pro上,这个窗口的显示效果很低,OpenGL的一个像素被四个物理像素表示。如果能以原生分辨率显示就好了。

我的问题
有没有办法在视网膜显示器上,用Python获取原生分辨率的OpenGL上下文,不管是用glut还是其他方式?

问题示例
下面是一个简单的PyOpenGL程序的示例。这个程序没有什么特别之处——任何正常工作的PyOpenGL代码都会出现这个问题。这里有一张截图的放大细节。注意,构成白色三角形的像素是OS X小部件像素的四倍大。这是那些没有专门为视网膜设备设计的程序的默认情况。我想知道如何在PyOpenGL程序中关闭这个设置。

在这里输入图片描述

这里是代码:

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *

import os

def initFunc():
    glDisable(GL_DEPTH_TEST)
    glClearColor(0.0, 0.0, 0.0, 0.0)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0.0, 400.0, 0.0, 400.0)

def displayFunc():
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(1.0, 1.0, 1.0)
    glBegin(GL_TRIANGLES)
    glVertex2f(10.0, 10.0)
    glVertex2f(10.0, 100.0)
    glVertex2f(100.0, 100.0)
    glEnd()
    glFlush()

if __name__ == '__main__':
    glutInit()
    glutInitWindowSize(400,400)
    glutCreateWindow("GL test")
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutDisplayFunc(displayFunc)
    initFunc()
    os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')
        # prevent GL window from appearing behind other applications
    glutMainLoop()

3 个回答

1

我想你需要用一些PyObjC的绑定来获取Glut创建的NSWindow,然后调整一下缩放比例。这里有一些关于Objective C部分的信息:http://supermegaultragroovy.com/2012/10/24/coding-for-high-resolution-on-os-x-read-this/

3

你可以从这里获取一个修补过的GLUT版本,它可以选择性地支持高分辨率显示(HiDPI):http://iihm.imag.fr/blanch/software/glut-macosx/

如果你使用这个GLUT框架,你的应用程序可以选择以真实的屏幕像素分辨率获取OpenGL上下文。你只需要在代码中(无论是C语言、Python/PyOpenGL还是其他与GLUT框架链接的语言)修改显示初始化部分,从:

glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE)

改成:

glutInitDisplayString("rgba double hidpi")

这个修补版本能够很好地处理不同屏幕的不同缩放比例,并且为一些需要像素大小或维度的函数提供了一致的表现,比如 glutInitWindowSize, glutReshapeWindow, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT) 以及传递给事件回调的光标位置。

这个版本的GLUT也完全向后兼容,适合那些不想使用高分辨率支持的其他应用程序。如果你想自己构建一个版本,修补程序和构建框架的说明也提供了。

此外,这个修补版本还增加了鼠标滚轮的支持,如果你需要的话。

5

要实现你想要的分辨率,首先需要使用一个叫做 NSView 的东西,并且要告诉这个视图使用最佳分辨率(这也意味着你不再使用 GLUT 了)。因为这超出了 OpenGL 的范围,而你又想用 Python,所以你需要用一些可以和 py2app 一起使用的包来访问这些对象(不过不需要完整的 py2app build)。

为了访问 NSView,更具体地说是 NSOpenGLView,你需要用到 pyobjc。下面的代码是为了清晰起见导入了 Cocoa(这是 pyobjc 提供的),其实同样的对象也可以通过 AppKit 访问。代码创建了一个 NSWindow,然后创建了一个视图,并定义了 drawRect,这个方法会在需要的时候被调用。解决分辨率问题的关键代码是:view.setWantsBestResolutionOpenGLSurface_(True)

import Cocoa
from OpenGL.GL import *
from OpenGL.GLU import *

class GLView(Cocoa.NSOpenGLView):
    def initWithFrame_(self, frame):
        format = Cocoa.NSOpenGLPixelFormat.alloc().initWithAttributes_((0, ))
        view = super(GLView, self).initWithFrame_pixelFormat_(frame, format)
        view.setWantsBestResolutionOpenGLSurface_(True)

        view.openGLContext().makeCurrentContext()
        glDisable(GL_DEPTH_TEST)
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluOrtho2D(0.0, 1.0, 0.0, 1.0)
        return view

    def drawRect_(self, bounds):
        glClear(GL_COLOR_BUFFER_BIT)
        glColor3f(1.0, 1.0, 1.0)
        glBegin(GL_TRIANGLES)
        glVertex2f(0.1, 0.1)
        glVertex2f(0.1, 0.9)
        glVertex2f(0.9, 0.9)
        glEnd()
        glFlush()


class AppDelegate(Cocoa.NSObject):
    def windowWillClose_(self, notification):
        app.terminate_(self)

app = Cocoa.NSApplication.sharedApplication()

rect = Cocoa.NSMakeRect(0, 0, 300, 300)
winflags = Cocoa.NSTitledWindowMask | Cocoa.NSClosableWindowMask
win = Cocoa.NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
        rect, winflags, Cocoa.NSBackingStoreBuffered, False)
delegate = AppDelegate.alloc().init()
win.setDelegate_(delegate)
view = GLView.alloc().initWithFrame_(rect)
win.setContentView_(view)
win.makeKeyAndOrderFront_(None)

app.run()

如果你尝试运行这段代码,它会正常运行,但你不会发现分辨率方面有什么变化。要解决这个问题,你需要构建一个简单的 setup.py,假设上面的代码保存在一个叫做 test1.py 的文件中:

from distutils.core import setup
import py2app
setup(app=["test1.py"])

然后运行 mypythonexecutable setup.py py2app -A(当然,mypythonexecutable 要替换成像 pythonpython2.7 这样的有意义的东西)。现在你可以执行 open dist/test1.app,这样问题就解决了(dist/test1.app 会在之前的命令执行后创建)。

撰写回答