Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/project516/retroview/llms.txt

Use this file to discover all available pages before exploring further.

This page documents the full render pass architecture of retroview for developers and curious users who want to understand how the retro visual effect is constructed — or who want to modify the shader for their own needs. Passes run in the order listed below, each reading from and writing to named color buffers (colortex0colortex2).

Buffer layout

BufferFormatContents
colortex0RGB16Scene color (written by gbuffers, read by every subsequent pass)
colortex1defaultLightmap UV (lmcoord.xy) — blocklight X, skylight Y
colortex2defaultEncoded surface normal (normal * 0.5 + 0.5)
shadowtex0depthShadow map including transparent geometry
shadowtex1depthShadow map for opaque geometry only
shadowcolor0colorTinted shadow color for stained glass, etc.

Render passes

1

gbuffers passes — geometry to G-buffers

The gbuffers family of shaders runs first, once per rendered object type. They write raw scene data into the three color buffers that all later passes read from.gbuffers_terrain handles opaque blocks. It writes to all three render targets:
/* RENDERTARGETS: 0,1,2 */
layout(location = 0) out vec4 color;
layout(location = 1) out vec4 lightLevelData;
layout(location = 2) out vec4 encodedNormal;

void main() {
    color = texture(gtexture, texcoord) * glcolor;

    lightLevelData = vec4(lmcoord, 0.0, 1.0);
    encodedNormal = vec4(normal * 0.5 + 0.5, 1.0);
    if (color.a < alphaTestRef) {
        discard;
    }
}
gbuffers_water handles translucent geometry (water, ice). It only writes color (RENDERTARGETS: 0) and applies the lightmap directly in the fragment shader rather than deferring it:
void main() {
    color = texture(gtexture, texcoord) * glcolor;
    color *= texture(lightmap, lmcoord);
    if (color.a < alphaTestRef) {
        discard;
    }
}
gbuffers_entities handles mobs and items, and additionally supports the entity flash color (used for hurt/invincibility frames):
void main() {
    color = texture(gtexture, texcoord) * glcolor;
    color.rgb = mix(color.rgb, entityColor.rgb, entityColor.a);
    color *= texture(lightmap, lmcoord);
    lightLevelData = vec4(lmcoord, 0.0, 1.0);
    encodedNormal = vec4(normal * 0.5 + 0.5, 1.0);
    if (color.a < alphaTestRef) {
        discard;
    }
}
2

shadow pass — depth map from the light's perspective

The shadow pass renders the scene from the sun/moon’s point of view into the shadow buffers. retroview uses a 2048×2048 shadow map (shadowMapResolution = 2048):
const int shadowMapResolution = 2048;

void main() {
    color = texture(gtexture, texcoord) * glcolor;
    if (color.a < 0.1) {
        discard;
    }
}
Shadow clip-space positions are warped by lib/shadowDistort.glsl before sampling. The distortion compresses the outer regions of the shadow map, trading far-field precision for better resolution near the player:
vec3 distortShadowClipPos(vec3 shadowClipPos) {
    float distortionFactor = length(shadowClipPos.xy);
    distortionFactor += 0.1;

    shadowClipPos.xy /= distortionFactor;
    shadowClipPos.z *= 0.5;
    return shadowClipPos;
}
The composite pass applies this distortion before sampling shadowtex0 / shadowtex1:
shadowClipPos.z -= 0.001;
shadowClipPos.xyz = distortShadowClipPos(shadowClipPos.xyz);
vec3 shadowNdcPos = shadowClipPos.xyz / shadowClipPos.w;
vec3 shadowScreenPos = shadowNdcPos * 0.5 + 0.5;
The 0.001 z-bias prevents self-shadowing (shadow acne) on flat surfaces.
3

deferred pass — passthrough

deferred.fsh is a simple passthrough that copies colortex0 unchanged. It exists as an architectural hook — the deferred stage runs after all opaque geometry but before the composite passes, making it the correct place to insert screen-space effects that need a complete opaque scene (SSAO, screen-space reflections, etc.):
void main() {
    color = texture(colortex0, texcoord);
}
4

composite — main retro pass

composite.fsh is where all of retroview’s signature effects are applied. It reads the G-buffer data, reconstructs lighting, and applies the full retro filter stack in a fixed order.All effect strengths are driven by the uniforms declared at the top of the file with their defaults:
uniform float retroLevels          = 31.0;
uniform float retroDitherStrength  = 1.0;
uniform float retroScanStrength    = 0.08;
uniform float retroVignetteStrength = 0.25;
uniform float retroVignetteInner   = 0.35;
uniform float retroVignetteOuter   = 0.90;
uniform float retroPixelScale      = 5.0;
Pixelation via UV snappingThe texcoord is snapped to a coarser grid by flooring to multiples of retroPixelScale pixels, then sampling colortex0 at that snapped UV. This is what makes the image look lower-resolution without actually changing the render resolution:
vec2 screenSize = vec2(textureSize(colortex0, 0));
vec2 lowUV = floor(texcoord * screenSize / retroPixelScale) * retroPixelScale / screenSize;
color = texture(colortex0, lowUV);
color.rgb = pow(color.rgb, vec3(2.2));
The pow(color.rgb, 2.2) converts the sampled sRGB texture into linear light for correct lighting math.Lighting reconstructionThe lightmap values from colortex1 and the encoded normal from colortex2 are used to reconstruct per-pixel lighting. The world-space position is reconstructed from depth to compute shadow coordinates:
vec3 blocklight = lightmap.x * blocklightColor;
vec3 skylight   = lightmap.y * skylightColor;
vec3 ambient    = ambientColor;
vec3 sunlight   = sunlightColor * clamp(dot(worldLightVector, normal), 0.0, 1.0) * shadow;

