promotional bannermobile promotional banner

Dithering3D

Pixelated, retro, Dark Fantasy, gradient, B&W, RGB and more

File Details

Dithering3D-V0.5

  • R
  • May 15, 2026
  • 56.12 KB
  • 14.3K
  • 1.21.8+101

File Name

Dithering3D-V0.5.zip

Supported Versions

  • 1.16-Snapshot
  • 1.15-Snapshot
  • 1.19-Snapshot
  • 1.10-Snapshot
  • 1.17-Snapshot
  • 1.8-Snapshot
  • 1.21.2-Snapshot
  • 1.21.6-snapshot
  • 1.21.1-Snapshot
  • 1.21.5-Snapshot
  • 1.21.8
  • 1.21.5
  • 1.21.4
  • 1.21
  • 1.20.2-Snapshot
  • 1.20.5-Snapshot
  • 1.20.6
  • 1.20.5
  • 1.20.2
  • 1.20.1
  • 1.20
  • 1.19.4-Snapshot
  • 1.19.4
  • 1.19.3
  • 1.18.1
  • 1.18
  • 1.17.1
  • 1.17
  • 1.16.5
  • 1.16.3
  • 1.16.1
  • 1.16
  • 1.15
  • 1.14.4
  • 1.14.3
  • 1.14.1
  • 1.13.1
  • 1.13
  • 1.12.1
  • 1.12
  • 1.11
  • 1.10.2
  • 1.9.4
  • 1.9.3
  • 1.9.2
  • 1.9.1
  • 1.8.8
  • 1.8.7
  • 1.8.4
  • 1.8.3
  • OptiFine
  • 26.1-snapshot
  • 26.1.2
  • 26.1.1
  • 26.1
  • 1.20-Snapshot
  • 1.21-Snapshot
  • 1.18-Snapshot
  • 1.13-Snapshot
  • 1.14-Snapshot
  • 1.12-Snapshot
  • 1.11-Snapshot
  • 1.9-Snapshot
  • 1.21.4-Snapshot
  • 1.21.9-snapshot
  • 1.21.11-snapshot
  • 1.21.11
  • 1.21.10
  • 1.21.9
  • 1.21.7
  • 1.21.6
  • 1.21.3
  • 1.21.2
  • 1.21.1
  • 1.20.3-Snapshot
  • 1.20.4
  • 1.20.3
  • 1.19.3-Snapshot
  • 1.19.2
  • 1.19.1
  • 1.19
  • 1.18.2
  • 1.16.4
  • 1.16.2
  • 1.15.2
  • 1.15.1
  • 1.14.2
  • 1.14
  • 1.13.2
  • 1.12.2
  • 1.11.2
  • 1.11.1
  • 1.10.1
  • 1.10
  • 1.9
  • 1.8.9
  • 1.8.6
  • 1.8.5
  • 1.8.2
  • 1.8.1
  • 1.8
  • Iris

Dither3D Shader — Audit & Fix Report

Date: 2026-05-15

Scope: Full codebase integrity review against original Dither3DInclude.cginc


1. BUG FIXES

1.1 — CRITICAL: Sky double exposure/offset

File: shaders/gbuffers_skybasic.fsh:37 Severity: High — visual blowout at any non-default exposure/offset

applyDither3DColor() already applies DITHER_EXPOSURE * color + DITHER_OFFSET internally (line 53 of dither3d_color.glsl). The sky shader was pre-applying the same transform:

// BEFORE (bug):
vec3 skyColor = clamp(glcolor.rgb * DITHER_EXPOSURE + DITHER_OFFSET, 0.0, 1.0);
vec3 result = applyDither3DColor(skyUV, screenPos, dx, dy, skyColor);
// Result: exposure and offset squared (over-exposed sky)

// AFTER (fix):
vec3 result = applyDither3DColor(skyUV, screenPos, dx, dy, glcolor.rgb);

