diff --git a/src/graphics/rendering/renderer/renderer-implementation.ts b/src/graphics/rendering/renderer/renderer-implementation.ts index f5b8480..d7fe550 100644 --- a/src/graphics/rendering/renderer/renderer-implementation.ts +++ b/src/graphics/rendering/renderer/renderer-implementation.ts @@ -167,6 +167,7 @@ export class RendererImplementation implements Renderer { { shadowTraceCount: settings.shadowTraceCount.toString(), intensityInsideRatio: settings.lightPenetrationRatio, + lightOverlapReduction: settings.lightOverlapReduction, floatLinearEnabled: this.gl.insights.floatInterpolationEnabled ? '1' : '0', backgroundColor: colorToString(settings.backgroundColor), } diff --git a/src/graphics/rendering/settings/default-startup-settings.ts b/src/graphics/rendering/settings/default-startup-settings.ts index 85b2a94..0493cdd 100644 --- a/src/graphics/rendering/settings/default-startup-settings.ts +++ b/src/graphics/rendering/settings/default-startup-settings.ts @@ -10,6 +10,7 @@ export const defaultStartupSettings: StartupSettings = { ignoreWebGL2: false, backgroundColor: vec4.fromValues(1, 1, 1, 1), lightPenetrationRatio: 0.75, + lightOverlapReduction: 0, enableStopwatch: false, enableContextLostSimulator: false, }; diff --git a/src/graphics/rendering/settings/startup-settings.ts b/src/graphics/rendering/settings/startup-settings.ts index 5cee633..6447fbb 100644 --- a/src/graphics/rendering/settings/startup-settings.ts +++ b/src/graphics/rendering/settings/startup-settings.ts @@ -32,6 +32,19 @@ export interface StartupSettings { */ lightPenetrationRatio: number; + /** + * Controls how overlapping lights combine. + * + * At `0` lights are summed additively (the physically-correct default): + * two nearby lights brighten their overlap and their glows merge into a + * shape larger than either alone. At `1` overlapping lights instead take + * the per-channel maximum, so a combination never reads brighter or larger + * than its strongest contributor. Values in between blend the two. + * + * A single light looks the same at any value. Should be between 0 and 1. + */ + lightOverlapReduction: number; + /** * Gives the number of possible object colors for the scene. * diff --git a/src/graphics/rendering/shaders/shading-fs-100.glsl b/src/graphics/rendering/shaders/shading-fs-100.glsl index 34be6a7..7d1756d 100644 --- a/src/graphics/rendering/shaders/shading-fs-100.glsl +++ b/src/graphics/rendering/shaders/shading-fs-100.glsl @@ -4,6 +4,9 @@ precision lowp float; #define INTENSITY_INSIDE_RATIO {intensityInsideRatio} #define SHADOW_TRACE_COUNT {shadowTraceCount} +// 0.0 == lights add (overlaps brighten & merge); 1.0 == lights take the +// per-channel max (overlaps never exceed the brighter light). See main(). +#define LIGHT_OVERLAP_REDUCTION float({lightOverlapReduction}) {macroDefinitions} @@ -65,8 +68,15 @@ void main() { vec3 colorAtPosition = rgbaColorAtPosition.rgb; - vec3 lighting = ambientLight; - vec3 lightingInside = ambientLight; + // Each light contributes to two accumulators: an additive sum (overlaps + // brighten and glows bridge into one another) and a per-channel max + // (overlaps read as just the brightest light). LIGHT_OVERLAP_REDUCTION + // blends between them. Ambient stays a separate additive base, so a single + // light is identical to pure-additive (sum == max for one light). + vec3 lightSum = vec3(0.0); + vec3 lightMax = vec3(0.0); + vec3 lightSumInside = vec3(0.0); + vec3 lightMaxInside = vec3(0.0); #ifdef CIRCLE_LIGHT_COUNT #if CIRCLE_LIGHT_COUNT > 0 @@ -78,13 +88,16 @@ void main() { ); vec2 direction = normalize(circleLightCenters[i] - position) / squareToAspectRatioTimes2; - lighting += lightColorAtPosition * shadowTransparency( - startingDistance, - lightCenterDistance, + vec3 shadowed = lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, direction ); - lightingInside += lightColorAtPosition; + lightSum += shadowed; + lightMax = max(lightMax, shadowed); + lightSumInside += lightColorAtPosition; + lightMaxInside = max(lightMaxInside, lightColorAtPosition); } #endif #endif @@ -96,7 +109,7 @@ void main() { float lightCenterDistance = distance(flashlightCenters[i], position); - vec3 lightColorAtPosition = intensityInDirection(flashlightDirections[i], originalDirection) + vec3 lightColorAtPosition = intensityInDirection(flashlightDirections[i], originalDirection) * flashlightColors[i] / pow( lightCenterDistance / flashlightIntensities[i] + 1.0, 2.0 ); @@ -107,17 +120,24 @@ void main() { continue; } - lighting += lightColorAtPosition * shadowTransparency( - startingDistance, - lightCenterDistance, + vec3 shadowed = lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, direction ); - lightingInside += lightColorAtPosition; + lightSum += shadowed; + lightMax = max(lightMax, shadowed); + lightSumInside += lightColorAtPosition; + lightMaxInside = max(lightMaxInside, lightColorAtPosition); } #endif #endif + vec3 lighting = ambientLight + mix(lightSum, lightMax, LIGHT_OVERLAP_REDUCTION); + vec3 lightingInside = + ambientLight + mix(lightSumInside, lightMaxInside, LIGHT_OVERLAP_REDUCTION); + vec3 outsideColor = {backgroundColor}.rgb * lighting; vec3 insideColor = colorAtPosition * lightingInside * INTENSITY_INSIDE_RATIO; diff --git a/src/graphics/rendering/shaders/shading-fs.glsl b/src/graphics/rendering/shaders/shading-fs.glsl index 71077ee..de3f2fd 100644 --- a/src/graphics/rendering/shaders/shading-fs.glsl +++ b/src/graphics/rendering/shaders/shading-fs.glsl @@ -5,6 +5,9 @@ precision lowp float; #define INTENSITY_INSIDE_RATIO {intensityInsideRatio} #define SHADOW_TRACE_COUNT {shadowTraceCount} #define FLOAT_LINEAR_ENABLED {floatLinearEnabled} +// 0.0 == lights add (overlaps brighten & merge); 1.0 == lights take the +// per-channel max (overlaps never exceed the brighter light). See main(). +#define LIGHT_OVERLAP_REDUCTION float({lightOverlapReduction}) {macroDefinitions} @@ -87,9 +90,16 @@ void main() { float startingDistance = getDistance(uvCoordinates); - vec3 lighting = ambientLight; - vec3 lightingInside = ambientLight; - + // Each light contributes to two accumulators: an additive sum (overlaps + // brighten and glows bridge into one another) and a per-channel max + // (overlaps read as just the brightest light). LIGHT_OVERLAP_REDUCTION + // blends between them. Ambient stays a separate additive base, so a single + // light is identical to pure-additive (sum == max for one light). + vec3 lightSum = vec3(0.0); + vec3 lightMax = vec3(0.0); + vec3 lightSumInside = vec3(0.0); + vec3 lightMaxInside = vec3(0.0); + #ifdef CIRCLE_LIGHT_COUNT #if CIRCLE_LIGHT_COUNT > 0 for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { @@ -97,13 +107,16 @@ void main() { vec3 lightColorAtPosition = colorInPosition(i, lightCenterDistance); vec2 direction = normalize(circleLightCenters[i] - position) / squareToAspectRatioTimes2; - lighting += lightColorAtPosition * shadowTransparency( - startingDistance, - lightCenterDistance, + vec3 shadowed = lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, direction ); - - lightingInside += lightColorAtPosition; + + lightSum += shadowed; + lightMax = max(lightMax, shadowed); + lightSumInside += lightColorAtPosition; + lightMaxInside = max(lightMaxInside, lightColorAtPosition); } #endif #endif @@ -119,7 +132,7 @@ void main() { originalDirection, lightCenterDistance ); - + if (lightCenterDistance < flashlightStartCutoffs[i]) { continue; } @@ -130,17 +143,24 @@ void main() { continue; } - lighting += lightColorAtPosition * shadowTransparency( - startingDistance, - lightCenterDistance, + vec3 shadowed = lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, direction ); - lightingInside += lightColorAtPosition; + lightSum += shadowed; + lightMax = max(lightMax, shadowed); + lightSumInside += lightColorAtPosition; + lightMaxInside = max(lightMaxInside, lightColorAtPosition); } #endif #endif + vec3 lighting = ambientLight + mix(lightSum, lightMax, LIGHT_OVERLAP_REDUCTION); + vec3 lightingInside = + ambientLight + mix(lightSumInside, lightMaxInside, LIGHT_OVERLAP_REDUCTION); + vec3 outsideColor = {backgroundColor}.rgb * lighting; vec3 insideColor = colorAtPosition * lightingInside * INTENSITY_INSIDE_RATIO;