GPU Parallax Scrolling
If you are reading this article you are probably familiar with the parallax scrolling effect. You may of read an article on the internet explaining how to implement parallax scrolling by translating layers at different speeds. There is an easier and more performant way to do this. We can use a mixture of orthographic and perspective projections in our vertex shader to achieve pixel-perfect parallax scrolling without having to add a layer system to our renderer.
๐ The Perspective Camera
The perspective camera will determine the position of the sprite on the screen. The position, look-at direction and min and max distance must be the same as the orthographic camera. The field-of-view can be modified to adjust the intensity of the parallax affect.
In order to access perspective camera from vertex shader we add must add it to the uniforms binding.
// VERTEX SHADER BINDING
layout(set = 0, binding = 0) uniform Uniforms {
mat4 ortho;
// NEW!
mat4 persp;
};
The orthographic camera does not need to be changed, but you should follow best practices to avoid texture filtering artifacts. This means the width and height of the orthographic camera should be set to create a 1-to-1 mapping of the sprite texture to screen space.
See camera.rs for an example of how to generate perspective and orthographic matrices.
๐ Shifting the Orthographic Camera
Next we calculate where the sprite centre will be in normalised-device-coordinates; rendered using perspective and orthographic cameras. We calculate the distance between these two centres and shift the orthographic projection of the sprite vertex by this distance.
// VERTEX SHADER MAIN
void main() {
...
// We assume our sprites centres are always at (0.0, 0.0, 0.0)
vec4 centre = vec4(vec3(0.0), 1.0);
// Calculate p_c, the perspective projection of the centre in clip space
vec4 p_c = persp * model * centre;
// Calculate o_c, the orthographic projection of the centre in clip space
vec4 o_c = ortho * model * centre;
// Calculate o_pos, the orthographic projection of the vertex in clip space
vec4 o_pos = ortho * model * vertex;
// Convert p_c, o_c and o_pos to normalised device coordinates
vec4 o_c_ndc = o_c/o_c.w;
vec4 p_c_ndc = p_c/p_c.w;
vec4 o_pos_ndc = o_pos/o_pos.w;
// Calculate d_ndc, the distance between the perspective and orthographic
// centres in normalised device coordinates
vec4 d_ndc = p_c_ndc - o_c_ndc;
// Calculate the shifted ndc position, pos_ndc, by shifting o_pos_ndc by
// d_ndc. We are shifting the orthoghraphic projection of our sprite
// vertex to where it would be located if it were rendered with
// perspective projection
vec4 pos_ndc = o_pos_ndc + d_ndc;
// Convert back to clip space for output to the rasterizer. The rasterizer
// stage expects the vertex position in clip space
gl_Position = pos_ndc * o_pos.w;
}
Thats all there is to it. By shifting the orthographic render of the sprite, to where it should of been if it were rendered using a perspective camera, we have created the parallax scrolling effect!
๐ Try it yourself
Clone the repo
Build and run the example using cargo run
to see it in action.
You will need the Rust programming language to compile the example.
Use the left and right arrow keys to move camera and observe the parallax scrolling effect.
The scene is defined in main.rs. Feel free to move the trees around and adjust the field-of-view of the parallax camera.