All other gbuffer fragment shaders pass raw colors — only gbuffers_skybasic.fsh had this issue.

1.2 — MEDIUM: Invalid SVD frequency test

File: tests/test_math.py:15-30 Severity: Medium — test was validating a different algorithm than the shader uses

The Python compute_uv_frequency() computed sqrt(sqrt(lambda1 * lambda2)) — a geometric mean of eigenvalue square roots — instead of the SVD singular values the shader actually computes. The shader's algorithm:

float Q = dot(dx,dx) + dot(dy,dy);          // Frobenius norm squared
float R = dx.x*dy.y - dx.y*dy.x;            // determinant
float disc = sqrt(Q*Q - 4.0*R*R);
vec2 freq = sqrt(vec2(Q + disc, Q - disc) * 0.5);  // singular values (sigma_1, sigma_2)

The test now matches this exactly with three validated cases:

  • Isotropic (uniform UV scale): both singular values equal scale ✓
  • Anisotropic (stretched UV): singular values equal individual axis scales ✓
  • Rotated (45°): singular values invariant under rotation ✓

Also fixed: Unicode characters (checkmarks, arrows) replaced with ASCII for Windows console compatibility.


2. CODE QUALITY IMPROVEMENTS

2.1 — Clarified dotRadius formula

File: shaders/lib/dither3d_core.glsl:98

// BEFORE (confusing):
float dotRadius = 0.25 / sqrt(activeDots * 0.25);

// AFTER (clear):
float dotRadius = 0.5 / sqrt(activeDots);

Both expressions are mathematically identical. The new form directly shows the relationship: radius scales as 0.5 / sqrt(N), giving 0.125 for 16 dots and 0.25 for 4 dots.

2.2 — PALETTE_COLOR_MATCH default changed to Color-Aware

Files: shaders/lib/dither3d_options.glsl:41, shaders/lib/dither3d_config.glsl:63

#define PALETTE_COLOR_MATCH 1  // was 0

Color-aware palette matching preserves hues by finding the two closest palette colors and dithering between them. This gives better results for colored palettes (CGA, Pico-8, Nord, Eldritch). The GAMEBOY profile explicitly overrides to 0 (luminance-based) for authentic retro look.


3. ARCHITECTURE VERIFICATION

3.1 — Include chain (correct)

