Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -229,49 +229,52 @@


#if defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) || defined(ENABLE_PBRLightingUtils_computeLightInWorldSpace)
void PBRLightingUtils_computeLightInWorldSpace(vec3 worldPos,vec3 worldNormal, vec3 viewDir, inout Light light){
void PBRLightingUtils_computeLightInWorldSpace(vec3 worldPos, vec3 worldNormal, vec3 viewDir, inout Light light){
if(light.ready) return;

// lightComputeDir
float posLight = step(0.5, light.type);
light.vector = light.position.xyz * sign(posLight - 0.5) - (worldPos * posLight); //tempVec lightVec
float posLight = step(0.5, light.type);
light.vector = light.position.xyz * sign(posLight - 0.5) - (worldPos * posLight);

vec3 L; // lightDir
vec3 L;
float dist;
Math_lengthAndNormalize(light.vector,dist,L);
Math_lengthAndNormalize(light.vector, dist, L);

float invRange=light.invRadius; // position.w
const float light_threshold = 0.01;
if (posLight > 0.5) {
float clampedDist = max(dist, 0.00001);
float invSq = 1.0 / (clampedDist * clampedDist);

#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
float radius = 1.0 / light.invRadius;
float rangeAtt = 1.0;

if (radius > 0.0) {
float x = dist / radius;
rangeAtt = clamp(1.0 - pow(x, 4.0), 0.0, 1.0);
}

light.fallOff = invSq * rangeAtt;
} else {
light.fallOff = 1.0;
}

// computeSpotFalloff
if(light.type>1.){
vec3 spotdir = normalize(light.spotDirection);
float curAngleCos = dot(-L, spotdir);
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;
}

vec3 spotDir = normalize(light.spotDirection);
float cosA = dot(-L, spotDir);
float denom = max(innerAngleCos - outerAngleCos, 0.0001);
float spotAtten = clamp((cosA - outerAngleCos) / denom, 0.0, 1.0);

light.fallOff *= spotAtten;
}

vec3 h=normalize(L+viewDir);
light.dir=L;
vec3 h = normalize(L + viewDir);
light.dir = L;
light.NdotL = max(dot(worldNormal, L), 0.0);
light.NdotH = max(dot(worldNormal, h), 0.0);
light.LdotH = max(dot(L, h), 0.0);
light.HdotV = max(dot(viewDir,h), 0.);
light.HdotV = max(dot(viewDir, h), 0.0);
light.ready = true;
}
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ uniform sampler2DMS m_Texture;

vec4 applyToneMap() {
ivec2 iTexC = ivec2(texCoord * vec2(textureSize(m_Texture)));
vec4 color = vec4(0.0);
vec4 hdrColor = vec4(0.0);
for (int i = 0; i < NUM_SAMPLES; i++) {
vec4 hdrColor = texelFetch(m_Texture, iTexC, i);
vec3 ldrColor = applyCurve(hdrColor.rgb);
color += vec4(ldrColor, hdrColor.a);
hdrColor += texelFetch(m_Texture, iTexC, i);
}
return color / float(NUM_SAMPLES);
hdrColor /= float(NUM_SAMPLES);
vec3 ldrColor = vec4(applyCurve(hdrColor.rgb), hdrColor.a);
return ldrColor;
}

#else
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package jme3test.light;

import com.jme3.app.SimpleApplication;
import com.jme3.environment.EnvironmentProbeControl;
import com.jme3.input.ChaseCamera;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.KHRToneMapFilter;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.util.SkyFactory;

