Stretch cube to match aspect ratio

Description

While our cubes have unit length on each axis, when they're rendered they may appear more rectangular.

OpenGL's clipping space ranges from -1.0 to 1.0 on the X and Y axis, but it has to then map that to screen space coordinates of the window. If the window isn't a square, it has to stretch the clipping space to fit. Most windows/monitors are wider than tall, so the cubes will often be stretched along the X axis.

To compensate for this, we can stretch everything along either the X or Y axis when we transform from the world space to the clipping space. Then when OpenGL transforms from clipping space to screen space, it squeezes it back to the desired shape.

The amount that we need to stretch depends on the dimensions of the window, specifically how much wider it is than tall. This is referred to the aspect ratio. We can pass in the aspect ratio as a uniform value which will then be added to the transform matrix.

It's possible that this value can change if the window is resized, so we can set this value whenever the window requests a redraw.

Screenshot

Commands

git clone git@github.com:atsheehan/iridium
cd iridium
git checkout a88ff950c4518c7c397b98a9e901a7b7ca4be83f
cargo run --release

Code Changes

Modified shaders/cube.vertGitHub

@@ -4,6 +4,7 @@
44 const float FAR = 10000.0;
55
66 uniform vec3 position;
7+ uniform float aspect_ratio = 1.0;
78
89 out vec3 vertex_color;
910
@@ -82,7 +83,7 @@
8283
8384 mat4 world_to_clip_transform =
8485 mat4(1.0, 0.0, 0.0, 0.0,
85- 0.0, 1.0, 0.0, 0.0,
86+ 0.0, aspect_ratio, 0.0, 0.0,
8687 0.0, 0.0, (-NEAR - FAR) / (NEAR - FAR), 2.0 * FAR * NEAR / (NEAR - FAR),
8788 0.0, 0.0, 1.0, 0.0);
8889
@@ -4,6 +4,7 @@
4 const float FAR = 10000.0;
5
6 uniform vec3 position;
 
7
8 out vec3 vertex_color;
9
@@ -82,7 +83,7 @@
82
83 mat4 world_to_clip_transform =
84 mat4(1.0, 0.0, 0.0, 0.0,
85- 0.0, 1.0, 0.0, 0.0,
86 0.0, 0.0, (-NEAR - FAR) / (NEAR - FAR), 2.0 * FAR * NEAR / (NEAR - FAR),
87 0.0, 0.0, 1.0, 0.0);
88
@@ -4,6 +4,7 @@
4 const float FAR = 10000.0;
5
6 uniform vec3 position;
7+ uniform float aspect_ratio = 1.0;
8
9 out vec3 vertex_color;
10
@@ -82,7 +83,7 @@
83
84 mat4 world_to_clip_transform =
85 mat4(1.0, 0.0, 0.0, 0.0,
86+ 0.0, aspect_ratio, 0.0, 0.0,
87 0.0, 0.0, (-NEAR - FAR) / (NEAR - FAR), 2.0 * FAR * NEAR / (NEAR - FAR),
88 0.0, 0.0, 1.0, 0.0);
89

Modified src/main.rsGitHub

