struct Settings { colorA: vec3, backgroundColorPadding0: f32, colorB: vec3, backgroundColorPadding1: f32, colorC: vec3, backgroundColorPadding2: f32, backgroundColor: vec3, clarity: f32, traceNormalizationFloor: f32, brushColorBase: f32, brushColorStrengthMultiplier: f32, backgroundGrainStrength: f32, }; @group(1) @binding(0) var settings: Settings; @group(1) @binding(2) var trailMap: texture_2d; @group(1) @binding(3) var sourceMap: texture_2d; const NOISE_TEXTURE_MASK = 2047u; @fragment fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { let pixel = vec2(position.xy); let traces = textureLoad(trailMap, pixel, 0); let sources = textureLoad(sourceMap, pixel, 0); return renderColor(traces, sources, pixel); } @fragment fn fragmentNoSource(@builtin(position) position: vec4) -> @location(0) vec4 { let pixel = vec2(position.xy); let traces = textureLoad(trailMap, pixel, 0); return renderColor(traces, vec4(0.0), pixel); } fn renderColor(traces: vec4, sources: vec4, pixel: vec2) -> vec4 { let background = getTexturedBackground(pixel); let tracesMax = maxComponent(traces.rgb); let sourcesMax = maxComponent(sources.rgb); if max(tracesMax, sourcesMax) <= 0.0 { return vec4(background, 1); } let traceStrengths = vec3( clarity(traces.r), clarity(traces.g), clarity(traces.b) ); if sourcesMax <= 0.0 { let traceColor = traceStrengths.r * settings.colorA + traceStrengths.g * settings.colorB + traceStrengths.b * settings.colorC; let normalizedTraceColor = normalizeColorIntensity(traceColor); let traceStrength = maxComponent(traceStrengths); return vec4(mix(background, clamp(normalizedTraceColor, vec3(0), vec3(1)), traceStrength), 1); } let sourceStrengths = vec3( clarity(sources.r), clarity(sources.g), clarity(sources.b) ); let strengths = max(traceStrengths, sourceStrengths); let traceColor = strengths.r * settings.colorA + strengths.g * settings.colorB + strengths.b * settings.colorC; let normalizedTraceColor = normalizeColorIntensity(traceColor); let brushColor = sourceStrengths.r * settings.colorA + sourceStrengths.g * settings.colorB + sourceStrengths.b * settings.colorC; let normalizedBrushColor = normalizeColorIntensity(brushColor); let brushStrength = maxComponent(sourceStrengths); let brushVisibility = clamp( brushStrength * ( settings.brushColorBase + brushStrength * settings.brushColorStrengthMultiplier ), 0, 1 ); let color = max(normalizedTraceColor, normalizedBrushColor); let strength = max(maxComponent(strengths), brushVisibility); return vec4(mix(background, clamp(color, vec3(0), vec3(1)), strength), 1); } fn maxComponent(v: vec3) -> f32 { return max(max(v.r, v.g), v.b); } fn clarity(strength: f32) -> f32 { return pow(clamp(strength, 0, 1), settings.clarity); } fn normalizeColorIntensity(color: vec3) -> vec3 { let brightestChannel = maxComponent(color); return color / max(settings.traceNormalizationFloor, brightestChannel); } fn getTexturedBackground(pixel: vec2) -> vec3 { if settings.backgroundGrainStrength == 0.0 { return clamp(settings.backgroundColor, vec3(0), vec3(1)); } let noiseCoord = vec2(vec2(pixel) & vec2(NOISE_TEXTURE_MASK)); let grain = textureLoad(noise, noiseCoord, 0).r - 0.5; return clamp( settings.backgroundColor + vec3(grain * settings.backgroundGrainStrength), vec3(0), vec3(1) ); }