Inspred by 104 from ShaderToy, I made a new 404 Not Found page with two triangles in GLSL using calligraphic strokes. Live Demo Please visit: http://duruofei.com/404 or Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
// Ruofei Du // 404 Not Found Page // Forked and mixed from 104's Brush Experiments: https://www.shadertoy.com/view/ltj3Wc // Free to use and share vec2 mouse; //////////////////////////////////////////////////////////////// // BOILERPLATE UTILITIES................... const float pi = 3.14159265359; const float pi2 = pi * 2.; float opU( float d1, float d2 ){ return min(d1,d2); } float opS( float d2, float d1 ){ return max(-d1,d2); } float opI( float d1, float d2) { return max(d1,d2); } // from "Magic Fractal" by dgreensp // https://www.shadertoy.com/view/4ljGDd float magicBox(vec3 p) { const int MAGIC_BOX_ITERS = 13; const float MAGIC_BOX_MAGIC = 0.55; // The fractal lives in a 1x1x1 box with mirrors on all sides. // Take p anywhere in space and calculate the corresponding position // inside the box, 0<(x,y,z)<1 p = 1.0 - abs(1.0 - mod(p, 2.0)); float lastLength = length(p); float tot = 0.0; // This is the fractal. More iterations gives a more detailed // fractal at the expense of more computation. for (int i=0; i < MAGIC_BOX_ITERS; i++) { // The number subtracted here is a "magic" paremeter that // produces rather different fractals for different values. p = abs(p)/(lastLength*lastLength) - MAGIC_BOX_MAGIC; float newLength = length(p); tot += abs(newLength-lastLength); lastLength = newLength; } return tot; } float magicBox(vec2 uv){ // A random 3x3 unitary matrix, used to avoid artifacts from slicing the // volume along the same axes as the fractal's bounding box. const mat3 M = mat3(0.28862355854826727, 0.6997227302779844, 0.6535170557707412, 0.06997493955670424, 0.6653237235314099, -0.7432683571499161, -0.9548821651308448, 0.26025457467376617, 0.14306504491456504); vec3 p = 0.5*M*vec3(uv, 0.0); return magicBox(p); } mat2 rot2D(float r) { float c = cos(r), s = sin(r); return mat2(c, s, -s, c); } float nsin(float a){return .5+.5*sin(a);} float ncos(float a){return .5+.5*cos(a);} vec3 saturate(vec3 a){return clamp(a,0.,1.);} float rand(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } float rand(float n){ return fract(cos(n*89.42)*343.42); } float dtoa(float d, float amount) { return clamp(1.0 / (clamp(d, 1.0/amount, 1.0)*amount), 0.,1.); } float sdAxisAlignedRect(vec2 uv, vec2 tl, vec2 br) { vec2 d = max(tl-uv, uv-br); return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y)); } float sdCircle(vec2 uv, vec2 origin, float radius) { return length(uv - origin) - radius; } // 0-1 1-0 float smoothstep4(float e1, float e2, float e3, float e4, float val) { return min(smoothstep(e1,e2,val), 1.-smoothstep(e3,e4,val)); } // hash & simplex noise from https://www.shadertoy.com/view/Msf3WH vec2 hash( vec2 p ) { p = vec2( dot(p,vec2(127.1,311.7)), dot(p,vec2(269.5,183.3)) ); return -1.0 + 2.0*fract(sin(p)*43758.5453123); } // returns -.5 to 1.5. i think. float noise( in vec2 p ) { const float K1 = 0.366025404; // (sqrt(3)-1)/2; const float K2 = 0.211324865; // (3-sqrt(3))/6; vec2 i = floor( p + (p.x+p.y)*K1 ); vec2 a = p - i + (i.x+i.y)*K2; vec2 o = (a.x>a.y) ? vec2(1.0,0.0) : vec2(0.0,1.0); //vec2 of = 0.5 + 0.5*vec2(sign(a.x-a.y), sign(a.y-a.x)); vec2 b = a - o + K2; vec2 c = a - 1.0 + 2.0*K2; vec3 h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 ); vec3 n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0))); return dot( n, vec3(70.0) ); } float noise01(vec2 p) { return clamp((noise(p)+.5)*.5, 0.,1.); } // debug function to convert distance to color, revealing sign. vec3 dtocolor(vec3 inpColor, float dist) { vec3 ret; if(dist > 0.) ret = mix(vec3(0,0,.5), vec3(.5,.5,1), sin(dist * pi2 * 50.));// red = negative / inside geometry. else ret = mix(vec3(1.,.5,.5), vec3(.5,0,0), sin(dist * pi2 * 50.));// blue = positive, of of geometry. ret = mix(ret, vec3(0), clamp(abs(dist),0.,1.));// falloff return ret; } float smooth(float x) { return x*x*x*(x*(x*6. - 15.) + 10.); } //////////////////////////////////////////////////////////////// // APP CODE ................... // this function will produce a line with brush strokes. the inputs are such // that you can apply it to pretty much any line; the geometry is separated from this function. vec3 colorBrushStroke(vec2 uvLine, vec2 uvPaper, vec2 lineSize, float sdGeometry, vec3 inpColor, vec4 brushColor) { float posInLineY = (uvLine.y / lineSize.y);// position along the line. in the line is 0-1. if(iMouse.z > 0.) { // return mix(inpColor, vec3(0), dtoa(sdGeometry, 1000.));// reveal geometry. // return mix(inpColor, dtocolor(inpColor, uvLine.y), dtoa(sdGeometry, 1000.));// reveal Y // return mix(inpColor, dtocolor(inpColor, posInLineY), dtoa(sdGeometry, 1000.));// reveal pos in line. // return mix(inpColor, dtocolor(inpColor, uvLine.x), dtoa(sdGeometry, 1000.));// reveal X float okthen = 42.;// NOP } // warp the position-in-line, to control the curve of the brush falloff. if(posInLineY > 0.) { float mouseX = iMouse.x == 0. ? 0.2 : (iMouse.x / iResolution.x); //float mouseX = 1.0; posInLineY = pow(posInLineY, (pow(mouseX,2.) * 15.) + 1.5); } // brush stroke fibers effect. float strokeBoundary = dtoa(sdGeometry, 300.);// keeps stroke texture inside the geometry. float strokeTexture = 0. + noise01(uvLine * vec2(min(iResolution.y,iResolution.x)*0.2, 1.))// high freq fibers + noise01(uvLine * vec2(79., 1.))// smooth brush texture. lots of room for variation here, also layering. + noise01(uvLine * vec2(14., 1.))// low freq noise, gives more variation ; strokeTexture *= 0.333 * strokeBoundary;// 0 to 1 (take average of above) strokeTexture = max(0.008, strokeTexture);// avoid 0; it will be ugly to modulate // fade it from very dark to almost nonexistent by manipulating the curve along Y float strokeAlpha = pow(strokeTexture, max(0.,posInLineY)+0.09);// add allows bleeding // fade out the end of the stroke by shifting the noise curve below 0 const float strokeAlphaBoost = 1.09; if(posInLineY > 0.) strokeAlpha = strokeAlphaBoost * max(0., strokeAlpha - pow(posInLineY,0.5));// fade out else strokeAlpha *= strokeAlphaBoost; strokeAlpha = smooth(strokeAlpha); // paper bleed effect. float paperBleedAmt = 60. + (rand(uvPaper.y) * 30.) + (rand(uvPaper.x) * 30.); // amt = 500.;// disable paper bleed // blotches (per stroke) //float blotchAmt = smoothstep(17.,18.5,magicBox(vec3(uvPaper, uvLine.x))); //blotchAmt *= 0.4; //strokeAlpha += blotchAmt; float alpha = strokeAlpha * brushColor.a * dtoa(sdGeometry, paperBleedAmt); alpha = clamp(alpha, 0.,1.); return mix(inpColor, brushColor.rgb, alpha); } vec3 colorBrushStrokeLine(vec2 uv, vec3 inpColor, vec4 brushColor, vec2 p1_, vec2 p2_, float lineWidth) { // flatten the line to be axis-aligned. float lineAngle = pi-atan(p1_.x - p2_.x, p1_.y - p2_.y); mat2 rotMat = rot2D(lineAngle); float lineLength = distance(p2_, p1_); // make an axis-aligned line from this line. vec2 tl = (p1_ * rotMat);// top left vec2 br = tl + vec2(0,lineLength);// bottom right vec2 uvLine = uv * rotMat; // make line slightly narrower at end. lineWidth *= mix(1., .9, smoothstep(tl.y,br.y,uvLine.y)); // wobble it around, humanize float res = min(iResolution.y,iResolution.x); uvLine.x += (noise01(uvLine * 1.)-0.5) * 0.02; uvLine.x += cos(uvLine.y * 3.) * 0.009;// smooth lp wave uvLine.x += (noise01(uvLine * 5.)-0.5) * 0.005;// a sort of random waviness like individual strands are moving around // uvLine.x += (noise01(uvLine * res * 0.18)-0.5) * 0.0035;// HP random noise makes it look less scientific // calc distance to geometry. actually just do a straight line, then we will round it out to create the line width. float d = sdAxisAlignedRect(uvLine, tl, br) - lineWidth / 2.; uvLine = tl - uvLine; vec2 lineSize = vec2(lineWidth, lineLength); vec3 ret = colorBrushStroke(vec2(uvLine.x, -uvLine.y), uv, lineSize, d, inpColor, brushColor); return ret; } // returns: // xy = uvLine // z = radius vec3 humanizeBrushStrokeDonut(vec2 uvLine, float radius_, bool clockwise, float lineLength) { vec2 humanizedUVLine = uvLine; // offsetting the circle along its path creates a twisting effect. float twistAmt = .24; float linePosY = humanizedUVLine.y / lineLength;// 0 to 1 scale humanizedUVLine.x += linePosY * twistAmt; // perturb radius / x float humanizedRadius = radius_; float res = min(iResolution.y,iResolution.x); humanizedRadius += (noise01(uvLine * 1.)-0.5) * 0.04; humanizedRadius += sin(uvLine.y * 3.) * 0.019;// smooth lp wave humanizedUVLine.x += sin(uvLine.x * 30.) * 0.02;// more messin humanizedUVLine.x += (noise01(uvLine * 5.)-0.5) * 0.005;// a sort of random waviness like individual strands are moving around // humanizedUVLine.x += (noise01(uvLine * res * 0.18)-0.5) * 0.0035;// HP random noise makes it look less scientific return vec3(humanizedUVLine, humanizedRadius); } // there's something about calling an Enso a "donut" that makes me giggle. // TODO: sweepAmt is 0 to 1, the amount of the circle to cover by the brush stroke. 1=whole circle. 0=just a point. vec3 colorBrushStrokeDonut(vec2 uv, vec3 inpColor, vec4 brushColor, vec2 o, float radius_, float angleStart, float sweepAmt, float lineWidth, bool clockwise) { vec2 uvLine = uv - o; float angle = atan(uvLine.x, uvLine.y) + pi;// 0-2pi angle = mod(angle-angleStart+pi, pi2); if(!clockwise) angle = pi2 - angle; float lineLength = radius_ * pi2;// this is calculated before any humanizing/perturbance. so it's possible that it's slightly inaccurate, but in ways that will never matter uvLine = vec2( radius_ - length(uvLine), angle / pi2 * lineLength ); // make line slightly narrower at end. float lineWidth1 = lineWidth * mix(1., .9, smoothstep(0.,lineLength,uvLine.y)); vec3 hu = humanizeBrushStrokeDonut(uvLine, radius_, clockwise, lineLength); vec2 humanizedUVLine = hu.xy; float humanizedRadius = hu.z; float d = opS(sdCircle(uv, o, humanizedRadius), sdCircle(uv, o, humanizedRadius)); d -= lineWidth1 * 0.5;// round off things just like in the line routine. vec3 ret = colorBrushStroke(humanizedUVLine, uv, vec2(lineWidth1, lineLength), d, inpColor, brushColor); // do the same but for before the beginning of the line. distance field is just a single point vec3 ret2 = vec3(1); if(angle > pi) { uvLine.y -= lineLength; hu = humanizeBrushStrokeDonut(uvLine, radius_, clockwise, lineLength); humanizedUVLine = hu.xy; humanizedRadius = hu.z; vec2 strokeStartPos = o + vec2(sin(angleStart), cos(angleStart)) * humanizedRadius; d = distance(uv, strokeStartPos); d -= lineWidth * 0.5 * 1.;// round off things just like in the line routine. ret2 = colorBrushStroke(humanizedUVLine, uv, vec2(lineWidth, lineLength), d, inpColor, brushColor); } return min(ret, ret2); } vec2 getuv_centerX(vec2 fragCoord, vec2 newTL, vec2 newSize) { vec2 ret = vec2(fragCoord.x / iResolution.x, (iResolution.y - fragCoord.y) / iResolution.y);// ret is now 0-1 in both dimensions ret *= newSize;// scale up to new dimensions float aspect = iResolution.x / iResolution.y; ret.x *= aspect;// orig aspect ratio float newWidth = newSize.x * aspect; return ret + vec2(newTL.x - (newWidth - newSize.x) / 2.0, newTL.y); } void main() { mouse = getuv_centerX(iMouse.xy, vec2(-1,-1), vec2(2,2));// (iMouse.xy / iResolution.y * 2.0) - 1.; vec2 uv = getuv_centerX(fragCoord, vec2(-1,-1), vec2(2,2));// 0-1 centered vec3 col = vec3(1.,1.,0.875);// bg float dist; // geometry on display... float yo = sin(-uv.x*pi*0.5)*0.2; vec4 colFour = vec4(vec3(.8,.1,0),.9); float widFour = 0.15; // 4 col = colorBrushStrokeLine(uv, col, colFour, vec2(-1.0, -0.6+yo), vec2(-1.6, 0.2+yo), widFour); col = colorBrushStrokeLine(uv, col, colFour, vec2(-1.45, 0.05+yo), vec2(-0.5, 0.35-yo), widFour); col = colorBrushStrokeLine(uv, col, colFour, vec2(-1.0, -0.6+yo), vec2(-1.0, 0.6+yo), widFour); // 0 col = colorBrushStrokeDonut(uv, col, vec4(0,0,0,.9), vec2(0,0),// origin 0.4,// radius 3.4,// angle of brush start 0.5,// sweep amt 0-1 0.2,// width true);// clockwise // 4 colFour = vec4(vec3(.8,.1,0),.8); float dt = 2.3; widFour = 0.15; col = colorBrushStrokeLine(uv, col, colFour, vec2(-1.0+dt+yo, -0.4), vec2(-1.6+dt+yo, 0.2), widFour); col = colorBrushStrokeLine(uv, col, colFour, vec2(-1.6+dt, -0.0-yo), vec2(-0.5+dt+yo, 0.25+yo), widFour); col = colorBrushStrokeLine(uv, col, colFour, vec2(-1.0+dt+yo, -0.4), vec2(-1.0+dt+yo, 0.8), widFour); // paint blotches float blotchAmt = smoothstep(20.,50.,magicBox((uv+12.)*2.));// smoothstep(40.,40.5, magicBox((uv+9.4)*2.)); blotchAmt = pow(blotchAmt, 3.);// attenuate blotchAmt = .7*smoothstep(.2,.4,blotchAmt);// sharpen col *= 1.-blotchAmt; // grain col.rgb += (rand(uv)-.5)*.08; col.rgb = saturate(col.rgb); // vignette vec2 uvScreen = (fragCoord / iResolution.xy * 2.)-1.; float vignetteAmt = 1.-dot(uvScreen*0.5,uvScreen* 0.62); col *= vignetteAmt; gl_FragColor = vec4(col, 1.); } |