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

On Linux or on MacOS, in a shell, type “python src/demo17/demo.py”. On Windows, in a command prompt, type “python src\demo17\demo.py”.

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_space_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
375while not glfw.window_should_close(window):
...
src/demo17/demo.py
417    # draw paddle 1
418    glColor3f(paddle1.r, paddle1.g, paddle1.b)
419
420    glBegin(GL_QUADS)
421    for paddle1_vertex_in_model_space in paddle1.vertices:
422        paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.rotate_z(paddle1.rotation) \
423                                         .translate(paddle1.position)
424        # paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_camera_space.rotate_x(camera.rot_x) \
425        #                                   .rotate_y(camera.rot_y) \
426        #                                   .translate(camera.position_worldspace)
427        paddle1_vertex_in_camera_space: Vertex = paddle1_vertex_in_world_space.translate(-camera.position_worldspace) \
428                                                                              .rotate_y(-camera.rot_y) \
429                                                                              .rotate_x(-camera.rot_x)
430        paddle1_vertex_in_ndc_space: Vertex = paddle1_vertex_in_camera_space.camera_space_to_ndc_space_fn()
431        glVertex3f(paddle1_vertex_in_ndc_space.x, paddle1_vertex_in_ndc_space.y, paddle1_vertex_in_ndc_space.z)
432    glEnd()