Light overlap

This commit is contained in:
Andras Schmelczer 2026-06-10 19:51:23 +01:00
parent 62ee692291
commit 8220095e4e
5 changed files with 79 additions and 24 deletions

View file

@ -167,6 +167,7 @@ export class RendererImplementation implements Renderer {
{ {
shadowTraceCount: settings.shadowTraceCount.toString(), shadowTraceCount: settings.shadowTraceCount.toString(),
intensityInsideRatio: settings.lightPenetrationRatio, intensityInsideRatio: settings.lightPenetrationRatio,
lightOverlapReduction: settings.lightOverlapReduction,
floatLinearEnabled: this.gl.insights.floatInterpolationEnabled ? '1' : '0', floatLinearEnabled: this.gl.insights.floatInterpolationEnabled ? '1' : '0',
backgroundColor: colorToString(settings.backgroundColor), backgroundColor: colorToString(settings.backgroundColor),
} }

View file

@ -10,6 +10,7 @@ export const defaultStartupSettings: StartupSettings = {
ignoreWebGL2: false, ignoreWebGL2: false,
backgroundColor: vec4.fromValues(1, 1, 1, 1), backgroundColor: vec4.fromValues(1, 1, 1, 1),
lightPenetrationRatio: 0.75, lightPenetrationRatio: 0.75,
lightOverlapReduction: 0,
enableStopwatch: false, enableStopwatch: false,
enableContextLostSimulator: false, enableContextLostSimulator: false,
}; };

View file

@ -32,6 +32,19 @@ export interface StartupSettings {
*/ */
lightPenetrationRatio: number; 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. * Gives the number of possible object colors for the scene.
* *

View file

@ -4,6 +4,9 @@ precision lowp float;
#define INTENSITY_INSIDE_RATIO {intensityInsideRatio} #define INTENSITY_INSIDE_RATIO {intensityInsideRatio}
#define SHADOW_TRACE_COUNT {shadowTraceCount} #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} {macroDefinitions}
@ -65,8 +68,15 @@ void main() {
vec3 colorAtPosition = rgbaColorAtPosition.rgb; vec3 colorAtPosition = rgbaColorAtPosition.rgb;
vec3 lighting = ambientLight; // Each light contributes to two accumulators: an additive sum (overlaps
vec3 lightingInside = ambientLight; // 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 #ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0 #if CIRCLE_LIGHT_COUNT > 0
@ -78,13 +88,16 @@ void main() {
); );
vec2 direction = normalize(circleLightCenters[i] - position) / squareToAspectRatioTimes2; vec2 direction = normalize(circleLightCenters[i] - position) / squareToAspectRatioTimes2;
lighting += lightColorAtPosition * shadowTransparency( vec3 shadowed = lightColorAtPosition * shadowTransparency(
startingDistance, startingDistance,
lightCenterDistance, lightCenterDistance,
direction direction
); );
lightingInside += lightColorAtPosition; lightSum += shadowed;
lightMax = max(lightMax, shadowed);
lightSumInside += lightColorAtPosition;
lightMaxInside = max(lightMaxInside, lightColorAtPosition);
} }
#endif #endif
#endif #endif
@ -107,17 +120,24 @@ void main() {
continue; continue;
} }
lighting += lightColorAtPosition * shadowTransparency( vec3 shadowed = lightColorAtPosition * shadowTransparency(
startingDistance, startingDistance,
lightCenterDistance, lightCenterDistance,
direction direction
); );
lightingInside += lightColorAtPosition; lightSum += shadowed;
lightMax = max(lightMax, shadowed);
lightSumInside += lightColorAtPosition;
lightMaxInside = max(lightMaxInside, lightColorAtPosition);
} }
#endif #endif
#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 outsideColor = {backgroundColor}.rgb * lighting;
vec3 insideColor = colorAtPosition * lightingInside * INTENSITY_INSIDE_RATIO; vec3 insideColor = colorAtPosition * lightingInside * INTENSITY_INSIDE_RATIO;

View file

@ -5,6 +5,9 @@ precision lowp float;
#define INTENSITY_INSIDE_RATIO {intensityInsideRatio} #define INTENSITY_INSIDE_RATIO {intensityInsideRatio}
#define SHADOW_TRACE_COUNT {shadowTraceCount} #define SHADOW_TRACE_COUNT {shadowTraceCount}
#define FLOAT_LINEAR_ENABLED {floatLinearEnabled} #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} {macroDefinitions}
@ -87,8 +90,15 @@ void main() {
float startingDistance = getDistance(uvCoordinates); float startingDistance = getDistance(uvCoordinates);
vec3 lighting = ambientLight; // Each light contributes to two accumulators: an additive sum (overlaps
vec3 lightingInside = ambientLight; // 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 #ifdef CIRCLE_LIGHT_COUNT
#if CIRCLE_LIGHT_COUNT > 0 #if CIRCLE_LIGHT_COUNT > 0
@ -97,13 +107,16 @@ void main() {
vec3 lightColorAtPosition = colorInPosition(i, lightCenterDistance); vec3 lightColorAtPosition = colorInPosition(i, lightCenterDistance);
vec2 direction = normalize(circleLightCenters[i] - position) / squareToAspectRatioTimes2; vec2 direction = normalize(circleLightCenters[i] - position) / squareToAspectRatioTimes2;
lighting += lightColorAtPosition * shadowTransparency( vec3 shadowed = lightColorAtPosition * shadowTransparency(
startingDistance, startingDistance,
lightCenterDistance, lightCenterDistance,
direction direction
); );
lightingInside += lightColorAtPosition; lightSum += shadowed;
lightMax = max(lightMax, shadowed);
lightSumInside += lightColorAtPosition;
lightMaxInside = max(lightMaxInside, lightColorAtPosition);
} }
#endif #endif
#endif #endif
@ -130,17 +143,24 @@ void main() {
continue; continue;
} }
lighting += lightColorAtPosition * shadowTransparency( vec3 shadowed = lightColorAtPosition * shadowTransparency(
startingDistance, startingDistance,
lightCenterDistance, lightCenterDistance,
direction direction
); );
lightingInside += lightColorAtPosition; lightSum += shadowed;
lightMax = max(lightMax, shadowed);
lightSumInside += lightColorAtPosition;
lightMaxInside = max(lightMaxInside, lightColorAtPosition);
} }
#endif #endif
#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 outsideColor = {backgroundColor}.rgb * lighting;
vec3 insideColor = colorAtPosition * lightingInside * INTENSITY_INSIDE_RATIO; vec3 insideColor = colorAtPosition * lightingInside * INTENSITY_INSIDE_RATIO;