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; | |
} |