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 ·