Fragment shader
  -> dither3d_options.glsl     (defines all macros first)
  -> dither3d_color.glsl
       -> dither3d_core.glsl
            -> dither3d_config.glsl   (fallback defaults via #ifndef)
       -> dither3d_utils.glsl
       -> dither3d_palettes.glsl
            -> dither3d_utils.glsl    (guard prevents double include)

All 6 modules have unique, consistent include guards: | Module | Guard | |--------|-------| | dither3d_options.glsl | DITHER3D_OPTIONS_GLSL | | dither3d_config.glsl | DITHER3D_CONFIG_GLSL | | dither3d_core.glsl | DITHER3D_CORE_GLSL | | dither3d_utils.glsl | DITHER3D_UTILS_GLSL | | dither3d_palettes.glsl | DITHER3D_PALETTES_GLSL | | dither3d_color.glsl | DITHER3D_COLOR_GLSL |

3.2 — Vertex shaders (all 14 pairs verified)

All vertex shaders correctly compute and pass screenPos = gl_Position for radial compensation. World position and normal calculations are consistent across all geometry types:

  • Terrain, Entities, Block, Hand, Water: triplanar UV with world normal
  • Basic, Clouds, Weather, Armor Glint, Spider Eyes: simple world UV (no normal available)
  • Sky Basic: cylindrical UV with view direction and alternate UV seam handling
  • Sky Textured: texture atlas UV (scaled x4 for dithering)
  • Beacon Beam: XZ plane with vertical offset

3.3 — Fragment shaders (all 14 pairs verified)

All fragment shaders follow the same pattern:

  1. Include dither3d_options.glsl (first, defines macros)
  2. Include dither3d_color.glsl (pulls in everything)
  3. Sample textures, compute color
  4. Call applyDither3DColorSimple() or applyDither3DColor()
  5. Output to gl_FragData[0] with proper alpha preservation

3.4 — Shader properties (validated)

  • shaders.properties: 14 RENDER_STYLE values, 8 profiles, proper slider ranges, nested submenu structure
  • lang/en_US.lang: all options, values, profiles, and screen names have labels
  • Custom palette sliders (colors 1-8, 24 RGB sliders) all wired through shaders.propertiesdither3d_options.glsldither3d_config.glsl

3.5 — Palette system (validated)

  • 9 built-in palettes: 1-Bit(2), GameBoy(4), CGA(4), VirtualBoy(4), Sepia(4), Nord(8), Solarized(8), Pico-8(16), Eldritch(16)
  • 3 custom modes: 2-color, 4-color, 8-color via user-adjustable RGB sliders
  • Dual matching: luminance-based (fast, crisp) and color-aware (preserves hues)
  • All palettes sorted by luminance for correct dithering gradient
  • getPaletteColor() centralizes all access with clamp() bounds safety

3.6 — CMYK halftone (validated)

  • 4 plates with correct traditional screen angles (15°, 75°, 0°, 45°)
  • Slight scale variation per plate breaks up moire patterns
  • Round-trip conversion matches original HLSL (validated by test)

4. ORIGINAL PORT FIDELITY

Original (HLSL) Port (GLSL) Status
SVD frequency analysis Identical math (Q, R, discriminant) Match
Fractal level selection log2(spacing) + floor() Match
Sublayer calculation lerp(0.25*dotsTotal, dotsTotal, 1-f) Match
Dither pattern generation Procedural circular dots (replaces 3D texture) Adapted
Contrast application Same formula with adjusted multiplier (0.15 vs 0.1) Calibrated
Brightness ramp Simplified (no ramp texture lookup) Adapted
Radial compensation gbufferProjection instead of UNITY_MATRIX_P Adapted
GetDither3DAltUV Identical derivative comparison logic Match
CMYK conversion Identical math Match
UV rotation Identical Match

Known differences (intentional adaptions for Minecraft):

  • No 3D texture: replaced with procedural Bayer-pattern circular dots
  • No brightness ramp texture: direct clamping used instead
  • Contrast multiplier: 0.15 vs 0.1 (compensates for procedural pattern difference)
  • contrastFade formula: 1/(1+c*0.5) vs 1.05/(1+c) (recalibrated for procedural dots)
  • RENDER_STYLE 0 (RGB) uses small UV offsets instead of original's per-channel dithering

5. TEST RESULTS

python tests/test_math.py
======================================================================
Dither3D Mathematical Function Tests
======================================================================

--- SVD Frequency Computation (matches dither3d_core.glsl) ---
PASS Isotropic test:  maxFreq=2.000, minFreq=2.000, stretch=1.000
PASS Anisotropic test: maxFreq=4.000, minFreq=1.000, stretch=0.250
PASS Rotated test:     maxFreq=2.000, minFreq=2.000, stretch=1.000

--- CMYK Color Conversion ---
PASS Red, Green, Blue, Yellow, Magenta, Cyan, White, Black (8/8)

--- Bayer Matrix Generation ---
PASS Bayer 1x1, 2x2, 4x4, 8x8 (4/4)

======================================================================
All tests passed!
======================================================================

6. SUMMARY

Category Count
Critical bugs fixed 1 (sky double exposure)
Invalid tests fixed 1 (SVD algorithm mismatch)
Code clarity improvements 1 (dotRadius formula)
Default value changes 1 (PALETTE_COLOR_MATCH 0→1)
Files audited 37 (lib + gbuffers + composite + final + config)
Architecture issues found 0

Verdict: Codebase is stable, well-structured, and faithful to the original algorithm.