--- /dev/null
+// === combine ===
+@vs vs_combine
+const vec2 positions[4] = { vec2(-1, 1), vec2(1, 1), vec2(1, -1), vec2(-1, -1) };
+const vec2 uvs[4] = { vec2(0, 1), vec2(1, 1), vec2(1, 0), vec2(0, 0) };
+const int indices[6] = { 0, 1, 2, 0, 2, 3 };
+
+layout(binding=0) uniform Game
+{
+ vec2 screen;
+ vec2 cam;
+};
+
+out vec2 uv;
+
+void main() {
+ int idx = indices[gl_VertexIndex];
+ vec2 pos = positions[idx];
+ uv = uvs[idx];
+
+ gl_Position = vec4(pos, 1, 1);
+}
+@end
+
+@fs fs_combine
+layout(binding=0) uniform texture2D tex_tex;
+layout(binding=1) uniform texture2D tex_shadow;
+layout(binding=2) uniform texture2D tex_light;
+layout(binding=0) uniform sampler smp;
+
+in vec2 uv;
+out vec4 frag_color;
+
+
+void main() {
+ vec4 tb = vec4(0.8,0.3,0.3,1);
+ if (mod(floor(uv.x*10), 2) != mod(floor(uv.y*10),2)) {
+ tb = vec4(0.3,0.3,0.8,1);
+ }
+ vec4 tt = texture(sampler2D(tex_tex, smp), uv);
+ vec4 ts = texture(sampler2D(tex_shadow, smp), uv);
+ vec4 tl = texture(sampler2D(tex_light, smp), uv);
+
+ vec3 l = tt.a * (tt.rgb * tl.rgb) + (1-tt.a) * (tl.rgb * tl.a);
+ l = ts.a * (l * ts.rgb) + (1-ts.a) * (l);
+ frag_color = vec4(l, 1);
+
+ vec3 a = (tb.rgb + tl.rgb) * tt.rgb * ts.rgb; // + tl.rgb * tl.a;
+ frag_color = vec4(a, 1);
+
+
+ vec3 bg = tb.rgb * tl.rgb;
+ vec3 bg_s = bg * (1-ts.a) + bg * ts.rgb * 0.5 * ts.a;
+ vec3 t = tt.rgb * tl.rgb;
+ vec3 t_s = t * (1-ts.a) + t * ts.rgb * 0.5 * ts.a;
+ vec3 bg_s_t_s = bg_s * (1-tt.a) + t_s * tt.a;
+ frag_color = vec4(bg_s_t_s, 1);
+
+ // frag_color = vec4(tl);
+}
+@end
+
+@program combine vs_combine fs_combine
+
+
+// === tex ===
+@vs vs_tex
+const vec2 positions[4] = { vec2(0, 1), vec2(1, 1), vec2(1, 0), vec2(0, 0) };
+const vec2 uvs[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) };
+const int indices[6] = { 0, 1, 2, 0, 2, 3 };
+
+layout(binding=0) uniform Game
+{
+ vec2 screen;
+ vec2 cam;
+};
+
+in vec2 inst_pos;
+in ivec2 inst_size;
+out vec2 uv;
+
+void main() {
+ int idx = indices[gl_VertexIndex];
+ vec2 pos = positions[idx];
+ uv = uvs[idx];
+
+ vec2 inst_pos_abs = inst_pos + inst_size * pos - cam - screen / 2;
+ gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1);
+}
+@end
+
+@fs fs_tex
+layout(binding=0) uniform texture2D tex;
+layout(binding=0) uniform sampler smp;
+
+in vec2 uv;
+out vec4 frag_color;
+
+void main() {
+ vec4 tex_color = texture(sampler2D(tex, smp), uv);
+ if (tex_color.a < 1)
+ discard;
+ frag_color = tex_color;
+}
+@end
+
+@program tex vs_tex fs_tex
+
+
+// === shadows ===
+@vs vs_shadow
+const vec2 positions[3] = { vec2(0, 1), vec2(1, 1), vec2(0.5, 0) };
+const vec2 uvs[3] = { vec2(0, 0), vec2(1, 0), vec2(0.5, 1) };
+
+layout(binding=0) uniform Game
+{
+ vec2 screen;
+ vec2 cam;
+};
+
+layout(binding=1) uniform Light
+{
+ vec3 color;
+ vec2 pos;
+ float reach;
+ float radius_glow;
+ float intensity;
+ float shadow_brightness;
+} light;
+
+in vec2 inst_pos;
+in ivec2 inst_size;
+out vec2 uv;
+out vec3 light_color;
+out float shadow_brightness;
+
+// https://gist.github.com/yiwenl/3f804e80d0930e34a0b33359259b556c
+vec2 rotate(vec2 v, float a) {
+ float s = sin(a);
+ float c = cos(a);
+ mat2 m = mat2(c, s, -s, c);
+ return m * v;
+}
+
+void main() {
+ int idx = gl_VertexIndex;
+ vec2 pos = positions[idx];
+ uv = uvs[idx];
+ light_color = light.color;
+ shadow_brightness = light.shadow_brightness;
+
+ if (idx == 2) { // keep pos for base
+ vec2 inst_pos_abs = inst_pos + inst_size * pos - cam - screen / 2;
+ gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1);
+ }
+ else {
+ vec2 anchor = vec2(0.5, 0);
+ vec2 anchor_abs = inst_pos + inst_size * anchor - cam - screen / 2;
+ vec2 light_pos_abs = light.pos - cam - screen / 2;
+ vec2 dir = anchor_abs - light_pos_abs;
+
+ vec2 left_right[] = {
+ rotate(dir, 1.5708),
+ rotate(dir, -1.5708),
+ };
+
+ float light_radius_factor = 1.5 / log(light.reach);
+
+ vec2 inst_pos_abs = anchor_abs + dir + left_right[idx] * light_radius_factor;
+ gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1);
+ }
+}
+@end
+
+@fs fs_shadow
+layout(binding=0) uniform texture2D tex;
+layout(binding=0) uniform sampler smp;
+
+in vec2 uv;
+in vec3 light_color;
+in float shadow_brightness;
+out vec4 frag_color;
+
+vec3 brighten(vec3 c, float f) {
+ vec3 d = vec3(1) - c;
+ return c + d * f;
+}
+
+vec4 blur(float radius, float resolution) {
+ float blur = radius/resolution;
+
+ float hstep = 1;
+ float vstep = 1;
+
+ vec4 sum = vec4(0.0);
+
+ sum += texture(sampler2D(tex, smp), vec2(uv.x - 4.0*blur*hstep, uv.y - 4.0*blur*vstep)) * 0.0162162162;
+ sum += texture(sampler2D(tex, smp), vec2(uv.x - 3.0*blur*hstep, uv.y - 3.0*blur*vstep)) * 0.0540540541;
+ sum += texture(sampler2D(tex, smp), vec2(uv.x - 2.0*blur*hstep, uv.y - 2.0*blur*vstep)) * 0.1216216216;
+ sum += texture(sampler2D(tex, smp), vec2(uv.x - 1.0*blur*hstep, uv.y - 1.0*blur*vstep)) * 0.1945945946;
+
+ sum += texture(sampler2D(tex, smp), vec2(uv.x, uv.y)) * 0.2270270270;
+
+ sum += texture(sampler2D(tex, smp), vec2(uv.x + 1.0*blur*hstep, uv.y + 1.0*blur*vstep)) * 0.1945945946;
+ sum += texture(sampler2D(tex, smp), vec2(uv.x + 2.0*blur*hstep, uv.y + 2.0*blur*vstep)) * 0.1216216216;
+ sum += texture(sampler2D(tex, smp), vec2(uv.x + 3.0*blur*hstep, uv.y + 3.0*blur*vstep)) * 0.0540540541;
+ sum += texture(sampler2D(tex, smp), vec2(uv.x + 4.0*blur*hstep, uv.y + 4.0*blur*vstep)) * 0.0162162162;
+
+ return sum;
+}
+
+void main() {
+ vec4 tex_color = blur(10, 1);
+ tex_color = texture(sampler2D(tex, smp), uv);
+
+ vec3 brighter = brighten(light_color, shadow_brightness);
+ frag_color = vec4(brighter * tex_color.a + vec3(1) * (1-tex_color.a), tex_color.a);
+}
+@end
+
+@program shadow vs_shadow fs_shadow
+
+
+// === lights ===
+@vs vs_light
+const vec2 positions[4] = { vec2(-0.5, 0.5), vec2(0.5, 0.5), vec2(0.5, -0.5), vec2(-0.5, -0.5) };
+const int indices[6] = { 0, 1, 2, 0, 2, 3 };
+
+layout(binding=0) uniform Game
+{
+ vec2 screen;
+ vec2 cam;
+};
+
+layout(binding=1) uniform Light
+{
+ vec3 color;
+ vec2 pos;
+ float reach;
+ float radius_glow;
+ float intensity;
+ float shadow_brightness;
+} light;
+
+out vec3 light_col;
+out vec2 light_pos;
+out float light_rea;
+out float light_glo;
+out float light_intensity;
+out vec2 fragpos;
+
+void main() {
+ int idx = indices[gl_VertexIndex];
+ vec2 pos = positions[idx];
+
+ vec2 inst_pos_ap = light.pos + light.reach * 2 * pos;
+ vec2 inst_pos_rp = inst_pos_ap - cam - screen / 2;
+ vec2 inst_pos_rw = inst_pos_rp / screen * 2;
+ gl_Position = vec4(inst_pos_rw, 1, 1);
+
+ light_col = light.color;
+ light_pos = light.pos;
+ light_rea = light.reach;
+ light_glo = light.radius_glow;
+ light_intensity = light.intensity;
+ fragpos = inst_pos_ap;
+}
+@end
+
+@fs fs_light
+layout(binding=0) uniform Game
+{
+ vec2 screen;
+ vec2 cam;
+};
+
+in vec3 light_col;
+in vec2 light_pos;
+in float light_rea;
+in float light_glo;
+in float light_intensity;
+in vec2 fragpos;
+out vec4 frag_color;
+
+float dist(vec2 a, vec2 b) {
+ float d1 = a.x - b.x;
+ float d2 = a.y - b.y;
+ return sqrt(d1*d1 + d2*d2);
+}
+
+void main() {
+ // vec2 frag_coord_abs = gl_FragCoord.xy * v_screen + v_cam + v_screen / 2;
+ // float factor = dist(light_pos, frag_coord_abs) / light_rad;
+ float factor_rea = 1 - (dist(light_pos, fragpos) / light_rea);
+ float factor_glo = 1 - (dist(light_pos, fragpos) / light_glo);
+ if (factor_rea > 1.0) discard;
+ frag_color = vec4(light_col.rgb * factor_rea, factor_glo * light_intensity);
+ // frag_color = vec4(vec3(mod(factor, 1.0)) * light_col, factor);
+}
+@end
+
+@program light vs_light fs_light