OpenGL 中的等距视图

Isometric view in OpenGL

我想了解为什么等轴测视图不正确。最准确的表示是与垂直轴成 55 度,与水平轴成 45 度。在 mcve 中有两个点应该在每个轴上以 45 度对齐。我已经根据球体代数定义设置了相机运动。平面的边缘应该重合,但它们不重合。

MCVE:

from OpenGL.GL import glClear, GL_COLOR_BUFFER_BIT, glEnable, GL_DEPTH_TEST, glMatrixMode, GL_PROJECTION, \
    glLoadIdentity, glOrtho, glClearColor, GL_DEPTH_BUFFER_BIT, GL_MODELVIEW, glLineWidth, glBegin, glColor, glVertex, \
    glEnd, glPointSize, GL_POINT_SMOOTH, GL_POINTS, GL_BLEND, glBlendFunc, GL_SRC_ALPHA, \
    GL_QUADS, glDisable, GL_LINES, GL_LINE_LOOP, glDepthMask, GL_FALSE, GL_TRUE, GL_ONE_MINUS_SRC_ALPHA
from OpenGL.GLU import gluLookAt
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QOpenGLWidget
from math import sin, cos, radians


class Renderizador(QOpenGLWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.dx = 0
        self.dy = 0
        self.dz = 0
        self.theta = 405
        self.phi = 45
        self.x = sin(radians(self.theta)) * cos(radians(self.phi)) + self.dx
        self.z = sin(radians(self.theta)) * sin(radians(self.phi)) + self.dz
        self.y = cos(radians(self.theta)) + self.dy
        self.vertices_vertical = ((100, 100, 0), (-100, 100, 0), (-100, 0, 0), (100, 0, 0))
        self.vertices_vertical_debajo = ((100, 0, 0), (-100, 0, 0), (-100, -100, 0), (100, -100, 0))
        self.vertices_horizontal = ((100, 0, 0), (100, 0, 100), (-100, 0, 100), (-100, 0, 0))
        self.vertices_horizontal_detras = ((100, 0, 0), (100, 0, -100), (-100, 0, -100), (-100, 0, 0))
        self.vertices_borde_v = ((100, 100, 0), (100, -100, 0), (-100, -100, 0), (-100, 100, 0))
        self.vertices_borde_h = ((100, 0, 100), (-100, 0, 100), (-100, 0, -100), (100, 0, -100))
        self.puntos = [("a", 1, 1, 1), ("b", 10, 10, 10)]

    def recalcular(self):
        self.x = sin(radians(self.theta)) * cos(radians(self.phi)) + self.dx
        self.z = sin(radians(self.theta)) * sin(radians(self.phi)) + self.dz
        self.y = cos(radians(self.theta)) + self.dy
        gluLookAt(self.x, self.y, self.z, self.dx, self.dy, self.dz, 0, 1, 0)
        self.update()

    def dibujar_planos(self):
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glDepthMask(GL_FALSE)
        glBegin(GL_QUADS)
        glColor(1, 0, 0, 0.5)
        for vertex in range(4):
            glVertex(self.vertices_horizontal_detras[vertex])
        glColor(0, 1, 0, 0.5)
        for vertex in range(4):
            glVertex(self.vertices_vertical_debajo[vertex])
        glColor(0, 1, 0, 0.5)
        for vertex in range(4):
            glVertex(self.vertices_vertical[vertex])
        glColor(1, 0, 0, 0.5)
        for vertex in range(4):
            glVertex(self.vertices_horizontal[vertex])
        glEnd()
        glDepthMask(GL_TRUE)
        glDisable(GL_BLEND)
        glLineWidth(1)
        glColor(0.2, 1, 0.2, 0.5)
        glBegin(GL_LINE_LOOP)
        for vertex in range(4):
            glVertex(self.vertices_borde_v[vertex])
        glColor(1, 0.2, 0.2, 0.5)
        glEnd()
        glBegin(GL_LINE_LOOP)
        for vertex in range(4):
            glVertex(self.vertices_borde_h[vertex])
        glEnd()

    @staticmethod
    def dibujar_ejes():
        glLineWidth(3)
        glBegin(GL_LINES)
        # X ROJO
        glColor(1, 0, 0)
        glVertex(0, 0, 0)
        glVertex(10, 0, 0)
        # Y VERDE
        glColor(0, 1, 0)
        glVertex(0, 0, 0)
        glVertex(0, 10, 0)
        # Z AZUL
        glColor(0, 0, 1)
        glVertex(0, 0, 0)
        glVertex(0, 0, 10)
        glEnd()

    def dibujar_punto(self):

        glColor(0, 0, 0, 0)
        glPointSize(4)
        glEnable(GL_POINT_SMOOTH)
        glBegin(GL_POINTS)
        for i in range(len(self.puntos)):
            glVertex(self.puntos[i][1], self.puntos[i][3], self.puntos[i][2])
        glEnd()

        # self.x = sin(radians(self.theta)) * cos(radians(self.phi)) + self.dx
        # self.z = sin(radians(self.theta)) * sin(radians(self.phi)) + self.dz
        # self.y = cos(radians(self.theta)) + self.dy
        # gluLookAt(self.x, self.y, self.z, self.dx, self.dy, self.dz, 0, 1, 0)
        self.update()

    def initializeGL(self):
        glEnable(GL_DEPTH_TEST)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(100, -100, -100, 100, -500, 150)
        glMatrixMode(GL_MODELVIEW)

    def paintGL(self):
        glClearColor(1, 1, 1, 0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        up = 1
        if self.theta == 360:
            up = -1
        gluLookAt(self.x, self.y, self.z, self.dx, self.dy, self.dz, 0, up, 0)
        self.dibujar_ejes()
        self.dibujar_planos()
        self.dibujar_punto()

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_W:
            self.theta -= 5
        elif event.key() == QtCore.Qt.Key_A:
            self.phi -= 5
        elif event.key() == QtCore.Qt.Key_S:
            self.theta += 5
        elif event.key() == QtCore.Qt.Key_D:
            self.phi += 5
        elif event.key() == QtCore.Qt.Key_Q:
            self.dz += 1
        elif event.key() == QtCore.Qt.Key_E:
            self.dz -= 1
        elif event.key() == QtCore.Qt.Key_Left:
            self.dx -= 1
        elif event.key() == QtCore.Qt.Key_Up:
            self.dy += 1
        elif event.key() == QtCore.Qt.Key_Right:
            self.dx += 1
        elif event.key() == QtCore.Qt.Key_Down:
            self.dy -= 1

        if self.theta < 360:
            self.theta = 360
        if self.theta > 540:
            self.theta = 540
        if self.phi >= 360:
            self.phi -= 360
        if self.phi < 0:
            self.phi += 360

        self.x = sin(radians(self.theta)) * cos(radians(self.phi)) + self.dx
        self.z = sin(radians(self.theta)) * sin(radians(self.phi)) + self.dz
        self.y = cos(radians(self.theta)) + self.dy

        ui.actualizar()
        self.update()
        super().keyPressEvent(event)


class UiVentana(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(UiVentana, self).__init__(parent)
        ventana.resize(1500, 1015)

        self.widget_central = QtWidgets.QWidget(ventana)
        self.Renderizador = Renderizador(self.widget_central)
        self.Renderizador.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
        self.Renderizador.setFocusPolicy(QtCore.Qt.StrongFocus)

        self.label_5 = QtWidgets.QLabel(self.widget_central)
        self.label_5.setGeometry(QtCore.QRect(1110, 49, 160, 20))
        self.label_6 = QtWidgets.QLabel(self.widget_central)
        self.label_6.setGeometry(QtCore.QRect(1010, 70, 111, 16))
        self.label_7 = QtWidgets.QLabel(self.widget_central)
        self.label_7.setGeometry(QtCore.QRect(1130, 70, 130, 16))

        self.label_5.setText("X: Y: Z:")
        self.label_6.setText("Ángulo vertical:")
        self.label_7.setText("Ángulo horizontal:")

        ventana.setCentralWidget(self.widget_central)
        ventana.show()

    def actualizar(self):
        x = round(100 * (sin(radians(self.Renderizador.theta)) * cos(radians(self.Renderizador.phi)))
                  + self.Renderizador.dx, 2)
        z = round(100 * (sin(radians(self.Renderizador.theta)) * sin(radians(self.Renderizador.phi)))
                  + self.Renderizador.dz, 2)
        y = round(100 * (cos(radians(self.Renderizador.theta))) + self.Renderizador.dy, 2)
        theta = self.Renderizador.theta - 360
        phi = self.Renderizador.phi
        if x == -0:
            x = 0
        if y == -0:
            y = 0
        if z == -0:
            z = 0
        self.label_5.setText("X: {} Y: {} Z: {}".format(x, z, y))
        self.label_6.setText("Ángulo vertical: " + str(theta))
        self.label_7.setText("Ángulo horizontal: " + str(phi))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication([])
    ventana = QtWidgets.QMainWindow()
    ui = UiVentana()
    sys.exit(app.exec_())

要获得完美的等距视图,视点必须位于立方体的对角线上。这意味着如果视图的目标是 (0, 0, 0),那么眼睛位置的 x、y 和 z 坐标的绝对值必须相等。

当您查看计算 x、y 和 z 的公式时:

self.x = sin(radians(self.theta)) * cos(radians(self.phi)) + self.dx
self.z = sin(radians(self.theta)) * sin(radians(self.phi)) + self.dz
self.y = cos(radians(self.theta)) + self.dy

这意味着您必须找到一个 phitheta 使 x、y 和 z 相等。

对于 phi 这很容易。 Phi 仅用于计算 x 和 z。 x 取决于 cos(phi),z 取决于 sin(phi),因为 sin(45) == cos(45)phi 必须为 45°。

sin(45) 等于 1/sqrt(2).

所以必须找到一个theta,其中x,y,z相等时

x = sin(theta) * 1/sqrt(2)
z = sin(theta) * 1/sqrt(2)
y = cos(theta)  

这意味着

sin(theta) * 1/sqrt(2) = cos(theta) 

Pythagoras我们知道:

cos²(theta) + sin²(theta) = 1

所以

sin(theta) = cos(theta) * sqrt(2)sin²(theta) = 1 - cos²(theta)

1 - cos²(theta) = cos²(theta) * 2

1 = cos(theta) * sqrt(3)

theta = acos(1/sqrt(3))

最后 phitheta 必须是:

self.phi   = 45
self.theta = 54.735610317245345684622999669982