Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jarcode-foss/0520c41c339beffeca46ed0e3997c25c to your computer and use it in GitHub Desktop.
Save jarcode-foss/0520c41c339beffeca46ed0e3997c25c to your computer and use it in GitHub Desktop.
/*
Lightmap fragment shader
Lighting is calculated per-fragment, and checks for collisions with boxes
provided to this shader in real-time (allowing for accurate, dynamic lights).
This shader is ran once for each light, which is blended per-component (max)
with other lights.
The final lightmap is later smoothed in the processing shader.
*/
#define MAX_LIGHTS ARRAY_LIMIT
#define MAX_BOXES ARRAY_LIMIT
layout(pixel_center_integer) in vec4 gl_FragCoord;
uniform ivec2 fb; /* framebuffer geometry */
uniform ivec2 mouse; /* mouse position (is already scaled) */
uniform vec3 light_color; /* light color */
uniform ivec4 light; /* light position info (only x,y is used for spot) */
uniform ivec4 light_data; /* x: cast range, y: direction (+/-), z: radius, w: effect */
uniform int light_type; /* light type */
uniform ivec4 boxes[MAX_BOXES];
uniform int boxes_sz;
uniform float nclock; /* [0, 1) floating point value that loops every 10 seconds */
uniform float ambience; /* amount of light applied to shadowed areas */
uniform ivec2 screen_pos; /* screen position */
uniform sampler2D depth_tex; /* depth texture */
out vec4 fragment; /* output */
vec2 pos; /* position of fragment in world space */
/* returns the higher r, g, or b value from v */
#define maxcomp(v) (v.r > v.g ? (v.r > v.b ? v.r : v.b) : (v.g > v.b ? v.g : v.b))
#define maxv(r, g, b) (r > g ? (r > b ? r : b) : (g > b ? g : b))
#define PI 3.1415926535897932384626433832795
/* shadow tracing parameters for effect field (mostly for particles and other effects) */
#define SHADE_PARAMETER 255
/* (only for light bar) only trace to the first point for the given light */
#define SHADE_ONCE (SHADE_PARAMETER + 1)
/* disable shadow tracing completely for a light */
#define SHADE_NEVER (SHADE_PARAMETER + 2)
/* light_type values */
#define TYPE_SPOT 0
#define TYPE_BAR 1
/* for lines p0 and p1, return true if they intersect */
bool intersect(vec4 p0, vec4 p1) {
float d = ((p0.x - p0.z) * (p1.y - p1.w)) - ((p0.y - p0.w) * (p1.x - p1.z));
if (d == 0f) return false; /* parallel */
float a = (p0.x * p0.w) - (p0.y * p0.z);
float b = (p1.x * p1.w) - (p1.y * p1.z);
float px = ((a * (p1.x - p1.z)) - ((p0.x - p0.z) * b)) / float(d);
float py = ((a * (p1.y - p1.w)) - ((p0.y - p0.w) * b)) / float(d);
if (px > max(p0.x, p0.z) + 0.01f ||
px < min(p0.x, p0.z) - 0.01f ||
px > max(p1.x, p1.z) + 0.01f ||
px < min(p1.x, p1.z) - 0.01f)
return false;
if (py > max(p0.y, p0.w) + 0.01f ||
py < min(p0.y, p0.w) - 0.01f ||
py > max(p1.y, p1.w) + 0.01f ||
py < min(p1.y, p1.w) - 0.01f)
return false;
return true;
}
vec2 intersect_coords(vec4 p0, vec4 p1) {
float d = ((p0.x - p0.z) * (p1.y - p1.w)) - ((p0.y - p0.w) * (p1.x - p1.z));
// if (d == 0f) return vec2(0, 0); /* parallel */
float a = (p0.x * p0.w) - (p0.y * p0.z);
float b = (p1.x * p1.w) - (p1.y * p1.z);
float px = ((a * (p1.x - p1.z)) - ((p0.x - p0.z) * b)) / float(d);
float py = ((a * (p1.y - p1.w)) - ((p0.y - p0.w) * b)) / float(d);
return vec2(px, py);
}
/* returns true if the current fragment is behind 'box', relative to light (x, y) */
bool behind_box(ivec4 box, int px, int py) {
/* return false if the point in inside the box */
if (px >= box.x && px <= box.x + (box.z - 1) &&
py >= box.y && py <= box.y + (box.w - 1))
return false;
vec4 p = vec4(pos.x, pos.y, px, py);
float w = box.z - 0.02f, h = box.w - 0.02f, x = box.x, y = box.y;
return
intersect(p, vec4(x, y, x + w, y )) || /* bottom */
intersect(p, vec4(x, y + h, x + w, y + h)) || /* top */
intersect(p, vec4(x + w, y, x + w, y + h)) || /* right */
intersect(p, vec4(x, y, x, y + h)); /* left */
}
float apply_effect(int effect, int lx, int ly) {
float f_in = nclock + (float(lx % 10) / 10f) + (float(ly % 100) / 100f);
if (f_in > 1.0f) f_in -= 1.0f;
switch (effect) {
case 1: /* torch flicker */
return -0.1f * (((0.2f * sin(2f * PI * f_in)) + 0.5f)
* ((0.5f * sin(40f * PI * f_in)) + 0.5f));
case 2: /* flickering/broken florescent light */
f_in = abs((0.5f * sin(4f * PI * f_in)) + (0.5f * sin(8f * PI * f_in)));
if (f_in > 0.85f) {
return -0.25f;
}
break;
case 3: /* inverse of above */
f_in = abs((0.5f * sin(4f * PI * f_in)) + (0.5f * sin(8f * PI * f_in)));
if (f_in < 0.8f) {
return -0.25f;
}
break;
}
return 0f;
}
void main() {
/* translate into world space */
pos.x = gl_FragCoord.x + screen_pos.x;
pos.y = gl_FragCoord.y + screen_pos.y;
/* calc light from all light point */
float dx, dy, intensity, d, squared_radius;
int t, b;
ivec4 l = light;
vec3 light = vec3(0f, 0f, 0f), result = vec3(0f, 0f, 0f);
switch (light_type) {
case TYPE_SPOT:
{
/* calc light from point */
dx = l.x - pos.x;
dy = l.y - pos.y;
d = (dx * dx) + (dy * dy); /* distance squared */
squared_radius = light_data.z * light_data.z;
if (d <= squared_radius) {
bool shadow = false;
/* ignore if behind a box */
for (b = 0; b < boxes_sz; ++b) {
if (behind_box(boxes[b], l.x, l.y)) {
shadow = true;
break;
}
}
/*
using squared distance here makes the intensity 'curve' a bit, rather than
linear light, but that's fine (avoiding sqrt usage)
*/
intensity = clamp((squared_radius - d) / squared_radius, 0f, 1f);
switch (l.a) {
case SHADE_NEVER: break;
default:
intensity += apply_effect(light_data.w, l.x, l.y);
case 0:
/* ignore if behind a box */
for (b = 0; b < boxes_sz; ++b) {
if (behind_box(boxes[b], l.x, l.y)) {
shadow = true;
break;
}
}
}
result = light_color;
result *= intensity;
if (shadow) result *= ambience;
}
}
break;
case TYPE_BAR:
{
/* calc light from all light bars */
float d1x, d1y, d2x, d2y;
ivec4 ld;
vec2 ic;
ld = light_data;
ic = vec2(pos.x, pos.y);
ic = intersect_coords(l, vec4(ic, ic + vec2(l.w - l.y, -(l.z - l.x))));
/* if intersection out of bounds */
if (ic.x < min(l.x, l.z) || ic.y < min(l.y, l.w) ||
ic.x > max(l.z, l.x) || ic.y > max(l.w, l.y)) {
/* calculate distance via points l.xy, l.zw */
d1x = pos.x - l.x;
d1y = pos.y - l.y;
d2x = pos.x - l.z;
d2y = pos.y - l.w;
d = min(sqrt((d1x * d1x) + (d1y * d1y)), sqrt((d2x * d2x) + (d2y * d2y)));
} else {
/* calculate distance between fragment and intersection */
dx = pos.x - ic.x;
dy = pos.y - ic.y;
d = sqrt((dx * dx) + (dy * dy));
}
if (d <= ld.z) {
bool shadow = false, ignore = false;
/* light boundaries */
if (ld.x != 0) {
dx = l.z - l.x;
dy = l.w - l.y;
vec2 mo = vec2(l.x + (dx / 2f), l.y + (dy / 2f)); /* center of light bar */
vec2 md = normalize(vec2(dy, -dx)) * sign(ld.y); /* light bar normal */
vec2 m = mo + (md * ld.z); /* light target center */
vec2 nd = normalize(vec2(dx, dy)) * (ld.x / 2f); /* light target offset */
vec2 p1 = m + nd, p2 = m - nd; /* light boundaries */
vec4 line = vec4(mo, pos.xy); /* line to center of bar */
/* test for intersection with light boundaries p1 and p2 */
if (intersect(line, vec4(l.zw, p1.xy)) || intersect(line, vec4(l.xy, p2.xy))) {
ignore = true;
} else {
/* ignore if behind the light: acos(dot(normal, frag_to_center)) <= PI / 2 */
float dotp = dot(normalize(vec2(line.x - line.z, line.y - line.w)), md);
if (dotp > -0.001f && dotp < 1.001f)
ignore = true;
}
}
/* ignore if behind a box */
if (!ignore) {
intensity = clamp((ld.z - d) / ld.z, 0f, 1f);
switch (ld.a) {
case SHADE_ONCE:
for (b = 0; b < boxes_sz; ++b) {
/* only check the first point */
if (behind_box(boxes[b], l.x, l.y)) {
shadow = true;
break;
}
};
break;
case SHADE_NEVER: break;
default:
intensity += apply_effect(ld.a, l.x, l.y);
case 0:
{
bool sa = false, sb = false;
for (b = 0; b < boxes_sz; ++b) {
/*
Because we only check the corners of the light, the shadows here
can be imperfectly drawn if the corners of the bar are blocked.
*/
if (behind_box(boxes[b], l.x, l.y))
sa = true;
if (behind_box(boxes[b], l.z, l.w))
sb = true;
if (sa && sb) {
shadow = true;
break;
}
};
}
break;
}
result = light_color;
result *= intensity;
if (shadow) result *= ambience;
}
}
}
}
fragment = vec4(result, 1f);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment