Fragment shadery napisane w języku GLSL. Niektóre mogą nie działać w przeglądarce innej niż Firefox.

Stary film · 
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform vec2 u_mouse; uniform float u_time; uniform sampler2D u_tex0; uniform vec2 u_tex0Resolution; uniform sampler2D u_tex1; uniform vec2 u_tex1Resolution; uniform sampler2D u_tex2; uniform vec2 u_tex2Resolution; #define PI 3.1415926 #define radius 1 float random (vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123); } vec4 rgb(vec2 st, vec2 matrix) { return texture2D(u_tex0, st*matrix + vec2(0., .25)); } vec4 zbuf(vec2 st, vec2 matrix) { return texture2D(u_tex1, st*matrix + vec2(0., .25)); } vec4 proc(vec2 st, vec2 matrix) { vec4 rgb = rgb(st, matrix); vec4 zbuf = zbuf(st, matrix); vec4 c = vec4(0.); float acc = 0.; for (int i = radius; i >= 0; i--) { for (int j = radius; j >= 0; j--) { c += texture2D(u_tex0, st*matrix + vec2(float(i)/u_resolution.x, float(j)/u_resolution.y) + vec2(0., .25)); acc += 1.; } } c = mix(texture2D(u_tex0, st*matrix + vec2(0., .25)), c/acc, abs(zbuf.b - .5) + length(st - vec2(.5))); c *= pow(smoothstep(0., 1., length(c.rgb)/1.7321), .5)*.85 + .1; //value curve c *= cos(length(st - vec2(.5))); //vignette c += (texture2D(u_tex2, fract(st*vec2(1., u_resolution.y/u_resolution.x) + vec2(random(vec2(u_time)), random(vec2(-u_time*PI)))*.1)) - .5)*10.*sin(length(c.rgb)/1.7321*PI) + (random(vec2(u_time*10. - fract(u_time*10.)) - .5)*.025); //grain c = vec4(vec3(.18*c.r + .41*c.g + .41*c.b), 1.); return c; } void main() { vec2 st = gl_FragCoord.xy/u_resolution; vec2 matrix = vec2(1., u_resolution.y/u_resolution.x*u_tex0Resolution.x/u_tex0Resolution.y); vec4 col; float c_cut = gl_FragCoord.x + u_resolution.y*.5; float z_cut = gl_FragCoord.x - u_resolution.y*1.5; if (u_mouse.y > u_mouse.x + u_resolution.y*.5) { c_cut = gl_FragCoord.x - u_resolution.y; z_cut = gl_FragCoord.x - u_resolution.y*1.5; } else if (u_mouse.y < u_mouse.x - u_resolution.y*1.5) { c_cut = gl_FragCoord.x + u_resolution.y*.5; z_cut = gl_FragCoord.x; } if (gl_FragCoord.y > c_cut) { col = rgb(st, matrix); } else if (gl_FragCoord.y < z_cut) { col = zbuf(st, matrix); } else { col = proc(st, matrix); } gl_FragColor = col; }

Najpierw wpadłem na pomysł „zakładek” rozsuwanych po najechaniu na nie myszką, potem stwierdziłem, że wykorzystam to do rozłożenia jakiegoś efektu na obraz wejściowy z użyciem mapy głębokości (z–buffer). Pomysł w pracy, wykonanie w pociągu.

Floraturowa tapeta · 
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; #define PI 3.1415926 float random(vec2 st) { return fract( sin( dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); } float Fleur(vec2 st) { // draw center leaf float center = sin((1. - st.y)*PI*2.5 + 2.*PI/3.) + 1.; if (st.y < .65) { center *= .49; } center = 1. - ((center)*0.4) - .5; if (abs((st.x*2. - 1.)*2.) < center && st.y < .9 && st.y > .1) { return 1.; } // draw left leaf if (length(vec2(.25, .275) - st) < .2 && length(vec2(.2, .2) - st) > .18) { return 1.; } // draw right leaf if (length(vec2(.75, .275) - st) < .2 && length(vec2(.8, .2) - st) > .18) { return 1.; } return 0.; } void main() { vec2 st = gl_FragCoord.xy/u_resolution; // shift every other column by half line if (mod(st.x*3., 1.) < .5 ) { st.y += 1./12.; } // draw fleurons vec2 pos = mod(st*6., 1.); float v = Fleur(pos); // assign base colors vec4 col; if (v == 1.) { col = vec4(1., .8, .2, 1.); } else { col = vec4(.1, .1, .3, 1.); col.rgb *= 1. + sin(gl_FragCoord.x*PI/3.)*.2; } // unshift columns to prevent messed up lighting if (mod(st.x*3., 1.) < .5 ) { st.y -= 1./12.; } // add lighting col.rgb *= 1. - sin(length(st - vec2(.5) + vec2(sin(u_time), cos(u_time))*.3)*PI/2.)*.75; // add noise col.rgb += (random(st) - .5)*.4; gl_FragColor = col; }

Postanowiłem spróbować swoich sił w programatycznym rysowaniu wzorów po obejrzeniu w internecie pięknych prac tego typu. Wybór padł na tapetę z floraturowym deseniem. Najpierw naszkicowałem kształt na kartce za pomocą sinusów i okręgów, potem spróbowałem odwzorować go łączeniem i wycinaniem funkcji w kodzie. Dla urozmaicenia dodałem też proceduralną teksturę złotej farby, rowki tapety oraz subtelnie krążące światło.

Wykres siły ziarna w funkcji jasności · 
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; uniform sampler2D u_tex0; uniform vec2 u_tex0Resolution; #define PI 3.1415926 float Curve(float value, float shadows, float lights, float dark_shape, float light_shape) { float val = 0.; if (value <= lights) { val = pow( smoothstep(-shadows, shadows, value)*2. - 1., dark_shape); } else { val = pow( (1. - smoothstep(lights, 2. - lights, value) )*2. - 1., light_shape); } return val; } void main() { float SCALE = 1.; float STRENGTH = 10.; float BLACKS = sin(u_time/2.)*.2 + .2; float WHITES = sin(u_time/PI)*.1 + .1; float DARK_SHAPE = sin(u_time/3.7) + 1.5; float LIGHT_SHAPE = sin(u_time/1.7) + 1.5; float SHADOWS = sin(u_time/4.51)*.2 + .4; float LIGHTS = sin(u_time/PI/2.1)*.1 + .7; vec2 st = gl_FragCoord.xy/u_resolution; vec4 col = vec4(0., 0., 0., 1.); // add positive bias to black and white point float bias = 0.; if (st.x < SHADOWS) { bias = BLACKS; } else { bias = WHITES; } // compute value of the curve float response = Curve(st.x, SHADOWS, LIGHTS, DARK_SHAPE, LIGHT_SHAPE) + (-Curve(st.x, SHADOWS, LIGHTS, DARK_SHAPE, LIGHT_SHAPE) + 1.)*bias; // prepare grain texture vec3 c = texture2D(u_tex0, st*(u_resolution/u_tex0Resolution)/SCALE).rgb; c = (c - vec3(.5))*STRENGTH*response + vec3(.5); // draw final graph float off = response - st.y; if (st.y < response) { col = vec4(c + vec3(st.x) - vec3(.5), 1.); } else { col = vec4(c*.15, 1.); } gl_FragColor = col; }

Wykres oryginalnie napisany (tak jak kolejny poniżej) w języku shaderów silnika Godot Engine, podobnym do GLSL. Ten wykres to część projektu interfejsu aplikacji graficznej służącej do symulacji kliszy fotograficznej. Ziarno pod krzywą pokrywa gradient zgodnie z wartością wykresu.

