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