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

       ------------------------------- 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)

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
149@dataclass
150class Vertex:
...
src/demo17/demo.py
215    def perspective(self: Vertex,
216                    fov: float,
217                    aspectRatio: float,
218                    nearZ: float,
219                    farZ: float) -> Vertex:
220        # fov, field of view, is angle of y
221        # aspectRatio is xwidth / ywidth
222
223        top: float = -nearZ * math.tan(math.radians(fov) / 2.0)
224        right: float = top * aspectRatio
225
226        scaled_x: float = self.x / self.z * nearZ
227        scaled_y: float = self.y / self.z * nearZ
228        retangular_prism: Vertex = Vertex(scaled_x,
229                                          scaled_y,
230                                          self.z)
231
232        return retangular_prism.ortho(left=-right,
233                                      right=right,
234                                      bottom=-top,
235                                      top=top,
236                                      near=nearZ,
237                                      far=farZ)
238
239    def camera_space_to_ndc_space_fn(self: Vertex) -> Vertex:
240        return self.perspective(fov=45.0,
241                                aspectRatio=1.0,
242                                nearZ=-.1,
243                                farZ=-1000.0)
src/demo17/demo.py
368while not glfw.window_should_close(window):
...
src/demo17/demo.py
400    # draw paddle 1
401    glColor3f(paddle1.r, paddle1.g, paddle1.b)
402
403    glBegin(GL_QUADS)
404    for paddle1_vertex_in_model_space in paddle1.vertices:
405        paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.rotate_z(paddle1.rotation) \
406                                         .translate(paddle1.position)
407        # paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_camera_space.rotate_x(camera.rot_x) \
408        #                                   .rotate_y(camera.rot_y) \
409        #                                   .translate(camera.position_worldspace)
410        paddle1_vertex_in_camera_space: Vertex = paddle1_vertex_in_world_space.translate(-camera.position_worldspace) \
411                                                                              .rotate_y(-camera.rot_y) \
412                                                                              .rotate_x(-camera.rot_x)
413        paddle1_vertex_in_ndc_space: Vertex = paddle1_vertex_in_camera_space.camera_space_to_ndc_space_fn()
414        glVertex3f(paddle1_vertex_in_ndc_space.x, paddle1_vertex_in_ndc_space.y, paddle1_vertex_in_ndc_space.z)
415    glEnd()