I am trying to project a photo with some angle.
If the photo was taken when the camera was looking straight ahead, then the camera angles (yaw, pitch, roll) are all zero.
Now let's say that the camera was looking somewhat upwards, let's say with pitch=1 radians, then the photo is actually capturing a trapezoid and not a rectangle:
Now let's get to the code - this is a simple program that projects a photo with no angles, using moderngl-window:
import moderngl
import moderngl_window
import numpy as np
from PIL import Image
class Pygame(moderngl_window.WindowConfig):
window_size = 1280, 720
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.program = self.ctx.program(
vertex_shader="""
#version 330
in vec2 vertex_xy;
in vec2 vertex_uv;
uniform mat4 model;
out vec2 fragment_uv;
void main() {
vec4 p = vec4(vertex_xy, 0.0, 1.0);
gl_Position = model * p;
fragment_uv = vertex_uv;
}
""",
fragment_shader="""
#version 330
in vec2 fragment_uv;
uniform sampler2D texture0;
out vec4 fragment_color;
void main() {
fragment_color = texture(texture0, fragment_uv);
}
"""
)
self.program['model'].write(bytes(np.eye(4, dtype=np.float32)))
self.program['texture0'].value = 0
self.vertex_array = self.init_vertex_array(self.ctx, self.program)
image = Image.open('test.jpg').transpose(Image.FLIP_TOP_BOTTOM)
self.texture = self.ctx.texture(image.size, 3, image.tobytes())
self.texture.use()
def render(self, time, frametime):
self.ctx.clear()
self.vertex_array.render()
def init_vertex_array(self, context: moderngl.Context, program: moderngl.Program) -> moderngl.VertexArray:
vertices_xy = self.get_vertices_for_quad_2d(size=(2.0, 2.0), bottom_left_corner=(-1.0, -1.0))
vertex_buffer_xy = context.buffer(vertices_xy.tobytes())
vertices_uv = self.get_vertices_for_quad_2d(size=(1.0, 1.0), bottom_left_corner=(0.0, 0.0))
vertex_buffer_uv = context.buffer(vertices_uv.tobytes())
vertex_array = context.vertex_array(program, [(vertex_buffer_xy, "2f", "vertex_xy"),
(vertex_buffer_uv, "2f", "vertex_uv")])
return vertex_array
def get_vertices_for_quad_2d(self, size=(2.0, 2.0), bottom_left_corner=(-1.0, -1.0)) -> np.array:
# A quad is composed of 2 triangles: https://en.wikipedia.org/wiki/Polygon_mesh
w, h = size
x_bl, y_bl = bottom_left_corner
vertices = np.array([x_bl, y_bl + h,
x_bl, y_bl,
x_bl + w, y_bl,
x_bl, y_bl + h,
x_bl + w, y_bl,
x_bl + w, y_bl + h], dtype=np.float32)
return vertices
if __name__ == '__main__':
moderngl_window.run_window_config(Pygame, args=('--window', 'glfw'))
When you run this program you will see this window:
Now, if we edit the render function to add an angle:
def render(self, time, frametime):
pitch_rad = 1
rotate_around_y_pitch = np.array([[np.cos(pitch_rad), 0, np.sin(pitch_rad), 0],
[0, 1, 0, 0],
[-np.sin(pitch_rad), 0, np.cos(pitch_rad), 0],
[0, 0, 0, 1]], dtype=np.float32)
self.program['model'].write(bytes(rotate_around_y_pitch))
self.ctx.clear()
self.vertex_array.render()
Then the projected photo will still be rectangular (just with a change in aspect ratio) and not a trapezoid.
What am I missing?



Many thanks to @Grimmy who supplied the missing details:
I should be using a
projectionmatrixI should position the object far from the camera
The complete working code: