【问题标题】:Proper way to handle camera rotations处理相机旋转的正确方法
【发布时间】:2019-05-30 08:44:42
【问题描述】:

让我们从考虑 2 种类型的相机旋转开始:

相机围绕一个点旋转(轨道):

def rotate_around_target(self, target, delta):
    right = (self.target - self.eye).cross(self.up).normalize()
    amount = (right * delta.y + self.up * delta.x)
    self.target = target
    self.up = self.original_up
    self.eye = (
        mat4.rotatez(amount.z) *
        mat4.rotatey(amount.y) *
        mat4.rotatex(amount.x) *
        vec3(self.eye)
    )

相机旋转目标 (FPS)

def rotate_target(self, delta):
    right = (self.target - self.eye).cross(self.up).normalize()
    self.target = (
        mat4.translate(self.eye) *
        mat4().rotate(delta.y, right) *
        mat4().rotate(delta.x, self.up) *
        mat4.translate(-self.eye) *
        self.target
    )

然后只是一个更新函数,其中投影/视图矩阵是根据眼睛/目标/向上相机向量计算出来的:

def update(self, aspect):
    self.view = mat4.lookat(self.eye, self.target, self.up)
    self.projection = mat4.perspective_fovx(
        self.fov, aspect, self.near, self.far
    )

当相机视图方向平行于上轴(此处为 z-up)时,这些旋转函数会出现问题...此时相机的行为非常糟糕,因此我会遇到以下故障:

所以我的问题是,我怎样才能调整上面的代码,以便相机进行完整旋转,而最终结果在某些边缘点看起来很奇怪(相机轴翻转:/)?

我希望拥有与市面上许多 DCC 软件包(3dsmax、maya、...)相同的行为,它们可以完全旋转而不会出现任何奇怪的行为。

编辑:

对于那些想试一试数学的人,我决定创建一个能够重现所解释问题的真正简约版本:

import math
from ctypes import c_void_p

import numpy as np
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import glm


class Camera():

    def __init__(
        self,
        eye=None, target=None, up=None,
        fov=None, near=0.1, far=100000
    ):
        self.eye = eye or glm.vec3(0, 0, 1)
        self.target = target or glm.vec3(0, 0, 0)
        self.up = up or glm.vec3(0, 1, 0)
        self.original_up = glm.vec3(self.up)
        self.fov = fov or glm.radians(45)
        self.near = near
        self.far = far

    def update(self, aspect):
        self.view = glm.lookAt(
            self.eye, self.target, self.up
        )
        self.projection = glm.perspective(
            self.fov, aspect, self.near, self.far
        )

    def rotate_target(self, delta):
        right = glm.normalize(glm.cross(self.target - self.eye, self.up))
        M = glm.mat4(1)
        M = glm.translate(M, self.eye)
        M = glm.rotate(M, delta.y, right)
        M = glm.rotate(M, delta.x, self.up)
        M = glm.translate(M, -self.eye)
        self.target = glm.vec3(M * glm.vec4(self.target, 1.0))

    def rotate_around_target(self, target, delta):
        right = glm.normalize(glm.cross(self.target - self.eye, self.up))
        amount = (right * delta.y + self.up * delta.x)
        M = glm.mat4(1)
        M = glm.rotate(M, amount.z, glm.vec3(0, 0, 1))
        M = glm.rotate(M, amount.y, glm.vec3(0, 1, 0))
        M = glm.rotate(M, amount.x, glm.vec3(1, 0, 0))
        self.eye = glm.vec3(M * glm.vec4(self.eye, 1.0))
        self.target = target
        self.up = self.original_up

    def rotate_around_origin(self, delta):
        return self.rotate_around_target(glm.vec3(0), delta)


class GlutController():

    FPS = 0
    ORBIT = 1

    def __init__(self, camera, velocity=100, velocity_wheel=100):
        self.velocity = velocity
        self.velocity_wheel = velocity_wheel
        self.camera = camera

    def glut_mouse(self, button, state, x, y):
        self.mouse_last_pos = glm.vec2(x, y)
        self.mouse_down_pos = glm.vec2(x, y)

        if button == GLUT_LEFT_BUTTON:
            self.mode = self.FPS
        elif button == GLUT_RIGHT_BUTTON:
            self.mode = self.ORBIT

    def glut_motion(self, x, y):
        pos = glm.vec2(x, y)
        move = self.mouse_last_pos - pos
        self.mouse_last_pos = pos

        if self.mode == self.FPS:
            self.camera.rotate_target(move * 0.005)
        elif self.mode == self.ORBIT:
            self.camera.rotate_around_origin(move * 0.005)


class MyWindow:

    def __init__(self, w, h):
        self.width = w
        self.height = h

        glutInit()
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
        glutInitWindowSize(w, h)
        glutCreateWindow('OpenGL Window')

        self.startup()

        glutReshapeFunc(self.reshape)
        glutDisplayFunc(self.display)
        glutMouseFunc(self.controller.glut_mouse)
        glutMotionFunc(self.controller.glut_motion)
        glutIdleFunc(self.idle_func)

    def startup(self):
        glEnable(GL_DEPTH_TEST)

        aspect = self.width / self.height
        self.camera = Camera(
            eye=glm.vec3(10, 10, 10),
            target=glm.vec3(0, 0, 0),
            up=glm.vec3(0, 1, 0)
        )
        self.model = glm.mat4(1)
        self.controller = GlutController(self.camera)

    def run(self):
        glutMainLoop()

    def idle_func(self):
        glutPostRedisplay()

    def reshape(self, w, h):
        glViewport(0, 0, w, h)
        self.width = w
        self.height = h

    def display(self):
        self.camera.update(self.width / self.height)

        glClearColor(0.2, 0.3, 0.3, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(glm.degrees(self.camera.fov), self.width / self.height, self.camera.near, self.camera.far)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        e = self.camera.eye
        t = self.camera.target
        u = self.camera.up
        gluLookAt(e.x, e.y, e.z, t.x, t.y, t.z, u.x, u.y, u.z)
        glColor3f(1, 1, 1)
        glBegin(GL_LINES)
        for i in range(-5, 6):
            if i == 0:
                continue
            glVertex3f(-5, 0, i)
            glVertex3f(5, 0, i)
            glVertex3f(i, 0, -5)
            glVertex3f(i, 0, 5)
        glEnd()

        glBegin(GL_LINES)
        glColor3f(1, 0, 0)
        glVertex3f(-5, 0, 0)
        glVertex3f(5, 0, 0)
        glColor3f(0, 1, 0)
        glVertex3f(0, -5, 0)
        glVertex3f(0, 5, 0)
        glColor3f(0, 0, 1)
        glVertex3f(0, 0, -5)
        glVertex3f(0, 0, 5)
        glEnd()

        glutSwapBuffers()


if __name__ == '__main__':
    window = MyWindow(800, 600)
    window.run()

为了运行它,您需要安装 pyopenglpyglm

【问题讨论】:

  • 这就是为什么你需要基于四元数的旋转。您遇到的问题由云台锁解释。
  • 我多次听说云台锁,但我从未完全理解其背后的数学原理。使用双亲旋转时,您最终可能会得到 6 种可能的组合 {xyz, xzy, yxz, yzx, zxy, zyx},在所有这些组合中,都会出现某些情况下会发生云台锁定,在发布的代码中,您如何识别会发生云台锁吗?但最有趣的问题是,如何更新代码以使用四元数?您可以假设存在典型的四元数类
  • 中间角为90度时会发生Gimabl锁。但这只是一个旁注。不需要四元数来解决这个问题。您的问题的根本原因是您无缘无故地使用了一些“lookAt”功能。您所需要的只是一个相机位置(通常是一个矢量)和它的 3d 方向(表示为旋转矩阵或四元数或任何您认为合适的)。因此,您可以轻松地从两者创建视图矩阵,并且可以单独调整这两个组件。
  • 这里的事情是,修复lookAt 方法的唯一解决方案意味着不仅要旋转target 向量,还要旋转up 向量 - 并做到这一点,您需要对相机方向(mat 或 quat)进行一些合理的表示,但如果有,则根本不需要 lookAt
  • ammount = (right * delta.y + self.up * delta.x) - 尝试组合围绕两个轴的旋转的奇怪方式,当然是不正确的。 FPS 相机中的矩阵代码产生更好的结果并且可以进行调整。

标签: python opengl 3d camera pyopengl


【解决方案1】:

我建议在视图空间中围绕枢轴进行旋转

您必须知道视图矩阵 (V)。由于视图矩阵在self.eyeself.targetself.up中编码,因此必须由lookAt计算:

V = glm.lookAt(self.eye, self.target, self.up)

计算视图空间中的pivot、旋转角度和旋转轴。在这种情况下,轴是右旋转方向,其中 y 轴必须翻转:

pivot = glm.vec3(V * glm.vec4(target.x, target.y, target.z, 1))
axis  = glm.vec3(-delta.y, -delta.x, 0)
angle = glm.length(delta)

设置旋转矩阵R 并计算围绕枢轴RP 的比率矩阵。最后通过旋转矩阵变换视图矩阵(V)。结果是新的视图矩阵NV

R  = glm.rotate( glm.mat4(1), angle, axis )
RP = glm.translate(glm.mat4(1), pivot) * R * glm.translate(glm.mat4(1), -pivot)
NV = RP * V

从新的视图矩阵NV解码self.eyeself.targetself.up

C = glm.inverse(NV)
targetDist  = glm.length(self.target - self.eye)
self.eye    = glm.vec3(C[3])
self.target = self.eye - glm.vec3(C[2]) * targetDist 
self.up     = glm.vec3(C[1])

方法rotate_around_target_view的完整编码:

def rotate_around_target_view(self, target, delta):

    V = glm.lookAt(self.eye, self.target, self.up)

    pivot = glm.vec3(V * glm.vec4(target.x, target.y, target.z, 1))
    axis  = glm.vec3(-delta.y, -delta.x, 0)
    angle = glm.length(delta)

    R  = glm.rotate( glm.mat4(1), angle, axis )
    RP = glm.translate(glm.mat4(1), pivot) * R * glm.translate(glm.mat4(1), -pivot)
    NV = RP * V

    C = glm.inverse(NV)
    targetDist  = glm.length(self.target - self.eye)
    self.eye    = glm.vec3(C[3])
    self.target = self.eye - glm.vec3(C[2]) * targetDist 
    self.up     = glm.vec3(C[1])

最后它可以围绕世界的原点和眼睛的位置甚至任何其他点进行旋转。

def rotate_around_origin(self, delta):
    return self.rotate_around_target_view(glm.vec3(0), delta)

def rotate_target(self, delta):
    return self.rotate_around_target_view(self.eye, delta)

或者,可以在模型的世界空间中执行旋转。解决方案非常相似。 旋转是在世界空间中完成的,因此枢轴不必转换到视图空间,并且旋转应用在视图矩阵之前(NV = V * RP):

def rotate_around_target_world(self, target, delta):

    V = glm.lookAt(self.eye, self.target, self.up)

    pivot = target
    axis  = glm.vec3(-delta.y, -delta.x, 0)
    angle = glm.length(delta)

    R  = glm.rotate( glm.mat4(1), angle, axis )
    RP = glm.translate(glm.mat4(1), pivot) * R * glm.translate(glm.mat4(1), -pivot)
    NV = V * RP

    C = glm.inverse(NV)
    targetDist  = glm.length(self.target - self.eye)
    self.eye    = glm.vec3(C[3])
    self.target = self.eye - glm.vec3(C[2]) * targetDist 
    self.up     = glm.vec3(C[1]) 

def rotate_around_origin(self, delta):
    return self.rotate_around_target_world(glm.vec3(0), delta)


当然,这两种解决方案都可以结合使用。通过垂直(上下)拖动,视图可以在其水平轴上旋转。通过水平(左右)拖动模型(世界)可以围绕其(上)轴旋转:

def rotate_around_target(self, target, delta):
    if abs(delta.x) > 0:
        self.rotate_around_target_world(target, glm.vec3(delta.x, 0.0, 0.0))
    if abs(delta.y) > 0:    
        self.rotate_around_target_view(target, glm.vec3(0.0, delta.y, 0.0))

我为了实现微创的方法,考虑到问题的原始代码,我会提出以下建议:

  • 操作后视图的目标应该是函数rotate_around_target的输入参数target

  • 水平鼠标移动应围绕世界的向上矢量旋转视图

  • 鼠标垂直移动应该围绕当前水平轴倾斜视图

我想出了以下方法:

  1. 计算当前视线(los)、上矢量(up)和水平轴(right

  2. 通过将向上矢量投影到由原始向上矢量和当前视线给出的平面上,使向上矢量垂直。这是Gram–Schmidt orthogonalization 穿的。

  3. 围绕当前水平轴倾斜。这意味着losup 围绕right 轴旋转。

  4. 围绕向上矢量旋转。 losright 围绕 up 旋转。

  5. Calculate 设置并计算眼睛和目标位置,其中目标由输入参数target设置:

def rotate_around_target(self, target, delta):

    # get directions
    los    = self.target - self.eye
    losLen = glm.length(los)
    right  = glm.normalize(glm.cross(los, self.up))
    up     = glm.cross(right, los)

    # upright up vector (Gram–Schmidt orthogonalization)
    fix_right = glm.normalize(glm.cross(los, self.original_up))
    UPdotX    = glm.dot(fix_right, up)
    up        = glm.normalize(up - UPdotX * fix_right)
    right     = glm.normalize(glm.cross(los, up))
    los       = glm.cross(up, right)

    # tilt around horizontal axis
    RHor = glm.rotate(glm.mat4(1), delta.y, right)
    up   = glm.vec3(RHor * glm.vec4(up, 0.0))
    los  = glm.vec3(RHor * glm.vec4(los, 0.0))

    # rotate around up vector
    RUp   = glm.rotate(glm.mat4(1), delta.x, up)
    right = glm.vec3(RUp * glm.vec4(right, 0.0))
    los   = glm.vec3(RUp * glm.vec4(los, 0.0))

    # set eye, target and up
    self.eye    = target - los * losLen 
    self.target = target
    self.up     = up    

【讨论】:

  • 首先非常感谢您的回答!我很高兴您尝试在不使用 cmets 上建议的任何替代相机表示的情况下修复我的代码,我发现在许多情况下,在不解码视图矩阵的情况下明确存储眼睛/目标/向上向量非常方便。也就是说,我已经在发布的 sn-p 和我的引擎中测试了你的代码,我得到的结果很奇怪......请看看这个video。如果您将代码插入 mcve,您应该得到相同的结果...... :(
  • 再次感谢...但是您的方法对我来说仍然不合适(可能是因为向上向量)?无论如何,我邀请您进行下一个测试,首先运行这个小 script 将第 167 行设置为mode=0,玩弄(这是正确的)然后将第 167 行切换到 mode=1(您的方法)...比较两者,然后请让我知道我是否能够正确解释我的担忧...同时,我将测试@skatic的答案给出的解决方案
  • 最后一个问题,你能解释一下axis = vec3(-delta.y, -delta.x, 0)angle = delta.length()这两行吗?在我的引擎中问你,我通常处理两种类型的相机,有时我有 y-up 相机,但在其他情况下,z-up 相机。对于最后一种情况,我需要稍微调整您的代码。
  • @BPL 1. delta 是“拖动”方向。 A (x, y) 可以通过 (y, -x) 向右旋转 90 度。 Y 必须翻转,因为“OpenGL”原点在左下角,但窗口(鼠标)原点在左上角。所以drag方向的旋转轴是flip = -1vec3(flip * delta.y, -delta.x, 0)。 2.我以为旋转角度编码为drag的长度。 angle = delta.length() 是我的假设。
  • 很抱歉回到这个,但是......无论相机是 [0,1,0] 还是 [0,0,1],你将如何完成这项工作。您的方法适用于 [0,1,0] 但在使用 [0,0,1] 时表现非常奇怪:/
【解决方案2】:

有这么多重新发明轮子的方法不是吗?这是一个简洁的选项(改编自 Opengl Development Cookbook,M.M.Movania,第 2 章中的目标相机概念):

  1. 首先创建新的方向(旋转)矩阵(更新为使用累积的鼠标增量)

    # global variables somewhere appropriate (or class variables)
    mouseX = 0.0
    mouseY = 0.0
    def rotate_around_target(self, target, delta):
        global mouseX
        global mouseY
        mouseX += delta.x/5.0
        mouseY += delta.y/5.0
        glm::mat4 M = glm::mat4(1)
        M = glm::rotate(M, delta.z, glm::vec3(0, 0, 1))
        M = glm::rotate(M, mouseX , glm::vec3(0, 1, 0))
        M = glm::rotate(M, mouseY, glm::vec3(1, 0, 0))
    
  2. 利用距离得到一个向量,然后通过当前的旋转矩阵平移这个向量

        self.target = target
        float distance = glm::distance(self.target, self.eye)
        glm::vec3 T = glm::vec3(0, 0, distance)
        T = glm::vec3(M*glm::vec4(T, 0.0f))
    
  3. 通过将平移向量添加到目标位置来获取新的相机眼睛位置

        self.eye = self.target + T
    
  4. 重新计算标准正交基(你只需要做 UP 向量)

        # assuming self.original_up = glm::vec3(0, 1, 0)
        self.up = glm::vec3(M*glm::vec4(self.original_up, 0.0f))
        # or
        self.up = glm::vec3(M*glm::vec4(glm::vec3(0, 1, 0), 0.0f))
    

5...然后您可以通过使用 lookAt 函数更新视图矩阵来尝试一下

    self.view = glm.lookAt( self.eye, self.target, self.up)

这是我迄今为止发现的这类转换问题/解决方案中最简单的概念。我在 C/C++ 中对其进行了测试,并为您将其修改为 pyopengl 语法(我真诚地希望如此)。让我们知道(或不)进展如何。

【讨论】:

  • 感谢您的贡献,但我无法正确插入您的方法,如果您想修补它,我已经创建了一个小答案。也就是说,为努力+1。我已经验证了@Rabbid76,因为它是第一个登陆的,开箱即用,还多次编辑了它的问题,给出了正确的解释:)
  • 啊,好吧,我错了。我怀疑问题是三角洲。此方法需要使用累积的增量:例如:class CameraSkatic(Camera):
  • 所以这是我第一次使用它。我的例子被删减/超时了吗?现在我不能再编辑它了,对吗?
  • 我不确定,但我猜你需要获得某些特权才能执行某些任务,看看:https://stackoverflow.com/help/privileges。但是,是的,2 cmets 前你的例子被删掉了......你刚刚写下了class CameraSkatic(Camera):。通常,当您想修复您的答案时,您可以直接在帖子下方编辑您的答案(您应该会在帖子下方看到 3 个共享/编辑/标记按钮)......希望对您有所帮助
  • 我会再试一次。 RE:累积的鼠标增量。您可以查看此示例修改是否有效# global variables mouseX = 0.0 mouseY = 0.0 class CameraSkatic(Camera): def rotate_around_target(self, target, delta): global mouseX global mouseY mouseX += delta.x/5.0 mouseY += delta.y/5.0 M = glm.mat4(1) M = glm.rotate(M, mouseX glm.vec3(0, 1, 0)) M = glm.rotate(M, mouseY, glm.vec3(1, 0, 0)) # rest of the code ........... ...........我想我也无法格式化代码?
【解决方案3】:

以下是此线程中提供的所有答案的小摘要:

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

import glm


class Camera():

    def __init__(
        self,
        eye=None, target=None, up=None,
        fov=None, near=0.1, far=100000
    ):
        self.eye = eye or glm.vec3(0, 0, 1)
        self.target = target or glm.vec3(0, 0, 0)
        self.up = up or glm.vec3(0, 1, 0)
        self.original_up = glm.vec3(self.up)
        self.fov = fov or glm.radians(45)
        self.near = near
        self.far = far

    def update(self, aspect):
        self.view = glm.lookAt(
            self.eye, self.target, self.up
        )
        self.projection = glm.perspective(
            self.fov, aspect, self.near, self.far
        )

    def zoom(self, *args):
        delta = -args[1] * 0.1
        distance = glm.length(self.target - self.eye)
        self.eye = self.target + (self.eye - self.target) * (delta + 1)

    def load_projection(self):
        width = glutGet(GLUT_WINDOW_WIDTH)
        height = glutGet(GLUT_WINDOW_HEIGHT)

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(glm.degrees(self.fov), width / height, self.near, self.far)

    def load_modelview(self):
        e = self.eye
        t = self.target
        u = self.up

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        gluLookAt(e.x, e.y, e.z, t.x, t.y, t.z, u.x, u.y, u.z)


class CameraSkatic(Camera):

    def rotate_around_target(self, target, delta):
        M = glm.mat4(1)
        M = glm.rotate(M, delta.x, glm.vec3(0, 1, 0))
        M = glm.rotate(M, delta.y, glm.vec3(1, 0, 0))

        self.target = target
        T = glm.vec3(0, 0, glm.distance(self.target, self.eye))
        T = glm.vec3(M * glm.vec4(T, 0.0))
        self.eye = self.target + T
        self.up = glm.vec3(M * glm.vec4(self.original_up, 1.0))

    def rotate_around_origin(self, delta):
        return self.rotate_around_target(glm.vec3(0), delta)


class CameraBPL(Camera):

    def rotate_target(self, delta):
        right = glm.normalize(glm.cross(self.target - self.eye, self.up))
        M = glm.mat4(1)
        M = glm.translate(M, self.eye)
        M = glm.rotate(M, delta.y, right)
        M = glm.rotate(M, delta.x, self.up)
        M = glm.translate(M, -self.eye)
        self.target = glm.vec3(M * glm.vec4(self.target, 1.0))

    def rotate_around_target(self, target, delta):
        right = glm.normalize(glm.cross(self.target - self.eye, self.up))
        amount = (right * delta.y + self.up * delta.x)
        M = glm.mat4(1)
        M = glm.rotate(M, amount.z, glm.vec3(0, 0, 1))
        M = glm.rotate(M, amount.y, glm.vec3(0, 1, 0))
        M = glm.rotate(M, amount.x, glm.vec3(1, 0, 0))
        self.eye = glm.vec3(M * glm.vec4(self.eye, 1.0))
        self.target = target
        self.up = self.original_up

    def rotate_around_origin(self, delta):
        return self.rotate_around_target(glm.vec3(0), delta)


class CameraRabbid76_v1(Camera):

    def rotate_around_target_world(self, target, delta):
        V = glm.lookAt(self.eye, self.target, self.up)

        pivot = target
        axis = glm.vec3(-delta.y, -delta.x, 0)
        angle = glm.length(delta)

        R = glm.rotate(glm.mat4(1), angle, axis)
        RP = glm.translate(glm.mat4(1), pivot) * R * glm.translate(glm.mat4(1), -pivot)
        NV = V * RP

        C = glm.inverse(NV)
        targetDist = glm.length(self.target - self.eye)
        self.eye = glm.vec3(C[3])
        self.target = self.eye - glm.vec3(C[2]) * targetDist
        self.up = glm.vec3(C[1])

    def rotate_around_target_view(self, target, delta):
        V = glm.lookAt(self.eye, self.target, self.up)

        pivot = glm.vec3(V * glm.vec4(target.x, target.y, target.z, 1))
        axis = glm.vec3(-delta.y, -delta.x, 0)
        angle = glm.length(delta)

        R = glm.rotate(glm.mat4(1), angle, axis)
        RP = glm.translate(glm.mat4(1), pivot) * R * glm.translate(glm.mat4(1), -pivot)
        NV = RP * V

        C = glm.inverse(NV)
        targetDist = glm.length(self.target - self.eye)
        self.eye = glm.vec3(C[3])
        self.target = self.eye - glm.vec3(C[2]) * targetDist
        self.up = glm.vec3(C[1])

    def rotate_around_target(self, target, delta):
        if abs(delta.x) > 0:
            self.rotate_around_target_world(target, glm.vec3(delta.x, 0.0, 0.0))
        if abs(delta.y) > 0:
            self.rotate_around_target_view(target, glm.vec3(0.0, delta.y, 0.0))

    def rotate_around_origin(self, delta):
        return self.rotate_around_target(glm.vec3(0), delta)

    def rotate_target(self, delta):
        return self.rotate_around_target(self.eye, delta)


class CameraRabbid76_v2(Camera):

    def rotate_around_target(self, target, delta):

        # get directions
        los = self.target - self.eye
        losLen = glm.length(los)
        right = glm.normalize(glm.cross(los, self.up))
        up = glm.cross(right, los)

        # upright up vector (Gram–Schmidt orthogonalization)
        fix_right = glm.normalize(glm.cross(los, self.original_up))
        UPdotX = glm.dot(fix_right, up)
        up = glm.normalize(up - UPdotX * fix_right)
        right = glm.normalize(glm.cross(los, up))
        los = glm.cross(up, right)

        # tilt around horizontal axis
        RHor = glm.rotate(glm.mat4(1), delta.y, right)
        up = glm.vec3(RHor * glm.vec4(up, 0.0))
        los = glm.vec3(RHor * glm.vec4(los, 0.0))

        # rotate around up vector
        RUp = glm.rotate(glm.mat4(1), delta.x, up)
        right = glm.vec3(RUp * glm.vec4(right, 0.0))
        los = glm.vec3(RUp * glm.vec4(los, 0.0))

        # set eye, target and up
        self.eye = target - los * losLen
        self.target = target
        self.up = up

    def rotate_around_origin(self, delta):
        return self.rotate_around_target(glm.vec3(0), delta)

    def rotate_target(self, delta):
        return self.rotate_around_target(self.eye, delta)


class GlutController():

    FPS = 0
    ORBIT = 1

    def __init__(self, camera, velocity=100, velocity_wheel=100):
        self.velocity = velocity
        self.velocity_wheel = velocity_wheel
        self.camera = camera

    def glut_mouse(self, button, state, x, y):
        self.mouse_last_pos = glm.vec2(x, y)
        self.mouse_down_pos = glm.vec2(x, y)

        if button == GLUT_LEFT_BUTTON:
            self.mode = self.FPS
        elif button == GLUT_RIGHT_BUTTON:
            self.mode = self.ORBIT

    def glut_motion(self, x, y):
        pos = glm.vec2(x, y)
        move = self.mouse_last_pos - pos
        self.mouse_last_pos = pos

        if self.mode == self.FPS:
            self.camera.rotate_target(move * 0.005)
        elif self.mode == self.ORBIT:
            self.camera.rotate_around_origin(move * 0.005)

    def glut_mouse_wheel(self, *args):
        self.camera.zoom(*args)


def render_text(x, y, text):
    glColor3f(1, 1, 1)
    glRasterPos2f(x, y)
    glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, text.encode("utf-8"))


def draw_plane_yup():
    glColor3f(1, 1, 1)
    glBegin(GL_LINES)
    for i in range(-5, 6):
        if i == 0:
            continue
        glVertex3f(-5, 0, i)
        glVertex3f(5, 0, i)
        glVertex3f(i, 0, -5)
        glVertex3f(i, 0, 5)
    glEnd()

    glBegin(GL_LINES)
    glColor3f(1, 1, 1)
    glVertex3f(-5, 0, 0)
    glVertex3f(0, 0, 0)
    glVertex3f(0, 0, -5)
    glVertex3f(0, 0, 0)

    glColor3f(1, 0, 0)
    glVertex3f(0, 0, 0)
    glVertex3f(5, 0, 0)
    glColor3f(0, 1, 0)
    glVertex3f(0, 0, 0)
    glVertex3f(0, 5, 0)
    glColor3f(0, 0, 1)
    glVertex3f(0, 0, 0)
    glVertex3f(0, 0, 5)
    glEnd()


def draw_plane_zup():
    glColor3f(1, 1, 1)
    glBegin(GL_LINES)
    for i in range(-5, 6):
        if i == 0:
            continue
        glVertex3f(-5, 0, i)
        glVertex3f(5, 0, i)
        glVertex3f(i, -5, 0)
        glVertex3f(i, 5, 0)
    glEnd()

    glBegin(GL_LINES)
    glColor3f(1, 1, 1)
    glVertex3f(-5, 0, 0)
    glVertex3f(0, 0, 0)
    glVertex3f(0, -5, 0)
    glVertex3f(0, 0, 0)

    glColor3f(1, 0, 0)
    glVertex3f(0, 0, 0)
    glVertex3f(5, 0, 0)
    glColor3f(0, 1, 0)
    glVertex3f(0, 0, 0)
    glVertex3f(0, 0, 5)
    glColor3f(0, 0, 1)
    glVertex3f(0, 0, 0)
    glVertex3f(0, 5, 0)
    glEnd()


def line(p0, p1, color=None):
    c = color or glm.vec3(1, 1, 1)
    glColor3f(c.x, c.y, c.z)
    glVertex3f(p0.x, p0.y, p0.z)
    glVertex3f(p1.x, p1.y, p1.z)


def grid(segment_count=10, spacing=1, yup=True):
    size = segment_count * spacing
    right = glm.vec3(1, 0, 0)
    forward = glm.vec3(0, 0, 1) if yup else glm.vec3(0, 1, 0)
    x_axis = right * size
    z_axis = forward * size

    data = []
    i = -segment_count

    glBegin(GL_LINES)
    while i <= segment_count:
        p0 = -x_axis + forward * i * spacing
        p1 = x_axis + forward * i * spacing
        line(p0, p1)
        p0 = -z_axis + right * i * spacing
        p1 = z_axis + right * i * spacing
        line(p0, p1)
        i += 1
    glEnd()


def axis(size=1.0, yup=True):
    right = glm.vec3(1, 0, 0)
    forward = glm.vec3(0, 0, 1) if yup else glm.vec3(0, 1, 0)
    x_axis = right * size
    z_axis = forward * size
    y_axis = glm.cross(forward, right) * size
    glBegin(GL_LINES)
    line(x_axis, glm.vec3(0, 0, 0), glm.vec3(1, 0, 0))
    line(y_axis, glm.vec3(0, 0, 0), glm.vec3(0, 1, 0))
    line(z_axis, glm.vec3(0, 0, 0), glm.vec3(0, 0, 1))
    glEnd()


class MyWindow:

    def __init__(self, w, h):
        self.width = w
        self.height = h

        glutInit()
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
        glutInitWindowSize(w, h)
        glutCreateWindow('OpenGL Window')

        self.startup()

        glutReshapeFunc(self.reshape)
        glutDisplayFunc(self.display)
        glutMouseFunc(self.controller.glut_mouse)
        glutMotionFunc(self.controller.glut_motion)
        glutMouseWheelFunc(self.controller.glut_mouse_wheel)
        glutKeyboardFunc(self.keyboard_func)
        glutIdleFunc(self.idle_func)

    def keyboard_func(self, *args):
        try:
            key = args[0].decode("utf8")

            if key == "\x1b":
                glutLeaveMainLoop()

            if key in ['1', '2', '3', '4']:
                if key == '1':
                    self.index_camera = "Skatic"
                elif key == '2':
                    self.index_camera = "BPL"
                elif key == '3':
                    self.index_camera = "Rabbid76_v1"
                elif key == '4':
                    self.index_camera = "Rabbid76_v2"

                self.camera = self.cameras[self.index_camera]
                self.controller.camera = self.camera

            if key in ['o', 'p']:
                self.camera.eye = glm.vec3(0, 10, 10)
                self.camera.target = glm.vec3(0, 0, 0)

                if key == 'o':
                    self.yup = True
                    # self.camera.up = glm.vec3(0, 0, 1)
                elif key == 'p':
                    self.yup = False
                    # self.camera.up = glm.vec3(0, 1, 0)

                self.camera.target = glm.vec3(0, 0, 0)

        except Exception as e:
            import traceback
            traceback.print_exc()

    def startup(self):
        glEnable(GL_DEPTH_TEST)

        aspect = self.width / self.height
        params = {
            "eye": glm.vec3(0, 100, 100),
            "target": glm.vec3(0, 0, 0),
            "up": glm.vec3(0, 1, 0)
        }
        self.cameras = {
            "Skatic": CameraSkatic(**params),
            "BPL": CameraBPL(**params),
            "Rabbid76_v1": CameraRabbid76_v1(**params),
            "Rabbid76_v2": CameraRabbid76_v2(**params)
        }
        self.index_camera = "BPL"
        self.yup = True
        self.camera = self.cameras[self.index_camera]
        self.model = glm.mat4(1)
        self.controller = GlutController(self.camera)

    def run(self):
        glutMainLoop()

    def idle_func(self):
        glutPostRedisplay()

    def reshape(self, w, h):
        glViewport(0, 0, w, h)
        self.width = w
        self.height = h

    def display(self):
        self.camera.update(self.width / self.height)

        glClearColor(0.2, 0.3, 0.3, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        self.camera.load_projection()
        self.camera.load_modelview()

        glLineWidth(5)
        axis(size=70, yup=self.yup)
        glLineWidth(1)
        grid(segment_count=7, spacing=10, yup=self.yup)

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(-1, 1, -1, 1, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        info = "\n".join([
            "1: Skatic Camera",
            "2: BPL Camera",
            "3: Rabbid76 Camera (version1)",
            "4: Rabbid76 Camera (version2)",
            "o: RHS Scene Y-UP",
            "p: RHS Scene Z-UP",
        ])
        render_text(-1.0, 1.0 - 0.1, info)
        render_text(-1.0, -1.0, "{} camera is active, scene is {}".format(self.index_camera, "Y-UP" if self.yup else "Z-UP"))

        glutSwapBuffers()


if __name__ == '__main__':
    window = MyWindow(800, 600)
    window.run()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-15
    • 1970-01-01
    • 1970-01-01
    • 2019-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多