Wykres nasycenia w funkcji odcienia · 
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform vec2 u_mouse; uniform float u_time; #define PI 3.1415926 #define SQRT2 1.4142 #define POINT0 0. #define POINT1 60. #define POINT2 120. #define POINT3 180. #define POINT4 240. #define POINT5 300. // Smooth HSV to RGB conversion (https://www.shadertoy.com/view/MsS3Wc) vec3 hsv2rgb_smooth(vec3 c) { vec3 rgb = clamp(abs(mod(c.x*6. + vec3(0., 4., 2.), 6.) - 3.) - 1., 0., 1.); rgb = rgb*rgb*(3. - 2.*rgb); // cubic smoothing return c.z*mix(vec3(1.), rgb, c.y); } float Curve(float left, float center, float right, float point) { if (point <= center) { return smoothstep(left, center, point); } else { return 1.0 - smoothstep(center, right, point); } } void main() { float VALUE0 = sin(u_time/10.)*sin(-u_time/37.913)*.6 + sin(u_time/1.913)*.2; float VALUE1 = sin(u_time/2.)*.5 + sin(u_time/12.37)*.2 + .2; float VALUE2 = sin(u_time/PI)*.3 + sin(-u_time/43.437)*.6; float VALUE3 = sin(u_time)* sin(u_time/13.71)*.7 - .1; float VALUE4 = pow(sin(-u_time/7.37),4.)*.6 + sin(u_time/1.13)*.3; float VALUE5 = sin(u_time/2.345)*.5 + sin(-u_time/9.97)*.3 + .1; vec2 st = gl_FragCoord.xy/u_resolution; // compute value of the curve float value = 0.; value += Curve(POINT5 - 360., POINT0, POINT1, st.x)*VALUE0; value += Curve(POINT0, POINT1, POINT2, st.x)*VALUE1; value += Curve(POINT1, POINT2, POINT3, st.x)*VALUE2; value += Curve(POINT2, POINT3, POINT4, st.x)*VALUE3; value += Curve(POINT3, POINT4, POINT5, st.x)*VALUE4; value += Curve(POINT4, POINT5, POINT0 + 360., st.x)*VALUE5; value += Curve(POINT5, POINT0 + 360., POINT1 + 360., st.x)*VALUE0; // check if given pixel bool tick = false; if (value >= 0. && st.y >= .5 && st.y - .5 < value*.5) { tick = true; } else if (value < 0. && st.y < .5 && st.y - .5 > value*.5) { tick = true; } if (abs(st.y - .5) < 4./u_resolution.x) { tick = true; } // color given pixel: x->hue, y->saturation+value vec4 c = vec4(hsv2rgb_smooth(vec3(st.x, smoothstep(0., 1., st.y), st.y*.75)), 1.); if (!tick) { c *= .15; } gl_FragColor = c; }

Kolejna część interfejsu aplikacji do symulacji kliszy fotograficznej. Ten wykres odpowiada za jasność wyjściowego piksela obrazu monochromatycznego w funkcji odcienia kolorowego piksela wejściowego.

Monitor CRT · 
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; #define PI 3.1415926 #define SQRT2 1.4142 void main() { vec2 st = gl_FragCoord.xy/u_resolution; // mark the border vec2 toCenter = vec2(.5) - st; float radius = length(toCenter) * SQRT2; float step = step(abs(mod(st.x + .5, 1.) - .5)*2.*abs(mod(st.y + .5,1.) - .5)*2., .05); vec3 color = vec3(0.); if (step < 1.) { // draw the pixel grid color = vec3(sin(gl_FragCoord.x*PI/3.)*.5 + .5, sin((gl_FragCoord.x - 2.)*PI/3.)*.5 + .5, sin((gl_FragCoord.x - 4.)*PI/3.)*.6 + .5); // add vignette step = abs(1. - sin(radius*.5) * (1. - sin(gl_FragCoord.y*PI/3.) - .5 )); // add animated framerate desynchronization effect step += .1*(pow(clamp(sin(st.y*PI*2. + u_time*.5) + .5, 0., 1.), .25) - 1.); color *= step; } gl_FragColor = vec4(color,1.); }

Prosty monitor CRT. Ma staromodną zaokrągloną ramkę, subpiksele RGB, winietę i efekt braku synchronizacji klatek między lampą a kamerą.

Różne kółka ·