3D Perspective - Demo 17

Purpose

Implement a perspective projection so that objects further away are smaller than the would be if they were close by

Demo 17

Demo 17

Frustum 1

Frustum 1

Frustum 2

Frustum 2

Frustum 3

Frustum 3

Frustum 4

Frustum 4

Frustum 5

Frustum 5

Frustum 6

Frustum 6

How to Execute

Load src/demo17/demo.py in Spyder and hit the play button

Move the Paddles using the Keyboard

Keyboard Input

Action

w

Move Left Paddle Up

s

Move Left Paddle Down

k

Move Right Paddle Down

i

Move Right Paddle Up

d

Increase Left Paddle’s Rotation

a

Decrease Left Paddle’s Rotation

l

Increase Right Paddle’s Rotation

j

Decrease Right Paddle’s Rotation

UP

Move the camera up, moving the objects down

DOWN

Move the camera down, moving the objects up

LEFT

Move the camera left, moving the objects right

RIGHT

Move the camera right, moving the objects left

q

Rotate the square around its center

e

Rotate the square around paddle 1’s center

Description

Frustum
       ------------------------------- far z
       \              |              /
        \             |             /
         \ (x,z) *----|(0,z)       /
          \      |    |           /
           \     |    |          /
            \    |    |         /
             \   |    |        /
              \  |    |       /
               \ |    |      /
                \---*-------- near z
                 |    |    /
                 |\   |   /
                 | \  |  /
                 |  \ | /
                 |   \|/
-----------------*----*-(0,0)-------------------
                (x,0)

If we draw a straight line between (x,z) and (0,0), we will have a right triangle with vertices (0,0), (0,z), and (x,z).

There also will be a similar right triangle with vertices (0,0), (0,nearZ), and whatever point the line above intersects the line at z=nearZ. Let’s call that point (projX, nearZ)

fov
because right angle and tan(theta) = tan(theta)
x / z = projX / nearZ
projX = x / z * nearZ

So use projX as the transformed x value, and keep the distance z.

                       ----------- far z
                       |          |
                       |          |
        (x / z * nearZ,z) *       |   non-linear -- the transformation of x depends on its z value
                       |          |
                       |          |
                       |          |
                       |          |
                       |          |
                       |          |
                       |          |
                       |          |
                       ------------ near z
                       \    |    /
                        \   |   /
                         \  |  /
                          \ | /
                           \|/
      ----------------------*-(0,0)-------------------

Top calculation based off of vertical field of view

                    /* top
                   / |
                  /  |
                 /   |
                /    |
               /     |
              /      |
             /       |
            /        |
           /         |
          /          |
         /           |
        /            |
 origin/             |
      / fov/2        |
z----*---------------*
     |\              |-nearZ
     | \             |
     |  \            |
     x   \           |
          \          |
           \         |
            \        |
             \       |
              \      |
               \     |
                \    |
                 \   |
                  \  |
                   \ |
                    \|

Right calculation based off of Top and aspect ration

                        top
-------------------------------------------------------
|                                                     |
|                         y                           |
|                         |                           |
|                         |                           |
|                         *----x                      | right =
|                            origin                   |   top * aspectRatio
|                                                     |
|                                                     |  aspect ratio should be the viewport's
|                                                     |    width/height, not necessarily the
-------------------------------------------------------    window's
src/demo17/demo.py
152@dataclass
153class Vertex:
...
src/demo17/demo.py
218    def perspective(self: Vertex,
219                    field_of_view: float,
220                    aspect_ratio: float,
221                    near_z: float,
222                    far_z: float) -> Vertex:
223        # field_of_view, field of view, is angle of y
224        # aspect_ratio is x_width / y_width
225
226        top: float = -near_z * math.tan(math.radians(field_of_view) / 2.0)
227        right: float = top * aspect_ratio
228
229        scaled_x: float = self.x / self.z * near_z
230        scaled_y: float = self.y / self.z * near_z
231        rectangular_prism: Vertex = Vertex(scaled_x,
232                                           scaled_y,
233                                           self.z)
234
235        return rectangular_prism.ortho(left=-right,
236                                       right=right,
237                                       bottom=-top,
238                                       top=top,
239                                       near=near_z,
240                                       far=far_z)
241
242    def camera_space_to_ndc_fn(self: Vertex) -> Vertex:
243        return self.perspective(field_of_view=45.0,
244                                aspect_ratio=1.0,
245                                near_z=-.1,
246                                far_z=-1000.0)
src/demo17/demo.py
369while not glfw.window_should_close(window):
...
src/demo17/demo.py
402    # draw paddle 1
403    glColor3f(paddle1.r, paddle1.g, paddle1.b)
404
405    glBegin(GL_QUADS)
406    for paddle1_vertex_ms in paddle1.vertices:
407        paddle1_vertex_ws: Vertex = paddle1_vertex_ms.rotate_z(
408            paddle1.rotation
409        ).translate(paddle1.position)
410        # paddle1_vertex_ws: Vertex = paddle1_vertex_cs.rotate_x(camera.rot_x) \
411        #                                              .rotate_y(camera.rot_y) \
412        #                                              .translate(camera.position_ws)
413        paddle1_vertex_cs: Vertex = (
414            paddle1_vertex_ws.translate(-camera.position_ws)
415            .rotate_y(-camera.rot_y)
416            .rotate_x(-camera.rot_x)
417        )
418        paddle1_vertex_ndc: Vertex = paddle1_vertex_cs.camera_space_to_ndc_fn()
419        glVertex3f(paddle1_vertex_ndc.x, paddle1_vertex_ndc.y, paddle1_vertex_ndc.z)
420    glEnd()