3D Perspective - Demo 18

Objective

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/modelviewprojection/demo18.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/modelviewprojection/demo18.py
32@dataclass
33class Vector3D:
34    x: float  #: The x-component of the 3D Vector
35    y: float  #: The y-component of the 3D Vector
36    z: float  #: The z-component of the 3D Vector
37
38    def __add__(self, rhs: Vector3D) -> Vector3D:
39        return Vector3D(
40            x=(self.x + rhs.x), y=(self.y + rhs.y), z=(self.z + rhs.z)
41        )
42
43    def __sub__(self, rhs: Vector3D) -> Vector3D:
44        return Vector3D(
45            x=(self.x - rhs.x), y=(self.y - rhs.y), z=(self.z - rhs.z)
46        )
47
48    def __mul__(vector, scalar: float) -> Vector3D:
49        return Vector3D(
50            x=(vector.x * scalar), y=(vector.y * scalar), z=(vector.z * scalar)
51        )
52
53    def __rmul__(vector, scalar: float) -> Vector3D:
54        return vector * scalar
55
56    def __neg__(vector):
57        return -1.0 * vector
58
59
60def translate(translate_amount: Vector3D) -> InvertibleFunction:
61    def f(vector: Vector3D) -> Vector3D:
62        return vector + translate_amount
63
64    def f_inv(vector: Vector3D) -> Vector3D:
65        return vector - translate_amount
66
67    return InvertibleFunction(f, f_inv)
68
69
...
src/modelviewprojection/demo18.py
208def perspective(
209    field_of_view: float, aspect_ratio: float, near_z: float, far_z: float
210) -> Vector3D:
211    # field_of_view, field of view, is angle of y
212    # aspect_ratio is x_width / y_width
213
214    top: float = -near_z * math.tan(math.radians(field_of_view) / 2.0)
215    right: float = top * aspect_ratio
216
217    def f(vector: Vector3D) -> Vector3D:
218        scaled_x: float = vector.x / vector.z * near_z
219        scaled_y: float = vector.y / vector.z * near_z
220        rectangular_prism: Vector3D = Vector3D(scaled_x, scaled_y, vector.z)
221
222        fn = ortho(
223            left=-right,
224            right=right,
225            bottom=-top,
226            top=top,
227            near=near_z,
228            far=far_z,
229        )
230        return fn(rectangular_prism)
231
232    def f_inv(vector: Vector3D) -> Vector3D:
233        raise ValueError("Inverse_Inner Perspective not yet implement")
234
235    return InvertibleFunction(f, f_inv)
236
237
src/modelviewprojection/demo18.py
236while not glfw.window_should_close(window):
...
src/modelviewprojection/demo18.py
278    # cameraspace to NDC
279    with push_transformation(
280        perspective(
281            field_of_view=45.0, aspect_ratio=1.0, near_z=-0.1, far_z=-1000.0
282        )
283    ):
284        # world space to camera space, which is inverse of camera space to
285        # world space
286        with push_transformation(
287            inverse(
288                compose(
289                    translate(camera.position_ws),
290                    rotate_y(camera.rot_y),
291                    rotate_x(camera.rot_x),
292                )
293            )
294        ):
295            # paddle 1 space to world space
296            with push_transformation(
297                compose(translate(paddle1.position), rotate_z(paddle1.rotation))
298            ):
299                glColor3f(*astuple(paddle1.color))
300                glBegin(GL_QUADS)
301                for p1_v_ms in paddle1.vertices:
302                    paddle1_vector_ndc = fn_stack.modelspace_to_ndc_fn()(
303                        p1_v_ms
304                    )
305                    glVertex3f(
306                        paddle1_vector_ndc.x,
307                        paddle1_vector_ndc.y,
308                        paddle1_vector_ndc.z,
309                    )
310                glEnd()