برچسب: realtime

  • Building a Real-Time Dithering Shader

    Building a Real-Time Dithering Shader


    In this post, we’ll take a closer look at the dithering-shader project: a minimal, real-time ordered dithering effect built using GLSL and the Post Processing library.

    Rather than just creating a one-off visual effect, the goal was to build something clean, composable, and extendable: a drop-in shader pass that brings pixel-based texture into modern WebGL pipelines.

    What It Does

    This shader applies ordered dithering as a postprocessing effect. It transforms smooth gradients into stylized, binary (or quantized) pixel patterns, simulating the visual language of early bitmap displays, dot matrix printers, and 8-bit games.

    It supports:

    • Dynamic resolution via pixelSize
    • Optional grayscale mode
    • Composability with bloom, blur, or other passes
    • Easy integration via postprocessing‘s Effect class

    Fragment Shader

    Our dithering shader implementation consists of two main components:

    1. The Core Shader

    The heart of the effect lies in the GLSL fragment shader that implements ordered dithering:

    bool getValue(float brightness, vec2 pos) {
    
    // Early return for extreme values
    if (brightness > 16.0 / 17.0) return false;
    if (brightness < 1.0 / 17.0) return true;
    
    // Calculate position in 4x4 dither matrix
    vec2 pixel = floor(mod(pos.xy / gridSize, 4.0));
    int x = int(pixel.x);
    int y = int(pixel.y);
    
    // 4x4 Bayer matrix threshold map
    // ... threshold comparisons based on matrix position
    
    }

    The getValue function is the core of the dithering algorithm. It:

    • Takes brightness and position: Uses the pixel’s luminance value and screen position
    • Maps to dither matrix: Calculates which cell of the 4×4 Bayer matrix the pixel belongs to
    • Applies threshold: Compares the brightness against a predetermined threshold for that matrix position
    • Returns binary decision: Whether the pixel should be black or colored

    Key Shader Features

    • gridSize: Controls the size of the dithering pattern
    • pixelSizeRatio: Adds pixelation effect for enhanced retro feel
    • grayscaleOnly: Converts the image to grayscale before dithering
    • invertColor: Inverts the final colors for different aesthetic effects

    2. Pixelation Integration

    float pixelSize = gridSize * pixelSizeRatio;
    vec2 pixelatedUV = floor(fragCoord / pixelSize) * pixelSize / resolution;
    baseColor = texture2D(inputBuffer, pixelatedUV).rgb;

    The shader combines dithering with optional pixelation, creating a compound retro effect that’s perfect for game-like visuals.

    Creating a Custom Postprocessing Effect

    The shader is wrapped using the Effect base class from the postprocessing library. This abstracts away the boilerplate of managing framebuffers and passes, allowing the shader to be dropped into a scene with minimal setup.

    export class DitheringEffect extends Effect {
      uniforms: Map<string, THREE.Uniform<number | THREE.Vector2>>;
    
      constructor({
        time = 0,
        resolution = new THREE.Vector2(1, 1),
        gridSize = 4.0,
        luminanceMethod = 0,
        invertColor = false,
        pixelSizeRatio = 1,
        grayscaleOnly = false
      }: DitheringEffectOptions = {}) {
        const uniforms = new Map<string, THREE.Uniform<number | THREE.Vector2>>([
          ["time", new THREE.Uniform(time)],
          ["resolution", new THREE.Uniform(resolution)],
          ["gridSize", new THREE.Uniform(gridSize)],
          ["luminanceMethod", new THREE.Uniform(luminanceMethod)],
          ["invertColor", new THREE.Uniform(invertColor ? 1 : 0)],
          ["ditheringEnabled", new THREE.Uniform(1)],
          ["pixelSizeRatio", new THREE.Uniform(pixelSizeRatio)],
          ["grayscaleOnly", new THREE.Uniform(grayscaleOnly ? 1 : 0)]
        ]);
    
        super("DitheringEffect", ditheringShader, { uniforms });
        this.uniforms = uniforms;
      }
    
     ...
    
    }

    Optional: Integrating with React Three Fiber

    Once defined, the effect is registered and applied using @react-three/postprocessing. Here’s a minimal usage example with bloom and dithering:

    <Canvas>
      {/* ... your scene ... */}
      <EffectComposer>
        <Bloom intensity={0.5} />
        <Dithering pixelSize={2} grayscale />
      </EffectComposer>
    </Canvas>

    You can also tweak pixelSize dynamically to scale the effect with resolution, or toggle grayscale mode based on UI controls or scene context.

    Extending the Shader

    This shader is intentionally kept simple, a foundation rather than a full system. It’s easy to customize or extend. Here are some ideas you can try:

    • Add color quantization: convert color.rgb to indexed palettes
    • Pack depth-based dither layers for fake shadows
    • Animate the pattern for VHS-like shimmer
    • Interactive pixelation: use mouse proximity to affect u_pixelSize

    Why Not Use a Texture?

    Some dithering shaders rely on threshold maps or pre-baked noise textures. This one doesn’t. The matrix pattern is deterministic and screen-space based, which means:

    • No texture loading required
    • Fully procedural
    • Clean pixel alignment

    It’s not meant for photorealism. It’s for styling and flattening. Think more zine than render farm.

    Final Thoughts

    This project started as a side experiment to explore what it would look like to bring tactile, stylized “non-photorealism” back into postprocessing workflows. But I found it had broader use cases, especially in cases where design direction favors abstraction or controlled distortion.

    If you’re building UIs, games, or interactive 3D scenes where “perfect” isn’t the goal, maybe a little pixel grit is exactly what you need.



    Source link

  • A Nightscout Segment for OhMyPosh shows my realtime Blood Sugar readings in my Git Prompt

    A Nightscout Segment for OhMyPosh shows my realtime Blood Sugar readings in my Git Prompt



    I’ve talked about how I love a nice pretty prompt in my Windows Terminal and made videos showing in detail how to do it. I’ve also worked with my buddy TooTallNate to put my real-time blood sugar into a bash or PowerShell prompt, but this was back in 2017.

    Now that I’m “Team OhMyPosh” I have been meaning to write a Nightscout “segment” for my prompt. Nightscout is an open source self-hosted (there are commercial hosts also like T1Pal) website and API for remote display of real-time and near-real-time glucose readings for Diabetics like myself.

    Since my body has an active REST API where I can just do an HTTP GET (via curl or whatever) and see my blood sugar, it clearly belongs in a place of honor, just like my current Git Branch!

    My blood sugar in my Prompt!

    Oh My Posh supports configurable “segments” and now there’s a beta (still needs mmol and stale readings support) Nightscout segment that you can setup in just a few minutes!

    This prompt works in ANY shell on ANY os! You can do this in zsh, PowerShell, Bash, whatever makes you happy.

    Here is a YouTube of Jan from OhMyPosh and I coding the segment LIVE in Go.

    https://www.youtube.com/watch?v=_meKUIm9NwA

    If you have an existing OhMyPosh json config, you can just add another segment like this. Make sure your Nightscout URL includes a secure Token or is public (up to you). Note also that I setup “if/then” rules in my background_templates. These are optional and up to you to change to your taste. I set my background colors to red, yellow, green depending on sugar numbers. I also have a foreground template that is not really used, as you can see it always evaluates to black #000, but it shows you how you could set it to white text on a darker background if you wanted.

    {
    "type": "nightscout",
    "style": "diamond",
    "foreground": "#ffffff",
    "background": "#ff0000",
    "background_templates": [
    "{{ if gt .Sgv 150 }}#FFFF00{{ end }}",
    "{{ if lt .Sgv 60 }}#FF0000{{ end }}",
    "#00FF00"
    ],
    "foreground_templates": [
    "{{ if gt .Sgv 150 }}#000000{{ end }}",
    "{{ if lt .Sgv 60 }}#000000{{ end }}",
    "#000000"
    ],

    "leading_diamond": "",
    "trailing_diamond": "\uE0B0",
    "properties": {
    "url": "https://YOURNIGHTSCOUTAPP.herokuapp.com/api/v1/entries.json?count=1&token=APITOKENFROMYOURADMIN",
    "http_timeout": 1500,
    "template": " {{.Sgv}}{{.TrendIcon}}"
    }
    },

    By default we will only go out and hit your Nightscout instance every 5 min, only when the prompt is repainted, and we’ll only wait 1500ms before giving up. You can set that “http_timeout” (how long before we give up) if you feel this slows you down. It’ll be cached for 5 min so it’s unlikely  to b something you’ll notice. The benefit of this new OhMyPosh segment over the previous solution is that it requires no additional services/chron jobs and can be setup extremely quickly. Note also that you can customize your template with NerdFonts. I’ve included a tiny syringe!

    What a lovely prompt with Blood Sugar!

    Next I’ll hope to improve the segment with mmol support as well as strikeout style for “stale” (over 15 min old) results. You’re also welcome to help out by watching our YouTube and submitting a PR!


    Sponsor: Make login Auth0’s problem. Not yours. Provide the convenient login features your customers want, like social login, multi-factor authentication, single sign-on, passwordless, and more. Get started for free.




    About Scott

    Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

    facebook
    bluesky
    subscribe
    About   Newsletter

    Hosting By
    Hosted on Linux using .NET in an Azure App Service










    Source link