Skip to content

Instantly share code, notes, and snippets.

@depp
Created December 1, 2022 01:12
Show Gist options
  • Save depp/f4dc0d50c22f28f3b6585725219d7eb8 to your computer and use it in GitHub Desktop.
Save depp/f4dc0d50c22f28f3b6585725219d7eb8 to your computer and use it in GitHub Desktop.
1-bit Ordered Dithering in WebGL

Dithering

Shows how to get a 1-bit dithering effect in WebGL.

I disclaim copyright on this code, feel free to use it without attribution.

The TypeScript file shows how the texture is generated. The GLSL code shows how it is used in a fragment shader.

#version 100
precision mediump float;
// Bayer texture for dithering.
uniform sampler2D Dither;
// Scaling factor to convert to coordinates in the dither texture.
// If the dither texture is x pixels across, then this value is 1/x.
// For example, 4x4 texture -> DitherScale = 0.25.
uniform float DitherScale;
// The color palette for black & white.
uniform vec3 Palette[2];
void main() {
float value = /* calculate grayscale value */;
// This is scaled to 0.01-0.99 in order to get cleaner black and white
// values. Without this scaling factor, less of the picture will be
float dither = texture2D(Dither, gl_FragCoord.xy * DitherScale).r * 0.98 + 0.01;
gl_FragColor = vec4(mix(Palette[0], Palette[1], step(dither, value)), 1.0);
}
/**
* Create a Bayer matrix.
*
* @param size The base 2 logarithm of the matrix size. Should be in the range 0-4.
* @return A flat array containing the Bayer matrix, with coefficients scaled to 0-255.
*/
function BayerMatrix(size: number): Uint8Array {
const dim = 1 << size;
const arr = new Uint8Array(dim * dim);
if (size > 0) {
const sub = BayerMatrix(size - 1);
const subdim = dim >> 1;
const delta = size <= 4 ? (1 << (2 * (4 - size))) : 0;
for (let y = 0; y < subdim; y++) {
for (let x = 0; x < subdim; x++) {
const val = sub[y*subdim+x];
const idx = y*dim+x;
arr[idx] = val;
arr[idx+subdim] = val+2*delta;
arr[idx+subdim*dim] = val+3*delta;
arr[idx+subdim*(dim+1)] = val+1*delta;
}
}
}
return arr;
}
/** Bayer ordered dithering texture. */
let BayerTexture: WebGLTexture | null = null;
/** Scaling factor for converting pixel */
let DitherScalingFactor: number = 0;
/** Make the Bayer dithering texture. */
function InitBayerTexture(gl: WebGLRenderingContext): void {
const size = 2;
const dim = 1 << size;
if (BayerTexture == null) {
BayerTexture = gl.createTexture();
if (BayerTexture == null) {
throw new Error('Could not create texture');
}
}
const data = BayerMatrix(size);
gl.bindTexture(gl.TEXTURE_2D, BayerTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dim, dim, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.bindTexture(gl.TEXTURE_2D, null);
DitherScalingFactor = 1 / dim;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment