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(),
intensityInsideRatio: settings.lightPenetrationRatio,
lightOverlapReduction: settings.lightOverlapReduction,
floatLinearEnabled: this.gl.insights.floatInterpolationEnabled ? '1' : '0',
backgroundColor: colorToString(settings.backgroundColor),
}

View file

@ -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,
};

View file

@ -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.
*

View file

@ -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;

View file

@ -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;