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¶
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 it’s 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 verticies (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
@dataclass
class Vertex:
...
def perspective(self: Vertex,
fov: float,
aspectRatio: float,
nearZ: float,
farZ: float) -> Vertex:
# fov, field of view, is angle of y
# aspectRatio is xwidth / ywidth
top: float = -nearZ * math.tan(math.radians(fov) / 2.0)
right: float = top * aspectRatio
scaled_x: float = self.x / self.z * nearZ
scaled_y: float = self.y / self.z * nearZ
retangular_prism: Vertex = Vertex(scaled_x,
scaled_y,
self.z)
return retangular_prism.ortho(left=-right,
right=right,
bottom=-top,
top=top,
near=nearZ,
far=farZ)
def camera_space_to_ndc_space_fn(self: Vertex) -> Vertex:
return self.perspective(fov=45.0,
aspectRatio=1.0,
nearZ=-0.1,
farZ=-10000.0)
while not glfw.window_should_close(window):
...
# draw paddle 1
glColor3f(paddle1.r, paddle1.g, paddle1.b)
glBegin(GL_QUADS)
for model_space in paddle1.vertices:
world_space: Vertex = model_space.rotate_z(paddle1.rotation) \
.translate(tx=paddle1.position.x,
ty=paddle1.position.y,
tz=0.0)
# world_space: Vertex = camera_space.rotate_x(camera.rot_x) \
# .rotate_y(camera.rot_y) \
# .translate(tx=camera.position_worldspace.x,
# ty=camera.position_worldspace.y,
# tz=camera.position_worldspace.z)
camera_space: Vertex = world_space.translate(tx=-camera.position_worldspace.x,
ty=-camera.position_worldspace.y,
tz=-camera.position_worldspace.z) \
.rotate_y(-camera.rot_y) \
.rotate_x(-camera.rot_x)
ndc_space: Vertex = camera_space.camera_space_to_ndc_space_fn()
glVertex3f(ndc_space.x, ndc_space.y, ndc_space.z)
glEnd()