Skip to content
Color · Hit flashBeginner

White flash

Turn a sprite white for a split second when it is hit, keeping its silhouette.

UVTexture2D / sprite
  1. 01GoalColor
  2. 02TechniqueHit flash
  3. 03ExecutionWhite flash
Sample sprite

Parameters

0.70
#ffffff

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.

  1. textureTexture2Dtexturetexturetexture
    UV
    Texture

    01 Sample the sprite

  2. mixMixmixmixmix

    02 Push toward the flash color

  3. vec4(rgb, a)Output (Color, Alpha)vec4(rgb, a)vec4(rgb, a)vec4(rgb, a)

    03 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.

  1. 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 texel and the pixel art stays crisp at any scale.
  2. 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.
  3. 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 alpha or padding the color outward avoids it.

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.

  1. 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 (plus v_position, v_normal and v_view, which a 2D vertex shader can fill with constants).
  2. Upload the sprite as an SRGB8_ALPHA8 texture with NEAREST filtering and CLAMP_TO_EDGE wrapping, bind it, and point t_sprite at its unit. WebGL’s images load top row first, so either set UNPACK_FLIP_Y_WEBGL before the upload or flip v_uv.y.
  3. Upload p_color in linear light (white is white in both) and set p_amount from your game loop: 1 on the frame of the hit, then down to 0 over about 0.1 seconds.
  4. 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.

  1. Select the Sprite2D. In CanvasItem > Material, create a ShaderMaterial with a new Shader and paste the code.
  2. Under Shader Parameters, drag the sprite’s texture into T Sprite, and leave Color white.
  3. 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

white-flash.fragGLSL
#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);}
white-flash.gdshaderGodot shader
// 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.

Further reading