Skip to content

Instantly share code, notes, and snippets.

@nahkd123
Created May 19, 2024 18:24
Show Gist options
  • Save nahkd123/87b52141ad3eb3e3e660052a6630f531 to your computer and use it in GitHub Desktop.
Save nahkd123/87b52141ad3eb3e3e660052a6630f531 to your computer and use it in GitHub Desktop.
2D Sprite with Normal map

2D Sprite + Normal map

Get the nice looking 2D sprite with some 3D lightning effects on it through the power of normal maps!

image

To try it, go to https://glsl.app/#~c4wr7h4l5n7i040yt4r29vp0 Make sure to upload the assets below! Or you can copy the fragment shader below.

Assets

Base color map Normal map Emission map
base color map normal map emission map
u_textures[0] u_textures[1] u_textures[2]

All assets are created in Krita by me (@nahkd123) and licensed under CC 4.0 BY-SA-NC.

Links

// This fragment shader is licensed under CC0 1.0 Universal. No rights reserved.
#version 300 es
precision highp float;
precision highp sampler2D;
in vec2 uv;
out vec4 out_color;
uniform vec2 u_resolution;
uniform float u_time;
uniform vec4 u_mouse;
uniform sampler2D u_textures[16];
// Texture 0: Base color (use grayscale color for custom character color)
// Texture 1: Normal map
// Texture 2: Emission map
#define BASE_EMISSION 0.6
#define EMISSION_STRENGTH 1.2
#define DYNAMIC_COLOR vec4(1.000, 0.518, 0.000, 1.000)
#define SMOOTH_TO_CEL 20000.0
#define CEL_BRACKET_1 0.1
#define CEL_BRACKET_2 0.4
#define CEL_BRACKET_3 0.6
#define CEL_LIGHT_PER_BRACKET 0.1
#define SHOW_LIGHTBULB 1.0
vec4 toOklab(vec4 c);
vec4 toSrgb(vec4 c);
void main(){
vec2 st = uv * 2.0 - 1.0;
vec4 base = texture(u_textures[0], uv);
vec4 normal = texture(u_textures[1], uv);
vec4 emission = texture(u_textures[2], uv);
// Colorize base and emission based on dynamic color for grayscale fragments
vec4 baseLab = toOklab(base);
if (abs(baseLab.y) < 0.001 && abs(baseLab.z) < 0.001) {
base *= DYNAMIC_COLOR;
emission *= DYNAMIC_COLOR;
}
// Increase emission strength
emission = toOklab(emission);
emission.x *= EMISSION_STRENGTH;
emission = toSrgb(emission);
// Calculate light from normal map
vec2 xy = (normal.xy - 0.5) * 2.0;
vec2 lightXy = normalize(vec2(cos(u_time * 1.0), sin(u_time * 1.0)));
float light = clamp(dot(xy.xy, lightXy.xy), -1.0, 1.0);
// Do the light
out_color = base * BASE_EMISSION;
out_color += vec4(CEL_LIGHT_PER_BRACKET) * clamp((light - CEL_BRACKET_1) * SMOOTH_TO_CEL, 0.0, 1.0) * normal.a;
out_color += vec4(CEL_LIGHT_PER_BRACKET) * clamp((light - CEL_BRACKET_2) * SMOOTH_TO_CEL, 0.0, 1.0) * normal.a;
out_color += vec4(CEL_LIGHT_PER_BRACKET) * clamp((light - CEL_BRACKET_3) * SMOOTH_TO_CEL, 0.0, 1.0) * normal.a;
out_color *= base.a;
out_color = mix(out_color, emission, emission.a);
out_color += smoothstep(0.13, 0.1, distance(st, lightXy)) * vec4(1) * SHOW_LIGHTBULB;
out_color.a = out_color.a > 0.0 ? 1.0 : 0.0;
}
// Oklab
vec4 toOklab(vec4 c) {
float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
float l_ = pow(l, 1. / 3.);
float m_ = pow(m, 1. / 3.);
float s_ = pow(s, 1. / 3.);
return vec4(
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
c.a
);
}
vec4 toSrgb(vec4 c) {
float l_ = c.r + 0.3963377774f * c.g + 0.2158037573f * c.b;
float m_ = c.r - 0.1055613458f * c.g - 0.0638541728f * c.b;
float s_ = c.r - 0.0894841775f * c.g - 1.2914855480f * c.b;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
return vec4(
+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
c.a
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment