如何在 PyQt5 QOpenGLWidget 中对球体进行纹理处理

Posted

技术标签:

【中文标题】如何在 PyQt5 QOpenGLWidget 中对球体进行纹理处理【英文标题】:How to texture a sphere in PyQt5 QOpenGLWidget 【发布时间】:2020-11-27 05:48:20 【问题描述】:

我正在尝试在我使用 PyQt5 制作的程序中显示世界地图。对于到目前为止的渲染内容,这是我拥有的(主要是借用的)代码:

import sys
import math
import numpy as np
from PIL import Image, ImageQt
from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt
from PyQt5.QtGui import QColor, QOpenGLVersionProfile
from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QOpenGLWidget, QWidget)


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        self.glWidget = GLWidget()

        mainLayout = QHBoxLayout()
        mainLayout.addWidget(self.glWidget)
        self.setLayout(mainLayout)

        self.img = Image.open("BWTopo.png")
        self.mapWidth, self.mapHeight = self.img.size
        pgImData = np.asarray(self.img)
        self.inputMapFile = np.flipud(pgImData)

        self.setWindowTitle("SPHERE")


class GLWidget(QOpenGLWidget):

    def __init__(self, parent=None):
        super(GLWidget, self).__init__(parent)

        self.object = 0
        self.xRot = 0
        self.yRot = 0
        self.zRot = 0

        self.lastPos = QPoint()

        self.main = QColor.fromCmykF(0.40, 0.0, 1.0, 0.0)
        self.clear = QColor.fromCmykF(0.39, 0.39, 0.0, 0.0)

    def sizeHint(self):
        return QSize(400, 400)

    def setXRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.xRot:
            self.xRot = angle
            self.update()

    def setYRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.yRot:
            self.yRot = angle
            self.update()

    def setZRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.zRot:
            self.zRot = angle
            self.update()

    def initializeGL(self):
        version_profile = QOpenGLVersionProfile()
        version_profile.setVersion(2, 0)
        self.gl = self.context().versionFunctions(version_profile)
        self.gl.initializeOpenGLFunctions()

        self.setClearColor(self.clear.darker())
        self.object = self.makeObject()
        self.gl.glShadeModel(self.gl.GL_FLAT)
        self.gl.glEnable(self.gl.GL_DEPTH_TEST)
        self.gl.glEnable(self.gl.GL_CULL_FACE)
        self.gl.glEnable(self.gl.GL_LIGHTING)
        self.gl.glLightModelfv(self.gl.GL_LIGHT_MODEL_AMBIENT, [0.9, 0.9, 0.9, 1.0])
        self.gl.glEnable(self.gl.GL_COLOR_MATERIAL)
        self.gl.glColorMaterial(self.gl.GL_FRONT, self.gl.GL_AMBIENT_AND_DIFFUSE)

    def paintGL(self):
        self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
        self.gl.glLoadIdentity()
        self.gl.glTranslated(0.0, 0.0, -10.0)
        self.gl.glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0)
        self.gl.glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0)
        self.gl.glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0)
        self.gl.glCallList(self.object)

    def resizeGL(self, width, height):
        side = min(width, height)
        if side < 0:
            return

        self.gl.glViewport((width - side) // 2, (height - side) // 2, side,
                side)

        self.gl.glMatrixMode(self.gl.GL_PROJECTION)
        self.gl.glLoadIdentity()
        self.gl.glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0)
        self.gl.glMatrixMode(self.gl.GL_MODELVIEW)

    def mousePressEvent(self, event):
        self.lastPos = event.pos()

    def mouseMoveEvent(self, event):
        dx = event.x() - self.lastPos.x()
        dy = event.y() - self.lastPos.y()

        if event.buttons() & Qt.LeftButton:
            self.setXRotation(self.xRot + 8 * dy)
            self.setYRotation(self.yRot + 8 * dx)
        elif event.buttons() & Qt.RightButton:
            self.setXRotation(self.xRot + 8 * dy)
            self.setZRotation(self.zRot + 8 * dx)

        self.lastPos = event.pos()

    def makeObject(self):
        genList = self.gl.glGenLists(1)
        self.gl.glNewList(genList, self.gl.GL_COMPILE)

        self.gl.glBegin(self.gl.GL_TRIANGLES)

        
        UResolution = 18
        VResolution = 36
        r = 0.3
        startU = 0
        startV = 0
        endU = math.pi * 2
        endV = math.pi
        stepU = (endU-startU)/UResolution # step size between U-points on the grid
        stepV = (endV-startV)/VResolution # step size between V-points on the grid
        for i in range(UResolution):  # U-points
            for j in range(VResolution):  # V-points
                u = i*stepU+startU
                v = j*stepV+startV
                un = endU if (i+1==UResolution) else (i+1)*stepU+startU
                vn = endV if (j+1==VResolution) else (j+1)*stepV+startV
                
                p0 = [ math.cos(u)*math.sin(v)*r, math.cos(v)*r, math.sin(u)*math.sin(v)*r ]
                p1 = [ math.cos(u)*math.sin(vn)*r, math.cos(vn)*r, math.sin(u)*math.sin(vn)*r ] 
                p2 = [ math.cos(un)*math.sin(v)*r, math.cos(v)*r, math.sin(un)*math.sin(v)*r ]
                p3 = [ math.cos(un)*math.sin(vn)*r, math.cos(vn)*r, math.sin(un)*math.sin(vn)*r ]

                # Output the first triangle of this grid square
                self.gl.glVertex3f(p0[0],p0[1],p0[2])
                self.gl.glVertex3f(p2[0],p2[1],p2[2])
                self.gl.glVertex3f(p1[0],p1[1],p1[2])

                # Output the other triangle of this grid square
                self.gl.glVertex3f(p3[0],p3[1],p3[2])
                self.gl.glVertex3f(p1[0],p1[1],p1[2])
                self.gl.glVertex3f(p2[0],p2[1],p2[2])
                

        self.gl.glEnd()
        self.gl.glEndList()

        return genList

    def normalizeAngle(self, angle):
        while angle < 0:
            angle += 360 * 16
        while angle > 360 * 16:
            angle -= 360 * 16
        return angle

    def setClearColor(self, c):
        self.gl.glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF())

    def setColor(self, c):
        self.gl.glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF())


if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

它在窗口中创建一个无阴影的球体。我想将从“BWTopo”加载的图像作为等角投影图应用到球体上。我该怎么做呢?

【问题讨论】:

【参考方案1】:

你必须创建一个纹理对象:

self.gl.glActiveTexture(self.gl.GL_TEXTURE0)
self.text_obj = self.gl.glGenTextures(1)
self.gl.glBindTexture(self.gl.GL_TEXTURE_2D, self.text_obj)
self.gl.glPixelStorei(self.gl.GL_UNPACK_ALIGNMENT, 1)
self.gl.glTexImage2D(self.gl.GL_TEXTURE_2D, 0, self.gl.GL_RGB, self.mapWidth, self.mapHeight, 0, self.gl.GL_RGB, self.gl.GL_UNSIGNED_BYTE, self.inputMapFile.tobytes())
self.gl.glPixelStorei(self.gl.GL_UNPACK_ALIGNMENT, 4)
self.gl.glTexParameterf(self.gl.GL_TEXTURE_2D, self.gl.GL_TEXTURE_MAG_FILTER, self.gl.GL_LINEAR)
self.gl.glTexParameterf(self.gl.GL_TEXTURE_2D, self.gl.GL_TEXTURE_MIN_FILTER, self.gl.GL_LINEAR)

您需要在 [0, 1] 范围内创建纹理坐标:

t0 = [i/UResolution, 1-j/VResolution]
t1 = [i/UResolution, 1-(j+1)/VResolution]
t2 = [(i+1)/UResolution, 1-j/VResolution]
t3 = [(i+1)/UResolution, 1-(j+1)/VResolution]

# Output the first triangle of this grid square
self.gl.glTexCoord2f(*t0)
self.gl.glVertex3f(*p0)
self.gl.glTexCoord2f(*t2)
self.gl.glVertex3f(*p2)
self.gl.glTexCoord2f(*t1)
self.gl.glVertex3f(*p1)

# Output the other triangle of this grid square
self.gl.glTexCoord2f(*t3)
self.gl.glVertex3f(*p3)
self.gl.glTexCoord2f(*t1)
self.gl.glVertex3f(*p1)
self.gl.glTexCoord2f(*t2)
self.gl.glVertex3f(*p2)

二维纹理必须开启,见glEnable,并且在绘制网格之前需要绑定纹理对象:

