-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Improve light render parity with blender #2549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -235,29 +235,23 @@ | |
float dist; | ||
Math_lengthAndNormalize(light.vector,dist,L); | ||
|
||
float invRange=light.invRadius; // position.w | ||
const float light_threshold = 0.01; | ||
|
||
#ifdef SRGB | ||
light.fallOff = (1.0 - invRange * dist) / (1.0 + invRange * dist * dist); // lightDir.w | ||
light.fallOff = clamp(light.fallOff, 1.0 - posLight, 1.0); | ||
#else | ||
light.fallOff = clamp(1.0 - invRange * dist * posLight, 0.0, 1.0); | ||
#endif | ||
|
||
// computeSpotFalloff | ||
if(light.type>1.){ | ||
vec3 spotdir = normalize(light.spotDirection); | ||
float curAngleCos = dot(-L, spotdir); | ||
// Standard falloff | ||
float radius = 1./light.invRadius; | ||
float clampedDist = max(dist, 0.00001); | ||
light.fallOff = clamp(1.0 / (clampedDist * clampedDist), 0.0, 1.0); | ||
light.fallOff = clamp(light.fallOff, 1.0 - posLight, 1.0); | ||
light.fallOff *= step(dist, radius); | ||
|
||
// Spot cone falloff | ||
if (light.type > 1.0) { | ||
float innerAngleCos = floor(light.spotAngleCos) * 0.001; | ||
float outerAngleCos = fract(light.spotAngleCos); | ||
float innerMinusOuter = innerAngleCos - outerAngleCos; | ||
float falloff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0); | ||
#ifdef SRGB | ||
// Use quadratic falloff (notice the ^4) | ||
falloff = pow(clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0), 4.0); | ||
#endif | ||
light.fallOff*=falloff; | ||
float sharpnessFactor = 4.0; | ||
|
||
vec3 spotDir = normalize(light.spotDirection); | ||
float cosA = dot(-L, spotDir) ; | ||
float spotAtten = clamp((cosA - outerAngleCos) / (innerAngleCos - outerAngleCos), 0.0, 1.0); | ||
light.fallOff *= pow(spotAtten, sharpnessFactor); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This smooths out the angular attenuation. It doesn't look exactly like blender's. |
||
} | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -739,7 +739,9 @@ public static ColorRGBA getAsColor(JsonObject parent, String name) { | |
return null; | ||
} | ||
JsonArray color = el.getAsJsonArray(); | ||
return new ColorRGBA(color.get(0).getAsFloat(), color.get(1).getAsFloat(), color.get(2).getAsFloat(), color.size() > 3 ? color.get(3).getAsFloat() : 1f); | ||
return new ColorRGBA().setAsSrgb( | ||
color.get(0).getAsFloat(), color.get(1).getAsFloat(), color.get(2).getAsFloat(), color.size() > 3 ? color.get(3).getAsFloat() : 1f | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gltf colors are srgb |
||
); | ||
} | ||
|
||
public static ColorRGBA getAsColor(JsonObject parent, String name, ColorRGBA defaultValue) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,6 +56,9 @@ | |
* Created by Trevor Flynn - 3/23/2021 | ||
*/ | ||
public class LightsPunctualExtensionLoader implements ExtensionLoader { | ||
private static final boolean COMPUTE_LIGHT_RANGE = true; | ||
private static final boolean APPLY_INTENSITY_CONVERSION = true; | ||
private static final boolean SKIP_HDR_CONVERSION = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can use these toggles to test with and without the patches |
||
|
||
private final HashSet<NodeNeedingLight> pendingNodes = new HashSet<>(); | ||
private final HashMap<Integer, Light> lightDefinitions = new HashMap<>(); | ||
|
@@ -126,8 +129,6 @@ private SpotLight buildSpotLight(JsonObject obj) { | |
|
||
float intensity = obj.has("intensity") ? obj.get("intensity").getAsFloat() : 1.0f; | ||
ColorRGBA color = obj.has("color") ? GltfUtils.getAsColor(obj, "color") : new ColorRGBA(ColorRGBA.White); | ||
color = lumensToColor(color, intensity); | ||
float range = obj.has("range") ? obj.get("range").getAsFloat() : Float.POSITIVE_INFINITY; | ||
|
||
//Spot specific | ||
JsonObject spot = obj.getAsJsonObject("spot"); | ||
|
@@ -143,6 +144,15 @@ private SpotLight buildSpotLight(JsonObject obj) { | |
outerConeAngle = FastMath.HALF_PI - 0.000001f; | ||
} | ||
|
||
if(APPLY_INTENSITY_CONVERSION) { | ||
float solidAngle = 2.0f * FastMath.PI * (1.0f - FastMath.cos(outerConeAngle)); | ||
intensity = intensity / solidAngle; | ||
} | ||
|
||
float range = obj.has("range") ? obj.get("range").getAsFloat() : (COMPUTE_LIGHT_RANGE ? getCutoffDistance(color, intensity) : Float.POSITIVE_INFINITY); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The POSITIVE_INFINITY range was an error to begin with and required a patch to turn it into FLOAT_MAX_VALUE #2058 , but it ends up introducing some floating point errors with large numbers that makes the light calculation a bit off. Truth is: jme doesn't support infinite lights very well. |
||
|
||
color = lumensToColor(color, intensity); | ||
|
||
SpotLight spotLight = new SpotLight(); | ||
spotLight.setName(name); | ||
spotLight.setColor(color); | ||
|
@@ -165,7 +175,7 @@ private DirectionalLight buildDirectionalLight(JsonObject obj) { | |
|
||
float intensity = obj.has("intensity") ? obj.get("intensity").getAsFloat() : 1.0f; | ||
ColorRGBA color = obj.has("color") ? GltfUtils.getAsColor(obj, "color") : new ColorRGBA(ColorRGBA.White); | ||
color = lumensToColor(color, intensity); | ||
color = buildLinearLightColor(color, intensity); | ||
|
||
DirectionalLight directionalLight = new DirectionalLight(); | ||
directionalLight.setName(name); | ||
|
@@ -186,8 +196,11 @@ private PointLight buildPointLight(JsonObject obj) { | |
|
||
float intensity = obj.has("intensity") ? obj.get("intensity").getAsFloat() : 1.0f; | ||
ColorRGBA color = obj.has("color") ? GltfUtils.getAsColor(obj, "color") : new ColorRGBA(ColorRGBA.White); | ||
color = lumensToColor(color, intensity); | ||
float range = obj.has("range") ? obj.get("range").getAsFloat() : Float.POSITIVE_INFINITY; | ||
|
||
if(APPLY_INTENSITY_CONVERSION) intensity = intensity / (4.0f * FastMath.PI); | ||
|
||
float range = obj.has("range") ? obj.get("range").getAsFloat() : (COMPUTE_LIGHT_RANGE ? getCutoffDistance(color, intensity) : Float.POSITIVE_INFINITY); | ||
color = buildLinearLightColor(color, intensity); | ||
|
||
PointLight pointLight = new PointLight(); | ||
pointLight.setName(name); | ||
|
@@ -207,15 +220,16 @@ private PointLight buildPointLight(JsonObject obj) { | |
private void addLight(Node parent, Node node, int lightIndex) { | ||
if (lightDefinitions.containsKey(lightIndex)) { | ||
Light light = lightDefinitions.get(lightIndex); | ||
light = light.clone(); | ||
parent.addLight(light); | ||
LightControl control = new LightControl(light); | ||
node.addControl(control); | ||
} else { | ||
throw new AssetLoadException("KHR_lights_punctual extension accessed undefined light at index " + lightIndex); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
/** | ||
* Convert a floating point lumens value into a color that | ||
* represents both color and brightness of the light. | ||
* | ||
|
@@ -236,6 +250,10 @@ private ColorRGBA lumensToColor(ColorRGBA color, float lumens) { | |
* @return A color representing the intensity of the given lumens | ||
*/ | ||
private ColorRGBA lumensToColor(float lumens) { | ||
if(SKIP_HDR_CONVERSION){ | ||
lumens = 0.003f * lumens; | ||
return new ColorRGBA(lumens, lumens, lumens, 1f); | ||
} | ||
/* | ||
Taken from /Common/ShaderLib/Hdr.glsllib | ||
vec4 HDR_EncodeLum(in float lum){ | ||
|
@@ -286,4 +304,33 @@ private void setLightIndex(int lightIndex) { | |
this.lightIndex = lightIndex; | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Computes the effective cutoff distance of a light based on its raw color and intensity. | ||
* Uses inverse-square attenuation and a perceptual visibility threshold. | ||
* | ||
* @param color The base RGB color of the light (linear space) | ||
* @param intensity The light's intensity in lumens (or equivalent) | ||
* @return The cutoff distance where the light falls below a visible threshold | ||
*/ | ||
public float getCutoffDistance(ColorRGBA color, float intensity) { | ||
final float visibleThreshold = 0.001f; | ||
final float maxRange = 10000f; | ||
|
||
// Compute the max channel (R/G/B) for luminance estimation | ||
float maxComponent = Math.max(Math.max(color.r, color.g), color.b); | ||
|
||
if (maxComponent <= 0f || intensity <= 0f) { | ||
return 0f; | ||
} | ||
|
||
// The actual light output (lux at 1 meter) per component | ||
float effectiveIntensity = maxComponent * intensity; | ||
|
||
// Inverse-square attenuation: intensity / d^2 = visibleThreshold | ||
float range = (float) Math.sqrt(effectiveIntensity / visibleThreshold); | ||
return Math.min(range, maxRange); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the inverse square falloff used by blender with a radius cut-off, since jme allows to set the radius of lights