How to render Parallax Background and Foreground Layers Completely in the Shader [Tutorial]
EDIT Oct 16 2014: Changed precision to lowp for fragment shader. Consider using this as a default for mobile, unless you really need something better. I saw a 2x speed increase on Kindle by doing so.
Hi all!
Here's a quick little diddy for anyone curious on a quick way to get a layered effect in your 2D game (note: we are still using 3D vertices):
I did a little more progress on Hoarder Monkey and I am happy to have parallax backgrounds working all in the shader and in 1 draw call per object type (static VBO, dynamic sprites, Semi-transparent VBO, semi-transparent sprites). :)
The background scrolling is all handled in the shader, and works surprisingly well. I also do some color tweaking to make things a little darker in the foreground and blue-ish in the background:
Here's the Vertex shader for parallax. Notice I just arbitrarily chose < -50 to be the furthest away, scrolling layer (right before the background). This code will render based on camera movement half the distance that the action layer, will move (action layer == z distance where player is):
new_offset = u_camera_offset.xy / 2.0;
new_offset = u_camera_offset.xy * 2.0;
Ok, now that scrolling business has been taken care of, for that oh so nice cartoon layered depth illusion. Now what about coloring. How about we color the background layers a little bit blue-er and white than the closer layers, and maybe the foreground (closest branches and such) can be a little more black?
// The Blue/white far scrolling layer
orig_color.b *= 1.90;
So there you have it. In a nutshell this is how you can do parallax scrolling backgrounds all in the shader. Of course you will still want to order your draw polygons to draw first: closest to further away, and for semi-transparents draw first: further away to closest.
Draw ALL your semi-transparents last. Even after your background! If you don't you'll get fun artifacts and it won't blend correctly. Also, don't forget to use a TextureAtlas to reduce all your draws to just a few, and VBOs for any non-moving game objects (background decorations, non-moving platforms etc).
Happy GameDev-ing!
Hi all!
Here's a quick little diddy for anyone curious on a quick way to get a layered effect in your 2D game (note: we are still using 3D vertices):
I did a little more progress on Hoarder Monkey and I am happy to have parallax backgrounds working all in the shader and in 1 draw call per object type (static VBO, dynamic sprites, Semi-transparent VBO, semi-transparent sprites). :)
The background scrolling is all handled in the shader, and works surprisingly well. I also do some color tweaking to make things a little darker in the foreground and blue-ish in the background:
Notice the closest graphics are darker |
Notice the background objects are a more blue/white hue. |
In case your curious the shader code is pretty simple (as it should be for a shader):
new_offset = u_camera_offset.xy / 2.0;
The closest palm trees and stuff will move twice as fast as the action layer, abritrarily chosen to be distance Z > 50.0 (Keep in mind, in OpenGL -Z is further away from the camera, while +Z is towards the screen!):
new_offset = u_camera_offset.xy * 2.0;
uniform mat4 u_projection_matrix; uniform float u_cutoff_alpha; uniform vec2 u_camera_offset; uniform float u_scale; attribute vec4 a_Position; attribute vec2 a_texture_coords; varying vec2 v_texture_coords; varying float v_cutoff_alpha; varying float v_zdepth; void main() { vec2 new_offset = u_camera_offset; v_texture_coords = a_texture_coords; if(a_Position.z < -50.0) { new_offset = u_camera_offset.xy / 2.0; } else if(a_Position.z > 50.0) { new_offset = u_camera_offset.xy * 2.0; } vec4 pos = u_scale* (vec4(new_offset.x, new_offset.y, 0.0, 0.0) + vec4(a_Position.x, a_Position.y, a_Position.z/2.0, a_Position.a)); gl_Position = u_projection_matrix * pos; v_cutoff_alpha = u_cutoff_alpha; v_zdepth = a_Position.z; }
Ok, now that scrolling business has been taken care of, for that oh so nice cartoon layered depth illusion. Now what about coloring. How about we color the background layers a little bit blue-er and white than the closer layers, and maybe the foreground (closest branches and such) can be a little more black?
// The Blue/white far scrolling layer
orig_color.b *= 1.90;
gl_FragColor = mix(vec4(1.0,1.0,1.0,orig_color.a), orig_color, .9);
// The closest layer (more black color)
gl_FragColor = mix(vec4(0.0,0.0,0.0,orig_color.a), orig_color, .5);
// The normal action layer (where player is etc, no color changes):
gl_FragColor = orig_color;
#ifdef GL_ES precision lowp float; #endif uniform sampler2D u_texture; varying vec2 v_texture_coords; varying float v_cutoff_alpha; varying float v_zdepth; void main() { vec4 orig_color = texture2D(u_texture, v_texture_coords); if(orig_color.a < v_cutoff_alpha) { discard; } if(v_zdepth < -50.0) { orig_color.b *= 1.90; gl_FragColor = mix(vec4(1.0,1.0,1.0,orig_color.a), orig_color, .9); } else if(v_zdepth > 50.0) { gl_FragColor = mix(vec4(0.0,0.0,0.0,orig_color.a), orig_color, .5); } else { gl_FragColor = orig_color; } }
So there you have it. In a nutshell this is how you can do parallax scrolling backgrounds all in the shader. Of course you will still want to order your draw polygons to draw first: closest to further away, and for semi-transparents draw first: further away to closest.
Draw ALL your semi-transparents last. Even after your background! If you don't you'll get fun artifacts and it won't blend correctly. Also, don't forget to use a TextureAtlas to reduce all your draws to just a few, and VBOs for any non-moving game objects (background decorations, non-moving platforms etc).
Happy GameDev-ing!
Note: I had a bug in the frag shader code and changed it to:
ReplyDeleteorig_color.b *= 1.90;
EDIT Oct 16 2014: Changed precision to lowp for fragment shader. Consider using this as a default for mobile, unless you really need something better. I saw a 2x speed increase on Kindle by doing so.
ReplyDelete