cataracts.swsl 7.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. // This is a shader simulation of cataracts. Not exactly a realistic/faithful one (the faithful route would be bloom and blur strong enough to make anyone's eyes bleed), but rather, a stylized one!
  2. // This description and explainer here is mostly for the sake of future reference, and to make sure the artistic intent doesn't end up lost throughout the years to come.
  3. // The effects this goes for are based largely off reports from blind friends and colleagues alike. Mostly reported experiences of cataracts, but also other forms of blindness, such as neuro-opthalmic injuries.
  4. // The distortion here does the bulk of the work. This simulates the most common thing that those with the condition report: of everything being an amorphous blur, of seeing double even out of one eye, and more.
  5. // Of course, it'd be incredibly straight-forward to go for blur. However, this runs into a notable issue: making a blur strong enough to actually be debilitating would be pretty intense on GPU performance.
  6. // There's additional issues with blur as well, but the main sour point is that it *really* doesn't work well in 2D, where everything has the same effective focal point.
  7. // However, distortion is surprisingly suitable for the purpose of making everything amorphous, even if it's less a blur and more a haze.
  8. // This distortion also moves a bit. This definitely leans far more towards simulating neuro-opthalmic injuries than cataracts specifically (this is effectively oscillopsia), but it does make sure you can't negate the effect by memorizing what part of the screen correlates to exactly where.
  9. // As a bonus: this also simulates less-talked-about experiences, such as always seeing multiple, and attempts to rely on far-sighted vision being immensely disorienting.
  10. // But being unable to make out what's happening in your vision is far from the only pain that blind folks have to put up with.
  11. // Notably: another very common report is that it can be hard (but not impossible) to make out color, and that every light source is incredibly *bright* to the point of causing pain.
  12. // This, too, would be far more straight-forward to implement by slapping a heavy bloom filter on the effect.
  13. // ... However, this is a bad idea for a pretty obvious reason: heavy bloom almost always causes eyestrain. The idea here is to simulate blindness, not *cause* blindness!
  14. // So instead, this takes a slightly more creative route: aggressively tonemapping the distorted output, and pulling in a distorted lightmap to blend on top.
  15. // These two steps combined create a foggy and surprisingly blurry image. The result is deliberately muted a bit to reduce eyestrain, but it does lead to any amount of light overpowering everything else, which lines up with reported experiences.
  16. // All of this in combination manages to achieve an experience that hits the same notes of what's common for blind folks to report, but in a *very* stylized manner.
  17. // And of course, more importantly: the combination ensures that if your character is blind, then you simply can't see shit. You can certainly *try*, just as blind folks IRL are able to, but that'll be fruitless!
  18. // Oh. God. We're already at 18 lines of header comments. We rambled, huh? Anyway, thank you for coming to our TED talk. - Bhijn & Myr
  19. // Boilerplate vars
  20. uniform sampler2D SCREEN_TEXTURE;
  21. uniform sampler2D LIGHT_TEXTURE;
  22. uniform highp float Zoom;
  23. // Base distortion
  24. uniform highp float DistortionScalar; // Scales the total distortion applied to the final image.
  25. const highp float DistortionCoordScalar = 0.1125; //Scales the coordinates for the distortion texture
  26. const highp float DistortionCenterRadius = 200.0; // radius of the gradient used to tone down the distortion effect
  27. const highp float DistortionCenterMinDist = 0.25; // minimum distance from the center of the screen for the distortion to appear at all
  28. const highp float DistortionCenterPow = 1.1; // the exponent used for the distortion center
  29. const highp float DistortionGradientMax = 8.0; // Maximum value for the gradient used for the distortion
  30. const highp float DistortionZoomPow = 0.5; // exponent for the zoom uniform when applied to distortion. The math is funky
  31. const highp float NoiseScalar = 10.0; // Multiplies the size of the FBM noise
  32. // Base cloudiness
  33. uniform highp float CloudinessScalar;
  34. const highp float CloudinessCenterRadius = 125.0; // radius of the gradient used to tone down the cloudiness
  35. const highp float CloudinessCenterMinDist = 0.2; // minimum distance from the center of the screen for cloudiness to appear at all
  36. const highp float CloudinessCenterPow = 2.0; // the exponent used for the distortion center
  37. const highp float CloudinessGradientMax = 8.0; // Maximum value for the gradient used for cloudiness
  38. const highp float CloudinessGrayscaleMax = 0.8; // maximum desaturation for the cloudiness effect
  39. const highp float CloudinessGrayscaleMult = 0.15; // multiplier applied to the gradient for the desaturation scalar
  40. const highp float CloudinessZoomPow = 0.85; // exponent for the zoom uniform when applied to cloudiness
  41. // Additional lightmap pass for the cloudiness
  42. const highp float CloudyLightDistortionMult = 0.5; // multiplier applied to the total amount of distortion added to the lightmap during the lightmap pass
  43. const highp float CloudyLightMult = 0.05; // multiplier for scaling the lightmap's brightness
  44. const highp float CloudyLightGrayscaleMax = 0.25; // maximum amount of grayscale effect for the lightmap
  45. const highp float CloudyLightGrayscaleMult = 0.15; // multiplier used to scale the grayscale effect
  46. const highp float TimeScale = 0.25;
  47. void fragment() {
  48. highp vec2 aspect = vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y);
  49. highp float timesin = (sin(TIME * TimeScale) + 0.5) * 0.2;
  50. highp float timecos = (cos(TIME * TimeScale) + 0.5) * 0.2;
  51. highp float distortionZoom = pow(Zoom, DistortionZoomPow);
  52. highp float centergradient = zCircleGradient(aspect, FRAGCOORD.xy, DistortionGradientMax, DistortionCenterRadius / distortionZoom, DistortionCenterMinDist / distortionZoom, DistortionCenterPow);
  53. highp vec2 coord = (FRAGCOORD.xy * SCREEN_PIXEL_SIZE.xy) * centergradient * DistortionCoordScalar;
  54. highp vec2 offset = vec2((zFBM(coord * NoiseScalar + timesin) - 0.5) * centergradient, (zFBM(coord * NoiseScalar + timecos) - 0.5) * centergradient);
  55. COLOR = zTextureSpec(SCREEN_TEXTURE, Pos + (offset * DistortionScalar));
  56. highp float cloudyZoom = pow(Zoom, CloudinessZoomPow);
  57. highp float cloudygradient = zCircleGradient(aspect, FRAGCOORD.xy, CloudinessGradientMax, CloudinessCenterRadius / cloudyZoom, CloudinessCenterMinDist / cloudyZoom, CloudinessCenterPow) * CloudinessScalar;
  58. COLOR.rgb = mix(COLOR.rgb, vec3(zGrayscale(COLOR.rgb)), min(cloudygradient * CloudinessGrayscaleMult, CloudinessGrayscaleMax));
  59. COLOR.rgb = pow(COLOR.rgb, vec3((cloudygradient * CloudinessScalar + 1.0)));
  60. //technically the highp makes this higher-quality than the actual default frag shader's lightmap sampling but shushhhhhh it looks prettier without as much banding
  61. highp vec3 lightsample = (texture2D(LIGHT_TEXTURE, Pos + (offset * DistortionScalar * CloudyLightDistortionMult)).rgb * pow(cloudygradient, 1.0 / CloudinessCenterPow) * CloudyLightMult);
  62. lightsample = mix(lightsample, vec3(zGrayscale(lightsample)), min(cloudygradient * CloudyLightGrayscaleMult, CloudyLightGrayscaleMax));
  63. COLOR.rgb = COLOR.rgb + lightsample;
  64. }