color.rgb *= blocklight + skylight + ambient + sunlight;
The light colors are compile-time constants:
const vec3 blocklightColor = vec3(1.0, 0.5, 0.08);
const vec3 skylightColor   = vec3(0.05, 0.15, 0.3);
const vec3 sunlightColor   = vec3(1.0);
const vec3 ambientColor    = vec3(0.1);
Bayer 4×4 ordered ditheringAfter lighting, the linear color is converted back to sRGB for quantization. A Bayer 4×4 threshold matrix adds a structured offset before quantizing to retroLevels discrete steps, breaking up color banding:
const int BAYER4[16] = int[16]
(0,8,2,10,12,4,14,6,3,11,1,9,15,7,13,5);

float bayer4x4(int ix, int iy) {
    return (BAYER4[iy * 4 + ix] + 0.5) / 16.0;
}
vec3 srgb = pow(clamp(color.rgb, 0.0, 1.0), vec3(1.0 / 2.2));

int ix = int(gl_FragCoord.x) & 3;
int iy = int(gl_FragCoord.y) & 3;
float b = bayer4x4(ix, iy);
float offset = (b - 0.5) * (retroDitherStrength / retroLevels);

vec3 q = floor((srgb + vec3(offset)) * retroLevels + 0.5) / retroLevels;

color.rgb = pow(clamp(q, 0.0, 1.0), vec3(2.2));
The quantized result is converted back to linear (pow(..., 2.2)) so the remaining effects operate in linear light.Scanline darkeningEvery other row of pixels is attenuated by retroScanStrength. The step function selects which rows are darkened:
float rowMod = mod(gl_FragCoord.y, 2.0);
float scanFactor = mix(1.0 - retroScanStrength, 1.0, step(1.0, rowMod));
color.rgb *= scanFactor;
VignetteA radial darkening is applied using smoothstep between retroVignetteInner and retroVignetteOuter (both in normalized UV distance from screen center):
float d = distance(texcoord, vec2(0.5));
float v = smoothstep(retroVignetteInner, retroVignetteOuter, d);
color.rgb *= 1.0 - retroVignetteStrength * v;
Color bleedA faint chromatic right-shift is added by sampling colortex0 at a slightly offset UV and blending it in. This emulates the horizontal color bleeding of CRT phosphors:
vec3 bleed = texture(colortex0, lowUV + vec2(0.001, 0.0)).rgb * 0.1;
color.rgb += bleed * 0.5;
5

composite1 — atmospheric fog blending

composite1.fsh reconstructs view-space depth from depthtex0 and blends the scene color toward fogColor using an exponential fog curve. FOG_DENSITY is a compile-time constant:
const float FOG_DENSITY = 5.0;

void main() {
    color = texture(colortex0, texcoord);

    float depth = texture(depthtex0, texcoord).r;
    if (depth == 1.0) {
        return;
    }

    vec3 ndcPos = vec3(texcoord.xy, depth) * 2.0 - 1.0;
    vec3 viewPos = projectAndDivide(gbufferProjectionInverse, ndcPos);

    float dist = length(viewPos) / far;
    float fogFactor = exp(-FOG_DENSITY * (1.0 - dist));

    color.rgb = mix(color.rgb, pow(fogColor, vec3(2.2)), clamp(fogFactor, 0.0, 1.0));
}
Sky pixels (depth == 1.0) are skipped — fog is only applied to scene geometry.
6

final — sRGB gamma correction output

final.fsh converts the linear-light result from all composite passes into the sRGB color space required by the display. This is the only operation it performs:
void main() {
    color = texture(colortex0, texcoord);
    color.rgb = pow(color.rgb, vec3(1.0 / 2.2));
}
All intermediate passes work in linear light; final.fsh applies the gamma curve exactly once at the end of the pipeline.

Uniform reference

All tunable uniforms are declared in composite.fsh with their default values. Iris shader packs expose these via shaders.properties or #define blocks for in-game sliders.
UniformDefaultEffect
retroLevels31.0Number of discrete color steps per channel
retroPixelScale5.0Pixel block size in screen pixels
retroDitherStrength1.0Dither offset magnitude relative to one color step
retroScanStrength0.08Fraction of brightness removed from even scanline rows
retroVignetteStrength0.25Peak darkening at screen edges
retroVignetteInner0.35UV distance from center where vignette begins
retroVignetteOuter0.90UV distance from center where vignette reaches full strength
retroview is licensed under GPL-3.0. You are free to modify and redistribute it with attribution and under the same license.The files most commonly modified and what they control:
FileWhat to change here
shaders/composite.fshAll retro effect parameters and their defaults; lighting color constants; pixelation, dithering, scanlines, vignette, color bleed
shaders/composite1.fshFog density (FOG_DENSITY) and fog falloff curve
shaders/final.fshGamma exponent for final output (default 1/2.2 for sRGB)
shaders/deferred.fshInsert screen-space effects that need the full opaque scene (SSAO, etc.)
shaders/gbuffers_terrain.fshOpaque block rendering and G-buffer layout
shaders/gbuffers_water.fshTranslucent geometry rendering
shaders/gbuffers_entities.fshEntity rendering, including hurt flash
shaders/lib/shadowDistort.glslShadow map distortion function — adjust 0.1 bias to trade near/far shadow precision
To add a new tunable parameter, declare it as a uniform float with a default value in composite.fsh and expose it via shaders.properties for an in-game slider.
Removing the pow(color.rgb, 2.2) linearization step at the start of the composite pass or the pow(..., vec3(1.0 / 2.2)) in final.fsh will cause visually incorrect lighting. Always maintain consistent color space throughout the pipeline.

Build docs developers (and LLMs) love