@@ -24,6 +24,7 @@
2424 event: WindowEvent::RedrawRequested,
2525 window_id,
2626 } if window_id == renderer.window_id() => {
27+ renderer.set_viewport();
2728 renderer.clear();
2829 renderer.draw_cube(&Vec3(2.0, 3.0, 6.0));
2930 renderer.draw_cube(&Vec3(-4.0, 0.0, 6.0));
@@ -24,6 +24,7 @@
24 event: WindowEvent::RedrawRequested,
25 window_id,
26 } if window_id == renderer.window_id() => {
 
27 renderer.clear();
28 renderer.draw_cube(&Vec3(2.0, 3.0, 6.0));
29 renderer.draw_cube(&Vec3(-4.0, 0.0, 6.0));
@@ -24,6 +24,7 @@
24 event: WindowEvent::RedrawRequested,
25 window_id,
26 } if window_id == renderer.window_id() => {
27+ renderer.set_viewport();
28 renderer.clear();
29 renderer.draw_cube(&Vec3(2.0, 3.0, 6.0));
30 renderer.draw_cube(&Vec3(-4.0, 0.0, 6.0));

Modified src/render.rsGitHub

@@ -115,6 +115,17 @@
115115 pub(crate) fn present(&mut self) {
116116 self.surface.swap_buffers(&self.context).unwrap();
117117 }
118+
119+ pub(crate) fn set_viewport(&mut self) {
120+ let window_size = self.window.inner_size();
121+ let aspect_ratio = window_size.width as f32 / window_size.height as f32;
122+ self.cube_program
123+ .set_uniform_f32("aspect_ratio", &aspect_ratio);
124+
125+ unsafe {
126+ gl::Viewport(0, 0, window_size.width as i32, window_size.height as i32);
127+ }
128+ }
118129 }
119130
120131 struct ProgramId(GLuint);
@@ -180,12 +191,21 @@
180191 fn set_uniform_vec3(&mut self, name: &str, value: &Vec3) {
181192 let Vec3(x, y, z) = *value;
182193
183- let cstr_name = CString::new(name).unwrap();
184- let location = unsafe { gl::GetUniformLocation(self.gl_id(), cstr_name.as_ptr()) };
185194 unsafe {
186- gl::Uniform3f(location, x, y, z);
195+ gl::Uniform3f(self.uniform_location(name), x, y, z);
196+ }
197+ }
198+
199+ fn set_uniform_f32(&mut self, name: &str, value: &f32) {
200+ unsafe {
201+ gl::Uniform1f(self.uniform_location(name), *value);
187202 }
188203 }
204+
205+ fn uniform_location(&self, name: &str) -> GLint {
206+ let cstr_name = CString::new(name).unwrap();
207+ unsafe { gl::GetUniformLocation(self.gl_id(), cstr_name.as_ptr()) }
208+ }
189209 }
190210
191211 struct ShaderId(GLuint);
@@ -115,6 +115,17 @@
115 pub(crate) fn present(&mut self) {
116 self.surface.swap_buffers(&self.context).unwrap();
117 }
 
 
 
 
 
 
 
 
 
 
 
118 }
119
120 struct ProgramId(GLuint);
@@ -180,12 +191,21 @@
180 fn set_uniform_vec3(&mut self, name: &str, value: &Vec3) {
181 let Vec3(x, y, z) = *value;
182
183- let cstr_name = CString::new(name).unwrap();
184- let location = unsafe { gl::GetUniformLocation(self.gl_id(), cstr_name.as_ptr()) };
185 unsafe {
186- gl::Uniform3f(location, x, y, z);
 
 
 
 
 
 
187 }
188 }
 
 
 
 
 
189 }
190
191 struct ShaderId(GLuint);
@@ -115,6 +115,17 @@
115 pub(crate) fn present(&mut self) {
116 self.surface.swap_buffers(&self.context).unwrap();
117 }
118+
119+ pub(crate) fn set_viewport(&mut self) {
120+ let window_size = self.window.inner_size();
121+ let aspect_ratio = window_size.width as f32 / window_size.height as f32;
122+ self.cube_program
123+ .set_uniform_f32("aspect_ratio", &aspect_ratio);
124+
125+ unsafe {
126+ gl::Viewport(0, 0, window_size.width as i32, window_size.height as i32);
127+ }
128+ }
129 }
130
131 struct ProgramId(GLuint);
@@ -180,12 +191,21 @@
191 fn set_uniform_vec3(&mut self, name: &str, value: &Vec3) {
192 let Vec3(x, y, z) = *value;
193
 
 
194 unsafe {
195+ gl::Uniform3f(self.uniform_location(name), x, y, z);
196+ }
197+ }
198+
199+ fn set_uniform_f32(&mut self, name: &str, value: &f32) {
200+ unsafe {
201+ gl::Uniform1f(self.uniform_location(name), *value);
202 }
203 }
204+
205+ fn uniform_location(&self, name: &str) -> GLint {
206+ let cstr_name = CString::new(name).unwrap();
207+ unsafe { gl::GetUniformLocation(self.gl_id(), cstr_name.as_ptr()) }
208+ }
209 }
210
211 struct ShaderId(GLuint);