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