/**
* Test how lights render compared to the same scene in Blender. Open
* jme3-testdata/src/main/resources/BlenderParity/scene.blend in blender to compare.
*/
public class TestLightImportParity extends SimpleApplication {
public static void main(String[] args) {
TestLightImportParity app = new TestLightImportParity();
app.start();
}

@Override
public void simpleInitApp() {

flyCam.setDragToRotate(true);
flyCam.setMoveSpeed(100f);

Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Alien.png", SkyFactory.EnvMapType.EquirectMap);
sky.rotate(new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y));
rootNode.attachChild(sky);

EnvironmentProbeControl probe = new EnvironmentProbeControl(assetManager, 512);
rootNode.addControl(probe);
probe.tag(sky);

Node scene = (Node)assetManager.loadModel("BlenderParity/scene.glb");
rootNode.attachChild(scene);

KHRToneMapFilter toneMap = new KHRToneMapFilter();
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(toneMap);
viewPort.addProcessor(fpp);

ChaseCamera chaseCam = new ChaseCamera(cam, scene, inputManager);
chaseCam.setDefaultDistance(100);
chaseCam.setMaxDistance(200);




}
}
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,10 @@ 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);
// glTF colors are authored in linear space unless the spec says otherwise.
return new ColorRGBA().set(
color.get(0).getAsFloat(), color.get(1).getAsFloat(), color.get(2).getAsFloat(), color.size() > 3 ? color.get(3).getAsFloat() : 1f
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2024 jMonkeyEngine
* Copyright (c) 2009-2026 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -53,9 +53,11 @@
*
* Supports directional, point, and spot lights.
*
* Created by Trevor Flynn - 3/23/2021
* @author Trevor Flynn, Riccardo Balbo
*/
public class LightsPunctualExtensionLoader implements ExtensionLoader {
private static final boolean COMPUTE_LIGHT_RANGE = true;
private static final float GLTF_LIGHT_COMPAT_SCALE = 0.0009f;

private final HashSet<NodeNeedingLight> pendingNodes = new HashSet<>();
private final HashMap<Integer, Light> lightDefinitions = new HashMap<>();
Expand Down Expand Up @@ -126,13 +128,11 @@ 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");
float innerConeAngle = spot != null && spot.has("innerConeAngle") ? spot.get("innerConeAngle").getAsFloat() : 0f;
float outerConeAngle = spot != null && spot.has("outerConeAngle") ? spot.get("outerConeAngle").getAsFloat() : ((float) Math.PI) / 4f;
float innerConeAngle = (spot != null && spot.has("innerConeAngle")) ? spot.get("innerConeAngle").getAsFloat() : 0f;
float outerConeAngle = (spot != null && spot.has("outerConeAngle"))? spot.get("outerConeAngle").getAsFloat() : (FastMath.PI / 4f);

/*
Correct floating point error on half PI, GLTF spec says that the outerConeAngle
Expand All @@ -143,9 +143,12 @@ private SpotLight buildSpotLight(JsonObject obj) {
outerConeAngle = FastMath.HALF_PI - 0.000001f;
}

float scaledIntensity = toCompatIntensity(intensity);

float range = obj.has("range") ? obj.get("range").getAsFloat() : (COMPUTE_LIGHT_RANGE ? getCutoffDistance(color, scaledIntensity) : Float.POSITIVE_INFINITY);
SpotLight spotLight = new SpotLight(true);
spotLight.setName(name);
spotLight.setColor(color);
spotLight.setColor(applyScaledIntensity(color, scaledIntensity));
spotLight.setSpotRange(range);
spotLight.setSpotInnerAngle(innerConeAngle);
spotLight.setSpotOuterAngle(outerConeAngle);
Expand All @@ -165,11 +168,11 @@ 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);
float scaledIntensity = toCompatIntensity(intensity);

DirectionalLight directionalLight = new DirectionalLight(true);
directionalLight.setName(name);
directionalLight.setColor(color);
directionalLight.setColor(applyScaledIntensity(color, scaledIntensity));
directionalLight.setDirection(Vector3f.UNIT_Z.negate());

return directionalLight;
Expand All @@ -185,13 +188,16 @@ private PointLight buildPointLight(JsonObject obj) {
String name = obj.has("name") ? obj.get("name").getAsString() : "";

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;
ColorRGBA color = obj.has("color") ? GltfUtils.getAsColor(obj, "color")
: new ColorRGBA(ColorRGBA.White);

float scaledIntensity = toCompatIntensity(intensity);

float range = obj.has("range") ? obj.get("range").getAsFloat() : (COMPUTE_LIGHT_RANGE ? getCutoffDistance(color, scaledIntensity) : Float.POSITIVE_INFINITY);

PointLight pointLight = new PointLight(true);
pointLight.setName(name);
pointLight.setColor(color);
pointLight.setColor(applyScaledIntensity(color, scaledIntensity));
pointLight.setRadius(range);

return pointLight;
Expand All @@ -206,7 +212,7 @@ 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 = lightDefinitions.get(lightIndex).clone();
parent.addLight(light);
LightControl control = new LightControl(light);
control.setInvertAxisDirection(true);
Expand All @@ -216,55 +222,17 @@ private void addLight(Node parent, Node node, int lightIndex) {
}
}

/**
* Convert a floating point lumens value into a color that
* represents both color and brightness of the light.
*
* @param color The base color of the light
* @param lumens The lumens value to convert to a color
* @return A color representing the intensity of the given lumens encoded into the given color
*/
private ColorRGBA lumensToColor(ColorRGBA color, float lumens) {
ColorRGBA brightnessModifier = lumensToColor(lumens);
return color.mult(brightnessModifier);
private float toCompatIntensity(float intensity) {
return intensity * GLTF_LIGHT_COMPAT_SCALE;
}

/**
* Convert a floating point lumens value into a grayscale color that
* represents a brightness.
*
* @param lumens The lumens value to convert to a color
* @return A color representing the intensity of the given lumens
*/
private ColorRGBA lumensToColor(float lumens) {
/*
Taken from /Common/ShaderLib/Hdr.glsllib
vec4 HDR_EncodeLum(in float lum){
float Le = 2.0 * log2(lum + epsilon) + 127.0;
vec4 result = vec4(0.0);
result.a = fract(Le);
result.rgb = vec3((Le - (floor(result.a * 255.0)) / 255.0) / 255.0);
return result;
*/
float epsilon = 0.0001f;

double Le = 2f * Math.log(lumens * epsilon) / Math.log(2) + 127.0;
ColorRGBA color = new ColorRGBA();
color.a = (float) (Le - Math.floor(Le)); //Get fractional part
float val = (float) ((Le - (Math.floor(color.a * 255.0)) / 255.0) / 255.0);
color.r = val;
color.g = val;
color.b = val;

return color;
private ColorRGBA applyScaledIntensity(ColorRGBA color, float scaledIntensity) {
return color.mult(scaledIntensity);
}

/**
* A bean to contain the relation between a node and a light index
*/
private static class NodeNeedingLight {
private Node node;
private int lightIndex;
private final Node node;
private final int lightIndex;

private NodeNeedingLight(Node node, int lightIndex) {
this.node = node;
Expand All @@ -275,16 +243,35 @@ private Node getNode() {
return node;
}

private void setNode(Node node) {
this.node = node;
}

private int getLightIndex() {
return 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
*/
private float getCutoffDistance(ColorRGBA color, float scaledIntensity) {
final float visibleThreshold = 0.001f;
final float maxRange = 10000f;

private void setLightIndex(int lightIndex) {
this.lightIndex = lightIndex;
// 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 || scaledIntensity <= 0f) {
return 0f;
}

// The actual light output (lux at 1 meter) per component
float effectiveIntensity = maxComponent * scaledIntensity;
// Inverse-square attenuation: intensity / d^2 = visibleThreshold
float range = (float) Math.sqrt(effectiveIntensity / visibleThreshold);
return Math.min(range, maxRange);
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.