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¶

Frustum 1¶

Frustum 2¶

Frustum 3¶

Frustum 4¶

Frustum 5¶

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¶
------------------------------- 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
152@dataclass
153class Vertex:
...
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)
369while not glfw.window_should_close(window):
...
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()