self.gl.glEnable(self.gl.GL_TEXTURE_2D) 
self.gl.glBindTexture(self.gl.GL_TEXTURE_2D, self.text_obj)
self.gl.glColor3f(1, 1, 1)
self.gl.glCallList(self.object)

完整示例:

import sys
import math
import numpy as np
from PIL import Image, ImageQt
from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt
from PyQt5.QtGui import QColor, QOpenGLVersionProfile
from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QOpenGLWidget, QWidget)

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.glWidget = GLWidget()
        mainLayout = QHBoxLayout()
        mainLayout.addWidget(self.glWidget)
        self.setLayout(mainLayout)
        self.setWindowTitle("SPHERE")

class GLWidget(QOpenGLWidget):

    def __init__(self, parent=None):
        super(GLWidget, self).__init__(parent)

        self.object = 0
        self.xRot = 0
        self.yRot = 0
        self.zRot = 0

        self.lastPos = QPoint()

        self.main = QColor.fromCmykF(0.40, 0.0, 1.0, 0.0)
        self.clear = QColor.fromCmykF(0.39, 0.39, 0.0, 0.0)

        self.img = Image.open("BWTopo.png")
        #self.img = Image.open("worldmap1.bmp")
        self.mapWidth, self.mapHeight = self.img.size
        pgImData = np.asarray(self.img)
        self.inputMapFile = np.flipud(pgImData)

    def sizeHint(self):
        return QSize(400, 400)

    def setXRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.xRot:
            self.xRot = angle
            self.update()

    def setYRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.yRot:
            self.yRot = angle
            self.update()

    def setZRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.zRot:
            self.zRot = angle
            self.update()

    def initializeGL(self):
        version_profile = QOpenGLVersionProfile()
        version_profile.setVersion(2, 0)
        self.gl = self.context().versionFunctions(version_profile)
        self.gl.initializeOpenGLFunctions()

        self.setClearColor(self.clear.darker())
        self.object = self.makeObject()
        self.gl.glShadeModel(self.gl.GL_SMOOTH)
        self.gl.glEnable(self.gl.GL_DEPTH_TEST)
        self.gl.glEnable(self.gl.GL_CULL_FACE)
        self.gl.glEnable(self.gl.GL_LIGHTING)
        self.gl.glLightModelfv(self.gl.GL_LIGHT_MODEL_AMBIENT, [0.9, 0.9, 0.9, 1.0])
        self.gl.glEnable(self.gl.GL_COLOR_MATERIAL)
        self.gl.glColorMaterial(self.gl.GL_FRONT, self.gl.GL_AMBIENT_AND_DIFFUSE)
        
        self.gl.glActiveTexture(self.gl.GL_TEXTURE0)
        self.text_obj = self.gl.glGenTextures(1)
        self.gl.glBindTexture(self.gl.GL_TEXTURE_2D, self.text_obj)
        self.gl.glPixelStorei(self.gl.GL_UNPACK_ALIGNMENT, 1)
        self.gl.glTexImage2D(self.gl.GL_TEXTURE_2D, 0, self.gl.GL_RGB, self.mapWidth, self.mapHeight, 0, self.gl.GL_RGB, self.gl.GL_UNSIGNED_BYTE, self.inputMapFile.tobytes())
        self.gl.glPixelStorei(self.gl.GL_UNPACK_ALIGNMENT, 4)
        self.gl.glTexParameterf(self.gl.GL_TEXTURE_2D, self.gl.GL_TEXTURE_MAG_FILTER, self.gl.GL_LINEAR)
        self.gl.glTexParameterf(self.gl.GL_TEXTURE_2D, self.gl.GL_TEXTURE_MIN_FILTER, self.gl.GL_LINEAR)
        
    def paintGL(self):
        self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
        self.gl.glLoadIdentity()
        self.gl.glTranslated(0.0, 0.0, -10.0)
        self.gl.glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0)
        self.gl.glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0)
        self.gl.glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0)
        
        self.gl.glEnable(self.gl.GL_TEXTURE_2D) 
        self.gl.glBindTexture(self.gl.GL_TEXTURE_2D, self.text_obj)
        self.gl.glColor3f(1, 1, 1)
        self.gl.glCallList(self.object)
        
    def resizeGL(self, width, height):
        side = min(width, height)
        if side < 0:
            return

        self.gl.glViewport((width - side) // 2, (height - side) // 2, side,
                side)

        self.gl.glMatrixMode(self.gl.GL_PROJECTION)
        self.gl.glLoadIdentity()
        self.gl.glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0)
        self.gl.glMatrixMode(self.gl.GL_MODELVIEW)

    def mousePressEvent(self, event):
        self.lastPos = event.pos()

    def mouseMoveEvent(self, event):
        dx = event.x() - self.lastPos.x()
        dy = event.y() - self.lastPos.y()

        if event.buttons() & Qt.LeftButton:
            self.setXRotation(self.xRot + 8 * dy)
            self.setYRotation(self.yRot + 8 * dx)
        elif event.buttons() & Qt.RightButton:
            self.setXRotation(self.xRot + 8 * dy)
            self.setZRotation(self.zRot + 8 * dx)

        self.lastPos = event.pos()

    def makeObject(self):
        genList = self.gl.glGenLists(1)
        self.gl.glNewList(genList, self.gl.GL_COMPILE)
        self.gl.glBegin(self.gl.GL_TRIANGLES)

        UResolution = 18
        VResolution = 36
        r = 0.3
        startU = 0
        startV = 0
        endU = math.pi * 2
        endV = math.pi
        stepU = (endU-startU)/UResolution # step size between U-points on the grid
        stepV = (endV-startV)/VResolution # step size between V-points on the grid
        for i in range(UResolution):  # U-points
            for j in range(VResolution):  # V-points
                u = i*stepU+startU
                v = j*stepV+startV
                un = endU if (i+1==UResolution) else (i+1)*stepU+startU
                vn = endV if (j+1==VResolution) else (j+1)*stepV+startV

                p0 = [ math.cos(u)*math.sin(v)*r, math.cos(v)*r, math.sin(u)*math.sin(v)*r ]
                p1 = [ math.cos(u)*math.sin(vn)*r, math.cos(vn)*r, math.sin(u)*math.sin(vn)*r ] 
                p2 = [ math.cos(un)*math.sin(v)*r, math.cos(v)*r, math.sin(un)*math.sin(v)*r ]
                p3 = [ math.cos(un)*math.sin(vn)*r, math.cos(vn)*r, math.sin(un)*math.sin(vn)*r ]

                t0 = [i/UResolution, 1-j/VResolution]
                t1 = [i/UResolution, 1-(j+1)/VResolution]
                t2 = [(i+1)/UResolution, 1-j/VResolution]
                t3 = [(i+1)/UResolution, 1-(j+1)/VResolution]

                # Output the first triangle of this grid square
                self.gl.glTexCoord2f(*t0)
                self.gl.glVertex3f(*p0)
                self.gl.glTexCoord2f(*t2)
                self.gl.glVertex3f(*p2)
                self.gl.glTexCoord2f(*t1)
                self.gl.glVertex3f(*p1)

                # Output the other triangle of this grid square
                self.gl.glTexCoord2f(*t3)
                self.gl.glVertex3f(*p3)
                self.gl.glTexCoord2f(*t1)
                self.gl.glVertex3f(*p1)
                self.gl.glTexCoord2f(*t2)
                self.gl.glVertex3f(*p2)

        self.gl.glEnd()
        self.gl.glEndList()

        return genList

    def normalizeAngle(self, angle):
        while angle < 0:
            angle += 360 * 16
        while angle > 360 * 16:
            angle -= 360 * 16
        return angle

    def setClearColor(self, c):
        self.gl.glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF())

    def setColor(self, c):
        self.gl.glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF())

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

【讨论】:

感谢您的回答!但是,当我使用您在程序中提供的代码时,它会完成而不显示任何内容。这是我的问题吗? @Drickken 我已将完整、运行良好的示例添加到问题中。

以上是关于如何在 PyQt5 QOpenGLWidget 中对球体进行纹理处理的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 QPainter 在 QOpenGLWidget 中进行叠加 [关闭]

如何在 Android 上使用 QOpenGLWidget?

如何使用着色器程序将 2D 场景放入 QOpenGLWidget 窗口?

如何使用 QOpenGLWidget 渲染文本

添加到 QGraphicsScene 时如何更新 OpenGL 绘图(QOpenGLWidget)?

如何让 qt 设计器自定义 QOpenGLWidget 小部件背景透明?