Skip to content

Instantly share code, notes, and snippets.

@mds2
Last active November 11, 2021 00:08
Show Gist options
  • Save mds2/740b93aea480069b81daa97d71283419 to your computer and use it in GitHub Desktop.
Save mds2/740b93aea480069b81daa97d71283419 to your computer and use it in GitHub Desktop.
shader video effect for zoom (on linux)
# run this one *after* running setup_virtual_webcam.sh
# it will require a variety of gstreamer plugins
# it will also take over your main webcam.
# Kill the process(es) this launches to regain control of your main webcam
gst-launch-1.0 --gst-debug -v v4l2src device=/dev/video0 ! video/x-raw,width=640,height=360 ! videoconvert ! video/x-raw,format=RGBA ! glupload ! glshader fragment="\"`cat myshader.frag`\"" ! gldownload ! video/x-raw,width=640,height=360 ! queue ! videoconvert ! video/x-raw,format=I420,framerate=30/1 ! v4l2sink device=/dev/video10
#version 100
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texcoord;
uniform sampler2D tex;
uniform float time;
uniform float width;
uniform float height;
#define MAX_DIST 50.0
#define INTEGRATE_UNBOUNDED_STUFF 1
// set to 0 to make the density of the glowy stuff 1/(distance^2)
// by default it's 1/distance, which means the total glow contribution grows with MAX_DIST
// I think it looks better with 1/distance
// but, hey, neither of these functions come from 'physics'
// (or, if they do, it is only by coincidence)
//
// p.s. looks really nice when MAX_DIST is huge (thanks @CLPB for pointing this out)
const vec3 light = vec3(0.48, 0.64, -0.6);
vec3 ball1;
vec3 ball2;
vec3 ball3;
vec3 radii;
const float curve_rad = 0.1;
const mat3 color_map = 0.25 * mat3(1.0, 1.25, 1.0,
0.7, 0.5, 1.0,
0.0, 0.1, 1.0);
mat3 tilt;
float s_min(in float x, in float y, in float s) {
float bridge =
clamp(abs(x-y)/s, 0.0, 1.0);
return min(x,y) - 0.25 * s * (bridge - 1.0) * (bridge - 1.0);
}
float s_max(in float x, in float y, in float s) {
float bridge =
clamp(abs(x-y)/s, 0.0, 1.0);
return max(x,y) + 0.25 * s * (bridge - 1.0) * (bridge - 1.0);
}
float rect_sdf(in vec3 pt, in vec2 size, in float radius) {
vec2 xy = abs(pt.xy) - size;
xy = xy + curve_rad;
xy = max(vec2(0.0), xy);
float xy_dist = abs(length(xy) - curve_rad);
return length(vec2(pt.z, xy_dist)) - radius;
}
float hits(in vec2 plane_pt) {
vec2 rel = abs(plane_pt) + vec2(curve_rad) - vec2(1.0, 0.5625);
rel = max(vec2(0.0), rel);
return step(dot(rel, rel), curve_rad * curve_rad);
}
float screen_sdf(in vec3 pt) {
vec2 quad_coords = pt.xy / vec2(1.0, 0.5625);
float sqr_off = 0.15 * (dot(quad_coords, quad_coords) - 1.0);
return -pt.z + sqr_off;
}
float sdf1(in vec3 pt) {
return rect_sdf(pt, vec2(1.0, 0.5625), 0.025 * radii.x);
}
float sdf2(in vec3 pt) {
return rect_sdf(pt, vec2(1.0, 0.5625) + 0.05, 0.025 * radii.y);
}
float sdf3(in vec3 pt) {
return rect_sdf(pt, vec2(1.0, 0.5625) + 0.1, 0.025 * radii.z);
}
vec3 sdfs(in vec3 pt) {
return vec3(sdf1(pt), sdf2(pt), sdf3(pt));
}
float sdf(in vec3 pt) {
return min(sdf1(pt), min(sdf2(pt), sdf3(pt)));
}
float min_comp(in vec3 comps) {
return min(comps.x, min(comps.y, comps.z));
}
vec3 sdf_grad(in vec3 pt) {
float f = sdf(pt);
const float h = 0.001;
const float h_inv = 1000.0;
return vec3(sdf(pt + vec3(h, 0.0, 0.0)) - f,
sdf(pt + vec3(0.0, h, 0.0)) - f,
sdf(pt + vec3(0.0, 0.0, h)) - f);
}
vec3 march_to_screen(in vec3 orig, in vec3 dir) {
vec3 p = orig;
float accum = 0.0;
float d = screen_sdf(p);
for (int i = 0; i < 128; ++i) {
if (abs(d) < 1.0e-6 || abs(accum) > MAX_DIST) {
return p;
}
d = screen_sdf(p);
accum += 0.9 * d;
p = orig + accum * dir;
}
return p;
}
float raymarch(in vec3 orig, in vec3 dir, out vec3 integral) {
integral = vec3(0.0);
float curr = 0.0;
const float step_ratio = 0.25;
vec3 curr_sdf = sdfs(orig);
float dist = step_ratio * min_comp(curr_sdf);
vec3 next_sdf = sdfs(orig + dir * dist);
// integral from 0 to d of 1/(a+bx) =
// screw it, just average some things.
integral = dist * (0.25 / curr_sdf + 1.0 / (curr_sdf + next_sdf) + 0.25 / next_sdf);
float total_dist = dist;
const vec3 thresh = vec3(0.004);
for (int i = 0; i < 128; ++i) {
curr_sdf = next_sdf;
dist = step_ratio * min_comp(curr_sdf);
total_dist += dist;
next_sdf = sdfs(orig + total_dist * dir);
vec3 mid = 0.5 * (curr_sdf + next_sdf);
#if INTEGRATE_UNBOUNDED_STUFF
integral += dist * (0.25 / max(thresh, curr_sdf) +
0.5 / max(thresh, mid ) +
0.25 / max(thresh, next_sdf));
#else
integral += dist * (0.25 / max(thresh, curr_sdf * curr_sdf) +
0.5 / max(thresh , mid * mid ) +
0.25 / max(thresh, next_sdf * next_sdf));
#endif
if (min_comp(next_sdf) < 1.0e-4 || total_dist > MAX_DIST) {
return total_dist;
}
}
return total_dist;
}
vec4 mainSingleRay( in vec2 fragCoord )
{
vec2 iResolution = vec2(width, height);
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
vec3 orig = vec3(0.0, 0.0, -0.5);
vec3 dir = normalize(vec3(uv, 1.0));
orig = tilt * orig;
dir = tilt * dir;
orig.z -= 0.5;
vec3 integral;
vec2 plane_hit = march_to_screen(orig, dir).xy;
// orig.xy - dir.xy * (-0.0 + orig.z) / dir.z;
float raydist = raymarch(orig, dir, integral);
#if INTEGRATE_UNBOUNDED_STUFF
vec3 col = smoothstep(0.15 * (1.0 * radii), vec3(0.0), 0.45/(integral));
#else
vec3 col = smoothstep(0.15 * (1.0 * radii), vec3(0.0), 2.5/(integral));
#endif
col = color_map * col;
// plane_hit = plane_hit * (1.0 + 0.05 * smoothstep(0.5, 1.2, length(plane_hit)));
if (raydist < MAX_DIST) {
vec3 pt = orig + raydist * dir;
vec3 norm = normalize(sdf_grad(pt));
vec3 bounce = normalize(reflect(dir, norm));
vec3 dists = abs(sdfs(pt));
col += color_map * step(dists, vec3(1.0e-3));
col += 0.5 * smoothstep(0.5, 1.0, dot(bounce, light)) * vec3(0.9, 0.8, 1.0);
} else if (hits(plane_hit.xy) > 0.1) {
vec2 vid_uv = 0.5 * (plane_hit + vec2(1.0, 0.5625));
vid_uv.y /= 0.5625;
vid_uv -= vec2(0.5, 0.2);
vid_uv *= 1.0 - 0.2 * smoothstep(0.4, 0.0, length(vid_uv));
vid_uv += vec2(0.5, 0.2);
vec3 vid_col = texture2D(tex, vid_uv).rgb;
const mat3 blowout = mat3(1.87583893, 0.96308725, 0.,
0.96308725, 1.17416107, 0.,
0. , 0. , 0.5);
const vec3 cent = vec3(0.47968451,
0.450743,
0.45227517);
vec3 dir = blowout * (vid_col - cent);
vec3 maxes = (step(vec3(0.0), dir) - col)/dir;
float amount = min(maxes.x, min(maxes.y, maxes.z));
amount = min(amount, 0.75);
vid_col = vid_col + dir * amount;
col += 1.5 * vid_col;
}
// Output to screen
return vec4(col,1.0);
}
void main()
{
radii = 0.1 + 0.25 * vec3(1.0) +
0.2 * sin(8.0 * vec3(0.6, 0.81, 0.99) * time);
float theta = 0.1 * sin(time);
float ct = cos(theta);
float st = sin(theta);
tilt = mat3(ct, 0.0, st,
0.0, 1.0, 0.0,
-st, 0.0, ct);
vec4 result = vec4(0.0);
const float r = 0.6;
vec2 fragCoord = v_texcoord * vec2(width, height);
result += 0.25 * mainSingleRay(fragCoord);
result += 0.25 * mainSingleRay(fragCoord + r * vec2(0.0, 1.0));
result += 0.25 * mainSingleRay(fragCoord + r * vec2(0.866, 0.5));
result += 0.25 * mainSingleRay(fragCoord + r * vec2(-0.866, 0.5));
gl_FragColor = result;
}
# launch this one first.
# it will require first installing v4l2loopback
# getting that working properly might be tricky
sudo modprobe v4l2loopback video_nr="10" 'card_label=virtcam' exclusive_caps=1 max_buffers=2
// alternate shader for ufo effect, rename to myshader.frag to replace other one.
#version 100
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texcoord;
uniform sampler2D tex;
uniform float time;
uniform float width;
uniform float height;
//The MIT License
#define MAX_DIST 8.0
#define SKY_BRITE 0.6
#define CRINKLY_SHIP 0 // set to 1 to give the ship some texture2D
const float atmos_thick = 30000.0;
const float earth_rad = 6310000.0;
float atmos_dist(in float dir_y) {
// r^2 * dir_x^2 + (earth_rad + r *dir_y)^2 =
// (atmos_thick + earth_rad)^2
// r^2 * (dir_x^2 + dir_y^2) +
// r * 2.0 * dir_y * earth_rad +
// earth_rad^2 -
// (atmos_thick + earth_rad)^2 = 0.0
// -------------------------------
// r^2 * (dir_x^2 + dir_y^2) +
// r * 2.0 * dir_y * earth_rad -
// atmos_thick^2 -
// 2.0 * atmos_thick * earth_rad = 0.0
// -------------------------------
//r = (-b + sqrt(b^2 - 4ac)) / (2a)
// a = 1.0
// b = 2.0 * dir_y * earth_rad
// c = -atmos_thick^2 - 2.0 * atmos_thick * earth_rad
// r approx
// -dir_y*earth_rad + sqrt(dir_y^2+1.005)*earth_rad
float b = 2.0 * dir_y * earth_rad;
float c = -atmos_thick * atmos_thick - 2.0 * atmos_thick * earth_rad;
return 0.5 * (sqrt(b * b - 4.0 * c) - b);
}
float dist_to_sun_visible(in vec3 ray_dir, in vec3 sun_dir) {
if (false && ray_dir.y < 0.0) {
return earth_rad;
}
if (sun_dir.y > 0.0) {
return 1.0;
}
// e_z = sun_dir
vec3 e_y = normalize(vec3(0.0, 1.0, 0.0) - sun_dir.y * sun_dir);
vec3 e_x = normalize(cross(e_y, sun_dir));
vec2 start_xy = earth_rad * vec2(e_x.y, e_y.y);
vec2 ray_xy = vec2(dot(e_x,ray_dir), dot(e_y, ray_dir)); // do not normalize
// (start_xy + d * ray_xy) ^2 = earth_rad^2
// dot(start_xy, start_xy) - earth_rad^2 +
// 2.0 * d * dot(start_xy, ray_xy) +
// d^2 * dot(ray_xy, ray_xy) = 0.0
float a = dot(ray_xy, ray_xy);
float b = 2.0 * dot(start_xy, ray_xy);
float c = dot(start_xy, start_xy) - earth_rad * earth_rad;
return abs((-b + sqrt(b * b - 4.0 * a * c)) / (2.0 * a));
}
float atmos_weight(in vec3 dir, in vec3 sun_dir) {
float d = atmos_dist(dir.y);
float ds = max(atmos_thick, dist_to_sun_visible(dir, sun_dir));
return max(0.0, atmos_thick/ds - (0.5 * atmos_thick)/d);
}
vec3 get_sun_dir(in float in_time) {
float theta = mod(0.5 * in_time, 31.41592653589793);
float ctheta = cos(theta);
float stheta = sin(theta);
mat3 rot_mat = mat3(ctheta, 0.0, -stheta,
0.0, 1.0, 0.0,
stheta, 0.0, ctheta);
const vec3 sun_dir = normalize(vec3(0.0, 0.0, 1.0));
const mat3 tilt_mat = mat3(0.8, 0.6, 0.0,
-0.6, 0.8, 0.0,
0.0, 0.0, 1.0);
const mat3 inv_tilt_mat = mat3(0.8, -0.6, 0.0,
0.6, 0.8, 0.0,
0.0, 0.0, 1.0);
vec3 dir = tilt_mat * sun_dir;
dir = rot_mat * dir;
return inv_tilt_mat * dir;
}
/**
* Found some Rayleigh scatter equations on wikipedia.
* Mostly copied them, but dropped some terms and simplified others.
*
* Vaguely ends up looking like a blue sky.
*/
vec3 sky_color(in vec3 dir) {
vec3 sun_dir = get_sun_dir(-0.5);
const vec3 lambdas = vec3(0.6, 0.5, 0.45);
const vec3 sky_color_weights = vec3(0.9, 1.0, 0.8);
const vec3 scatter_terms =
sky_color_weights * vec3(0.0625) / (lambdas * lambdas * lambdas * lambdas);
float cos_theta = max(min(dot(dir, sun_dir), 1.0), -1.0);
float dir_factor = (1.0 + cos_theta * cos_theta) * 0.5;
float w = atmos_weight(dir, sun_dir);
float sin_theta = sqrt(1.0 - cos_theta * cos_theta);
vec3 scatter_factor = w * 2.0 * scatter_terms;
vec3 passthru = max(1.0 - scatter_factor, vec3(0.0));
float direct_intense = smoothstep(0.02, 0.018, sin_theta) * step(0.0, cos_theta);
return dir_factor * scatter_factor + direct_intense * passthru;
}
float s_min(in float x, in float y, in float s) {
float bridge =
clamp(abs(x-y)/s, 0.0, 1.0);
return min(x,y) - 0.25 * s * (bridge - 1.0) * (bridge - 1.0);
}
float s_max(in float x, in float y, in float s) {
float bridge =
clamp(abs(x-y)/s, 0.0, 1.0);
return max(x,y) + 0.25 * s * (bridge - 1.0) * (bridge - 1.0);
}
float falloff(in float x, in float range) {
float h = smoothstep(range, 0.0, 1.0/x);
return h * 0.1 * range / s_max(0.1 * range, 1.0/x, 0.05 * range);
}
vec3 perturb2(in vec3 loc) {
return loc;
}
float vehicle_sdf(in vec3 loc_in) {
vec3 loc = (2.0/3.0) * loc_in;
float ball1 = length(loc - vec3(0.0, -1.8, 0.0)) - 2.0;
float ball2 = length(loc - vec3(0.0, 2.0, 0.0)) - 2.1;
float disc = s_max(ball1, ball2, 0.05);
float ball3 = length(loc - vec3(0.0, 0.07, 0.0)) - 0.5;
float result = s_min(disc, max(ball3, -loc.y), 0.1);
float mult = 1.0;
#if CRINKLY_SHIP
mult = 1.0 + 0.95 * mult;
#else
mult = 1.0 + 0.95 * mult * smoothstep(0.025, 0.05, result);
#endif
return result * mult;
}
vec3 vehicle_sdf_grad(in vec3 loc) {
float dist = vehicle_sdf(perturb2(loc));
const float del = 0.01;
return vec3(vehicle_sdf(perturb2(loc + vec3(del, 0.0, 0.0))) - dist,
vehicle_sdf(perturb2(loc + vec3(0.0, del, 0.0))) - dist,
vehicle_sdf(perturb2(loc + vec3(0.0, 0.0, del))) - dist) / del;
}
float cast_to_vehicle(in vec3 orig, in vec3 dir, out float sumdist) {
vec3 p = orig;
float accum = 0.0;
sumdist = 0.0;
for (int i = 0; i < 256; ++i) {
float dist = vehicle_sdf(p);
// mindist = min(dist, mindist);
float remaining = 0.2 * dist;
sumdist += remaining / max(1.0e-3, abs(dist));
accum += remaining;
p = orig + accum * dir;
p = perturb2(p);
if (remaining < 1.0e-3) {
return accum;
}
if (accum > MAX_DIST) {
return accum;
}
}
return max(accum, MAX_DIST + 1.0);
}
float cast_out_of_vehicle(in vec3 orig, in vec3 dir) {
vec3 p = orig + 0.1 * dir;
float accum = 0.1;
for (int i = 0; i < 256; ++i) {
float dist = -vehicle_sdf(p);
// mindist = min(dist, mindist);
float remaining = 0.2 * dist;
accum += remaining;
p = orig + accum * dir;
p = perturb2(p);
if (remaining < 1.0e-3) {
return accum;
}
if (accum > MAX_DIST) {
return accum;
}
}
return max(accum, MAX_DIST + 1.0);
}
vec3 get_bounce(in vec3 pt, in vec3 dir, out float edge_term) {
vec3 norm = normalize(vehicle_sdf_grad(pt));
edge_term = smoothstep(0.7, 0.3, abs(dot(normalize(dir), norm)));
return normalize(reflect(dir, norm));
}
vec4 castRayUFO(in vec2 fragCoord) {
vec2 iResolution = vec2(width, height);
vec2 uv = (2.0 * fragCoord.xy - iResolution.xy) / iResolution.y;
vec3 ray_orig = vec3(0.0, 0.5, -5.0);
vec3 ray_dir = normalize(vec3(uv, 7.0));
float wiggle = abs(mod(0.2 * time, 4.0) - 2.0) - 1.0;
wiggle = 0.2 * sign(wiggle) * smoothstep(0.0, 1.0, abs(wiggle));// - 1.5;
float ct = sin(wiggle);
float st = cos(wiggle);
mat3 twist = mat3(ct, 0.0, st,
0.0, 1.0, 0.0,
-st, 0.0, ct);
ray_dir = twist * ray_dir;
ray_orig = twist * ray_orig;
wiggle = 0.05 * sin(time) - 0.2;
ct = cos(wiggle);st = sin(wiggle);
twist = mat3(ct, st, 0.0,
-st, ct, 0.0,
0.0, 0.0, 1.0);
wiggle = 0.05 * sin(0.71 *time + 1.3);
ct = cos(wiggle);st = sin(wiggle);
twist *= mat3(1.0, 0.0, 0.0,
0.0, ct, st,
0.0, -st, ct);
ray_dir = twist * ray_dir;
ray_orig = twist * ray_orig;
ray_orig = ray_orig - vec3(0.0, 0.2, 0.0) -
sin(vec3(4.2, 1.2, 3.4) * time) * 0.2 * vec3(0.5, 0.1, 0.2);
float closeness = MAX_DIST;
float d = cast_to_vehicle(ray_orig, ray_dir, closeness);
vec3 ray_mul = vec3(1.0);
vec3 paste_color = vec3(0.0);
float paste_modulate = 0.0;
vec3 orig_ray_dir = ray_dir;
if (d < MAX_DIST) {
vec3 pt = ray_orig + d * ray_dir;
float edginess = 0.0;
ray_dir = get_bounce(pt, ray_dir, edginess);
ray_mul = mix(vec3(1.0, 0.9, 0.85), ray_mul, edginess);
vec2 window_cent = vec2(0.0, 0.45);
vec2 window_size = vec2(0.9, 0.6);
vec2 projected = (pt.zy - window_cent) / window_size;
if (abs(projected.x) < 0.5 && abs(projected.y) < 0.5) {
paste_modulate = smoothstep(0.5, 0.48, length(projected));
float to_back = (0.2 - pt.x) / orig_ray_dir.x;
to_back = 0.5 * cast_out_of_vehicle(pt, orig_ray_dir);
vec3 back_pt = pt + to_back * orig_ray_dir;
vec3 back_norm = -normalize(vehicle_sdf_grad(pt));
vec2 back_projected =
(back_pt.zy - window_cent) / window_size;
projected.y *= -1.0;
back_projected *= 1.5;
back_projected.y *= -1.0;
paste_color = texture2D(tex, vec2(0.5, 0.6) + back_projected).rgb;
paste_color *=
smoothstep(0.5, 0.47, abs(back_projected.x)) *
smoothstep(0.5, 0.47, abs(back_projected.y + 0.1));
float darken = 0.1 / (0.1 + max(0.0, to_back));
darken = min(1.0, 5.0 * darken);
// darken = 0.5 + 0.5 * sin(2500.0 * to_back);
paste_color = mix(vec3(1.0, 0.7, 0.0), paste_color, darken);
vec3 back_light_color =
vec3(smoothstep(-0.2, 0.7, dot(back_norm, get_sun_dir(-0.5))));
paste_color = 0.7 * back_light_color + paste_color;
// paste_color = back_light_color;
}
}
vec3 thump = sin(vec3(0.1, 0.8, -0.6) + time * vec3(4.1, 6.5, 5.2));
thump = 0.3 * smoothstep(0.8, 1.0, thump);
float darken = 0.15 * (1.0 + 3.0 * smoothstep(1.5, 0.5, dot(thump, vec3(1.0))));
return vec4(mix(SKY_BRITE * darken * ray_mul * sky_color(ray_dir) +
(0.25 + 1.75 * smoothstep(0.05, 0.8, thump))
* falloff(closeness, 0.5), paste_color, paste_modulate),
1.0);
}
vec4 mainImage(in vec2 fragCoord )
{
return castRayUFO(fragCoord);
}
void main()
{
vec2 fragCoord = vec2(v_texcoord.x, 1.0 - v_texcoord.y) *
vec2(width, height);
gl_FragColor = mainImage(fragCoord);
/*
gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(1.0, 0.0, 0.0),
step(abs(v_texcoord.y - 0.5), 0.05));
gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0.0, 1.0, 0.0),
step(abs(v_texcoord.y - 0.75), 0.025));
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment