Add dithering

This commit is contained in:
Andras Schmelczer 2026-06-10 20:53:15 +01:00
parent e4ee421628
commit b355132861
3 changed files with 42 additions and 4 deletions

View file

@ -51,6 +51,7 @@ export class RendererImplementation implements Renderer {
private _canvasSize: vec2; private _canvasSize: vec2;
private blendFactor!: number; private blendFactor!: number;
private canvasResizeObserver!: ResizeObserver; private canvasResizeObserver!: ResizeObserver;
private frameIndex = 0;
private applyRuntimeSettings: { private applyRuntimeSettings: {
[key in keyof RuntimeSettings]: (value: any) => void; [key in keyof RuntimeSettings]: (value: any) => void;
@ -247,6 +248,18 @@ export class RendererImplementation implements Renderer {
distanceNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()), distanceNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()),
shadingNdcPixelSize: 2 / Math.max(...this.lightingFrameBuffer.getSize()), shadingNdcPixelSize: 2 / Math.max(...this.lightingFrameBuffer.getSize()),
// The motion-blur blend multiplies the lights pass' output by
// blendFactor before it reaches the 8-bit canvas, which would attenuate
// the anti-banding dither below the quantization step it has to mask.
// Pre-scale the dither to arrive at full strength after blending; the
// clamp keeps extreme motion-blur values from injecting visible grain.
ditherStrength: lightsSizeChanged ? 1 : 1 / Math.max(this.blendFactor, 0.125),
// Scroll the dither pattern every frame (golden-ratio offset), so the
// motion-blur blend averages independently dithered frames instead of
// freezing into a banded fixed point.
ditherSeed: (this.frameIndex = (this.frameIndex + 1) & 63) * 5.588238,
}; };
this.distancePass.render(this.uniformsProvider.getUniforms(common), [ this.distancePass.render(this.uniformsProvider.getUniforms(common), [

View file

@ -1,6 +1,10 @@
#version 100 #version 100
precision lowp float; #ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define INTENSITY_INSIDE_RATIO {intensityInsideRatio} #define INTENSITY_INSIDE_RATIO {intensityInsideRatio}
#define SHADOW_TRACE_COUNT {shadowTraceCount} #define SHADOW_TRACE_COUNT {shadowTraceCount}
@ -11,6 +15,8 @@ precision lowp float;
{macroDefinitions} {macroDefinitions}
uniform float shadingNdcPixelSize; uniform float shadingNdcPixelSize;
uniform float ditherStrength;
uniform float ditherSeed;
uniform vec2 squareToAspectRatioTimes2; uniform vec2 squareToAspectRatioTimes2;
uniform vec3 ambientLight; uniform vec3 ambientLight;
@ -62,6 +68,14 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2
#endif #endif
#endif #endif
float ditherNoise() {
float ign = fract(52.9829189 * fract(
dot(gl_FragCoord.xy + ditherSeed, vec2(0.06711056, 0.00583715))
));
float t = 2.0 * ign - 1.0;
return sign(t) * (1.0 - sqrt(1.0 - abs(t)));
}
void main() { void main() {
vec4 rgbaColorAtPosition; vec4 rgbaColorAtPosition;
float startingDistance = getDistance(uvCoordinates, rgbaColorAtPosition); float startingDistance = getDistance(uvCoordinates, rgbaColorAtPosition);
@ -144,7 +158,7 @@ void main() {
float edge = clamp(startingDistance / shadingNdcPixelSize, 0.0, 1.0); float edge = clamp(startingDistance / shadingNdcPixelSize, 0.0, 1.0);
vec3 antialiasedColor = mix(insideColor, outsideColor, edge); vec3 antialiasedColor = mix(insideColor, outsideColor, edge);
gl_FragColor = vec4( gl_FragColor = vec4(
antialiasedColor, antialiasedColor + ditherNoise() * ditherStrength / 255.0,
rgbaColorAtPosition.a rgbaColorAtPosition.a
); );
} }

View file

@ -1,6 +1,7 @@
#version 300 es #version 300 es
precision lowp float; precision highp float;
precision highp sampler2D;
#define INTENSITY_INSIDE_RATIO {intensityInsideRatio} #define INTENSITY_INSIDE_RATIO {intensityInsideRatio}
#define SHADOW_TRACE_COUNT {shadowTraceCount} #define SHADOW_TRACE_COUNT {shadowTraceCount}
@ -13,6 +14,8 @@ precision lowp float;
uniform float shadingNdcPixelSize; uniform float shadingNdcPixelSize;
uniform float distanceNdcPixelSize; uniform float distanceNdcPixelSize;
uniform float ditherStrength;
uniform float ditherSeed;
uniform vec2 squareToAspectRatioTimes2; uniform vec2 squareToAspectRatioTimes2;
uniform vec3 ambientLight; uniform vec3 ambientLight;
@ -83,6 +86,14 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2
#endif #endif
#endif #endif
float ditherNoise() {
float ign = fract(52.9829189 * fract(
dot(gl_FragCoord.xy + ditherSeed, vec2(0.06711056, 0.00583715))
));
float t = 2.0 * ign - 1.0;
return sign(t) * (1.0 - sqrt(1.0 - abs(t)));
}
out vec4 fragmentColor; out vec4 fragmentColor;
void main() { void main() {
vec4 rgbaColorAtPosition = getColor(uvCoordinates); vec4 rgbaColorAtPosition = getColor(uvCoordinates);
@ -167,7 +178,7 @@ void main() {
float edge = clamp(startingDistance / shadingNdcPixelSize, 0.0, 1.0); float edge = clamp(startingDistance / shadingNdcPixelSize, 0.0, 1.0);
vec3 antialiasedColor = mix(insideColor, outsideColor, edge); vec3 antialiasedColor = mix(insideColor, outsideColor, edge);
fragmentColor = vec4( fragmentColor = vec4(
antialiasedColor, antialiasedColor + ditherNoise() * ditherStrength / 255.0,
rgbaColorAtPosition.a rgbaColorAtPosition.a
); );
} }