关于python:处理相机旋转的正确方法

Proper way to handle camera rotations

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

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

1
2
3
4
5
6
7
8
9
10
11
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)

1
2
3
4
5
6
7
8
9
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
    )

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

1
2
3
4
5
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)时,这些旋转函数会出现问题......此时相机的行为非常糟糕,所以我会遇到诸如:

showcase

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

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

编辑:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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()

为了运行它,你需要安装 pyopengl 和 pyglm


我建议在视图空间中绕轴旋转

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

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

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

1
2
3
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:

1
2
3
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

1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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])

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

1
2
3
4
5
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)之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)

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

1
2
3
4
5
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

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

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

我想出了以下方法:

  • 计算当前视线(los)、上向量(up)和水平轴(right)

  • 通过将上向量投影到由原始上向量和当前视线给定的平面上,使上向量垂直。这是通过 Gram-Schmidt 正交化实现的。

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

  • 围绕向上矢量旋转。 losright 围绕 up 旋转。

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

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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


    以下是该线程中提供的所有答案的小总结:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    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 ="\
    "
    .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()

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 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))
  • 利用距离得到一个向量,然后通过当前的旋转矩阵平移这个向量

    1
    2
    3
    4
        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))
  • 通过将平移向量添加到目标位置来获取新的相机眼睛位置

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

    1
    2
    3
    4
        # 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函数更新视图矩阵来尝试一下

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

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