White flash
Turn a sprite white for a split second when it is hit, keeping its silhouette.
- 01GoalColor
- 02TechniqueHit flash
- 03ExecutionWhite flash
Parameters
Copy for your platform
- GLSLCopy
- GodotCopy
Node chain
Built step by step, in GLSL style, with its node names.
Built step by step, in Godot style, with its node names.
No Unreal Engine 5 version of this card yet, so the nodes show the GLSL names.
No Unity version of this card yet, so the nodes show the GLSL names.
No Blender version of this card yet, so the nodes show the GLSL names.
textureTexture2Dtexturetexturetexture UVTexture01 Sample the sprite
mixMixmixmixmix In02 Push toward the flash color
vec4(rgb, a)Output (Color, Alpha)vec4(rgb, a)vec4(rgb, a)vec4(rgb, a) In03 Keep the sprite's alpha
How it works
The preview draws an original 32 by 32 pixel mushroom with the flash at 0.7; drag “Flash amount” to 0 for the plain sprite and to 1 for the full white frame a game shows on impact.
- Sample the sprite.
texture(t_sprite, i.uv)reads the sprite’s color and alpha at this pixel. The texture is set to nearest filtering, so each screen pixel takes one whole texelOne element of a texture, the texture's version of a pixel. A sample blends nearby texels or returns the nearest one.FundamentalTexel
One element of a texture, the texture's version of a pixel. A sample blends nearby texels or returns the nearest one.
texel size = 1 / textureSize(sampler, 0)Sourcesand the pixel art stays crisp at any scale.- texture (opens in a new tab)Reference
- texelFetch (opens in a new tab)Reference
- Push toward the flash color.
mix(own.rgb, p_color, p_amount)moves every channel a fraction of the way to the flash color: nothing at 0, all the way at 1. Shown alone, this step fills the whole quad, the transparent corners too, because their color is defined (black) even where nothing is drawn. - Keep the sprite’s alpha.
vec4(flashed, own.a)puts the sprite’s own alpha back. The pass blends with ordinary alpha, so empty texels stay invisible and only the silhouette flashes.
A mix is the right operation because it reaches the flash color evenly from any starting point. Multiplying, which is what a tint or a modulate color does, scales each pixel. Below 1 it only darkens. Above 1 the light parts clip to white long before the dark ones, and pure black never changes. Adding white clips in the same uneven way.
The shader doesn’t know when the hit happened. Game code sets p_amount to 1 on the frame of the hit and brings it
back to 0 over roughly 0.1 seconds: a linear fade, 1 - elapsed / duration, or a quicker drop that lingers at the
end. Many games hold it at 1 for two or three frames first, so the flash survives a dropped frame. Because the value
belongs to one sprite, each sprite needs its own copy of the material, or every enemy flashes together.
Nearest filtering never blends neighboring texels, so the black stored in the transparent texels never reaches the
edge. With linear filtering and straight alpha, the border texels would blend toward that black and show a dark fringe;
premultiplied alphaA color stored already multiplied by its opacity. It filters and blends without dark fringes and can express additive light. Premultiplied alpha A color stored already multiplied by its opacity. It filters and blends without dark fringes and can express additive light.over: out = src + dst · (1 − src.a)
Make it in GLSL
The copied code is a complete GLSL ES 3.00 fragment shader for WebGL2, for a textured quad: the sprite you draw.
- Compile it with a vertex shader that places the quad and passes
v_uv, 0 at the sprite’s bottom-left and 1 at its top-right (plusv_position,v_normalandv_view, which a 2D vertex shader can fill with constants). - Upload the sprite as an
SRGB8_ALPHA8texture withNEARESTfiltering andCLAMP_TO_EDGEwrapping, bind it, and pointt_spriteat its unit. WebGL’s images load top row first, so either setUNPACK_FLIP_Y_WEBGLbefore the upload or flipv_uv.y. - Upload
p_colorin linear light (white is white in both) and setp_amountfrom your game loop: 1 on the frame of the hit, then down to 0 over about 0.1 seconds. - Draw with alpha blending on.
main()clamps and encodes the color to sRGB and leaves the sprite’s alpha as it is.
Make it in Godot
Set t_sprite to the same texture as the Sprite2D's own: Godot's TEXTURE can't be read inside a function, and effect() samples the sprite in one. The shader assumes HDR 2D is off (the project default) and decodes the sprite and the color itself; with HDR 2D on, make u_toLinear() and u_toSrgb() return c unchanged.
The export is a canvas_item shader for a Sprite2D.
- Select the Sprite2D. In CanvasItem > Material, create a ShaderMaterial with a new Shader and paste the code.
- Under Shader Parameters, drag the sprite’s texture into T Sprite, and leave Color white.
- Drive Amount from the script that handles damage, with a Tween:
func flash() -> void:
var flash_material := material as ShaderMaterial
create_tween().tween_method(func(v: float): flash_material.set_shader_parameter("amount", v), 1.0, 0.0, 0.12)
For a damage tint, set Color to red and lengthen the tween; for an invincibility blink, tween Amount between 0 and 0.8 a few times in a row. In VisualShader the chain is Texture2DParameter (T Sprite) → Texture2D → Mix with a ColorParameter by a FloatParameter into the Output node’s Color, with the texture’s alpha into its Alpha.
No Unreal Engine 5 tutorial for this card yet.
No Unity tutorial for this card yet.
No Blender tutorial for this card yet.
Code
#version 300 es// Hit flash → White flash, web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_amount; // Flash amount: 0.7 (0.0 to 1.0)uniform vec3 p_color; // Flash color: vec3(1.0, 1.0, 1.0), sRGB; upload it decoded to linearuniform sampler2D t_sprite; // mushroom.png: sRGB color, uploaded as SRGB8_ALPHA8 so it samples linear; nearest filter, clamp to edgein vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};vec3 u_toSrgb(vec3 c) { return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));}// Game code drives p_amount: 1 on the frame of the hit, back to 0 over a tenth of a second or so.vec4 effect(Inputs i) { vec4 own = texture(t_sprite, i.uv); vec3 flashed = mix(own.rgb, p_color, p_amount); // Only the color flashes; the sprite's own alpha keeps its silhouette, so the empty texels stay empty vec4 result = vec4(flashed, own.a); return result;}void main() { Inputs i; i.uv = v_uv; i.position = v_position; // Interpolation shortens the vectors between vertices i.normal = normalize(v_normal); i.view = normalize(v_view); i.time = u_time; i.fragCoord = gl_FragCoord.xy; i.resolution = u_resolution; vec4 frag_out = effect(i); // The output is sRGB: clamped and encoded once, here, with no tone mapping fragColor = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), frag_out.a);}// Hit flash → White flash, Godot 4. From Creative Center, MIT license.shader_type canvas_item;// Unshaded shows the effect's color as is, like the site's preview; scene lights don't touch itrender_mode unshaded, blend_mix;uniform float amount : hint_range(0.0, 1.0, 0.01) = 0.7;uniform vec3 color : source_color = vec3(1.0, 1.0, 1.0);uniform sampler2D t_sprite : source_color, filter_nearest, repeat_disable; // the sprite: the Sprite2D's own texture again (TEXTURE can't be read inside a function)struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// Godot's 2D renderer works in sRGB (HDR 2D off, the default), so the frame decodes color params and sRGB// textures to linear itself and encodes the result. With HDR 2D on, make these return c unchanged.vec3 u_toLinear(vec3 c) { return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));}vec4 u_toLinear4(vec4 c) { return vec4(u_toLinear(c.rgb), c.a);}vec3 u_toSrgb(vec3 c) { return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));}// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) { return vec2(uv.x, 1.0 - uv.y);}// Game code drives amount: 1 on the frame of the hit, back to 0 over a tenth of a second or so.vec4 effect(Inputs i) { vec4 own = u_toLinear4(texture(t_sprite, u_flipUv(i.uv))); vec3 flashed = mix(own.rgb, u_toLinear(color), amount); // Only the color flashes; the sprite's own alpha keeps its silhouette, so the empty texels stay empty vec4 result = vec4(flashed, own.a); return result;}void fragment() { Inputs i; // UV starts at the top-left; the effect expects GLSL's bottom-left i.uv = vec2(UV.x, 1.0 - UV.y); // A flat quad facing the viewer, the same as the site's 2D preview i.position = vec3(i.uv * 2.0 - 1.0, 0.0); i.normal = vec3(0.0, 0.0, 1.0); i.view = vec3(0.0, 0.0, 1.0); i.time = TIME; // FRAGCOORD's origin is the top-left too i.resolution = 1.0 / SCREEN_PIXEL_SIZE; i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y); vec4 frag_out = effect(i); COLOR = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), frag_out.a);}No Unreal Engine 5 code for this card yet.
No Unity code for this card yet.
No Blender code for this card yet.