Skip to content
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

Switches lm/m^2 to W/m^2 and lm/r^2 to W/r^2 in KHR_lights_punctual. #2214

Closed
wants to merge 3 commits into from

Conversation

will-ca
Copy link

@will-ca will-ca commented Oct 12, 2022

Lumens are not actually a physical unit. They are a perceptual unit, and this leads to all sorts of issues.

  • Lumens makes RGB difficult to process and reason about, because the same physical power of blue and red produces fewer perceptual lumens than does green.
  • Lumens produce very big numbers, in the hundreds for a household lightbulb. This may be petty, but is a compatibility issue when viewers and their importers expect numbers around the 0.0-10.0 range.
  • Lumens (perceptual luminance) are not actually convertible from watts (physical radiance), which are what authoring programs and rendering pipelines work in. This has two consequences:
  • In logistical terms, it makes implementation of exporters and importers tricky. E.G. Intensity units when exporting point lights  glTF-Blender-IO#564 was held up for over three years (and is still open) because nobody figured out the conversion function. Other projects, including Three.JS, and the official Khronos Group reference GLTF viewer, seem to also have just ignored the units part of the spec.
  • In mathematical terms, it means that there is not actually a canonical way to convert from physical watts in authoring and display programs to perceptual lumens for GLTF, or vice versa. They measure different things, so at best you can have a wild guess based on situational equivalence given a bunch of arbitrary assumptions. E.G. 594lm, 609lm, 572lm, and 0.2lm are all equally valid conversions for 1W, depending on the exact wavelength and luminous efficiency function you're using. This means that as written, the spec's behaviour is actually undefined relative to physically based workflows; It is actually not possible to build a physically accurate pipeline using lumens as specified in KHR_lights_punctual, because the unit itself does not represent a physical quantity.

Due to low previous adoption of the lumens unit from the spec (possibly partly due to the aforementioned issues), the disruption from this should hopefully be minimial. I have not found any examples that actually implement or use the lumens units from the spec, including both the Three.JS and official Khronos Group reference GLTF viewers. In any case applications are free to maintain their present, already non-conformant behaviour, and adopt the new behaviour if they wish.

See GH-2213.

Lumens are *not* actually a physical unit. They are a perceptual unit, and this leads to all sorts of issues.

- Lumens makes RGB difficult to process and reason about, because the same physical power of blue and red produces fewer perceptual lumens than does green.
- Lumens produce very big numbers, in the hundreds for a household lightbulb. This may be petty, but is a compatibility issue when viewers and their importers expect numbers around the 0.0-10.0 range.
- Lumens (perceptual luminance) are not actually convertible from watts (physical radiance), which are what authoring programs and rendering pipelines work in. This has two consequences:
- In logistical terms, it makes implementation of exporters and importers tricky. E.G. KhronosGroup/glTF-Blender-IO#564 was held up for over three years (and is still open) because nobody figured out the conversion function. Other projects, including Three.JS, and the official Khronos Group reference GLTF viewer, seem to also have just ignored the units part of the spec.
- In mathematical terms, it means that there is not actually a canonical way to convert from physical watts in authoring and display programs to perceptual lumens for GLTF, or vice versa. They measure different things, so at best you can have a wild guess based on situational equivalence given a bunch of arbitrary assumptions. E.G. 594lm, 609lm, 572lm, and 0.2lm are all equally valid conversions for 1W, depending on the exact wavelength and luminous efficiency function you're using. **This means that as written, the spec's behaviour is actually undefined relative to physically based workflows;** It is actually not possible to build a physically accurate pipeline using lumens as specified in `KHR_lights_punctual`, because the unit itself does not represent a physical quantity.

Due to low previous adoption of the lumens unit from the spec (possibly partly due to the aforementioned issues), the disruption from this should hopefully be minimial. I have not found any examples that actually implement or use the lumens units from the spec, including both the Three.JS and official Khronos Group reference GLTF viewers. In any case applications are free to maintain their present, already non-conformant behaviour, and adopt the new behaviour if they wish.

See KhronosGroupGH-2213.
@donmccurdy
Copy link
Contributor

donmccurdy commented Oct 12, 2022

I'm probably not the right person to address your larger comments, but I don't think it can be correct that existing implementations use Watts (with the exception of Blender). See this document, by @bhouston:

https://docs.google.com/spreadsheets/d/1Ce9XCC2Ub9eVjAQdYbYdLvlkDBJ_0GtAyRbSF0KJWzE/edit#gid=0

I would certainly love to get export of punctual lights in Blender working, so thanks for investigating here! If I (speaking for three.js) have gotten something wrong here, I think we're happy to make changes to address that.

@will-ca
Copy link
Author

will-ca commented Oct 12, 2022

@donmccurdy So, my question would be: Even if the UI says "lm", what is happening behind the scenes? Maybe it's just because I tend to think more in terms of physics, but I have a hard time picturing a robust rendering pipeline based on a perceptual unit— Do those BDRFs and whatnot ultimately operate in power, which is what watts measure, or in luminance, which is what lumens measure?

That said, I agree that if there are already established workflows involving GLTF that depend on lumens/perceptual units, and that is what gets presented by those tools on a UI level and stored on a file level, then this change would not be worth it and should not be made.

This PR came from investigation while trying to track down a "root cause" solution in frustration at trying to get Blender→GLTF export making sense according to the reference viewers, and I did try to assess the rest of the ecosystem but documents like that spreadsheet don't really show up on search engines, so it may well not actually make sense outside of that context. That said, if lumens as specified are actually in use, then IMO at least the Khronos Group GLTF viewer should be updated to handle it correctly, but that's another issue.

I don't think I've encountered anything explicitly wrong in Three.JS specifically. Different light scaling from Blender, of course, but that's normal. I did notice the docs seemed to say WebGLRenderer.physicallyCorrectLights makes punctual lights get interpreted as total lumens, whereas the spec says lumens per square radian— Depending on interpretation, and depending on the design goal of .physicallyCorrectLights, that could be either just a different choice or a failure to implement the spec— Hm, if the comment in that linked spreadsheet is meant to canonically imply Three.JS is GLTF-compliant with .physicallyCorrectLights, then I suppose it's the latter. Suppose I'll raise that over there.

@donmccurdy
Copy link
Contributor

donmccurdy commented Oct 12, 2022

The established workflows were built around lumens long before glTF's KHR_lights_punctual extension; glTF is following convention more than defining it here. three.js' lighting also predates KHR_lights_punctual.

If everyone is broadly using the same units already, perhaps what is missing from the extension is a standardized recommendation on the conversion factor to/from watts? Since, as you point out, it isn't obvious. And then fixing whichever tool(s) have gotten this wrong.

WebGLRenderer.physicallyCorrectLights makes punctual lights get interpreted as total lumens

We use candela, or lumens per unit area, for intensity. We also provide an equivalent input called "power", in lumens, but this is just a convenience and is converted when used.

The differences between three.js and babylon.js on punctual lighting are huge though, and babylon.js seems to look about right with Blender exports without any fix here. I'm not sure how that's possible if Blender isn't doing a conversion from Watts yet, perhaps @bghgary will know?

@donmccurdy
Copy link
Contributor

donmccurdy commented Oct 12, 2022

Other references —

Radiometry or Photometry? With non-spectral rendering, like most game engines, the luminous
efficacy information is required for going back and forth between radiometric and photometric units.
As it increases complexity for artists to provide two values (light intensity and luminous efficacy), it
is preferable to use the hypothesis η = 683. With such an approximation, each quantity is a linear
transform of the other and thus their processing will be identical.

It appears that implementations have just adopted a conversion factor of 683, and are (intentionally) not doing the more complex perceptual version of the conversion.

@bghgary
Copy link
Contributor

bghgary commented Oct 12, 2022

I'm not sure how that's possible if Blender isn't doing a conversion from Watts yet, perhaps @bghgary will know?

@sebavan Any thoughts?

@sebavan
Copy link
Contributor

sebavan commented Oct 12, 2022

We are currently relying on the same intensity units that Filament does :-)

I would answer like @donmccurdy here:

I would certainly love to get export of punctual lights in Blender working, so thanks for investigating here! If I (speaking for three.js) have gotten something wrong here, I think we're happy to make changes to address that.

I am happy to add any new required mode in Babylon if there is a spec change and a simple conversion exist.

@will-ca
Copy link
Author

will-ca commented Oct 12, 2022

If everyone is broadly using the same units already, perhaps what is missing from the extension is a standardized recommendation on the conversion factor to/from watts? Since, as you point out, it isn't obvious. And then fixing whichever tool(s) have gotten this wrong.

Imho that would be great. Now that you mention it, I realise that that is probably the source of most of my discomfort with trying to get intensity export working in Blender— Having the conversion factor be undefined means that with every export and import operation, the light intensity can change by a significant and arbitrary amount, and though I know glTF isn't meant for interchange, it still uh feels wrong that that can happen. Standardizing the conversion factor would solve that, and the rest of my concerns can be left to the philosophers.

Thanks a lot for addressing this in a timely manner, by the way.

@will-ca
Copy link
Author

will-ca commented Oct 12, 2022

It appears that implementations have just adopted a conversion factor of 683, and are (intentionally) not doing the more complex perceptual version of the conversion.

683 appears to be based on the peak possible luminous efficiency, at 555nm, and shows up in the modern (SI?) definition of the candela. So I suppose that's as good/canonical of a value to use as any.

@donmccurdy
Copy link
Contributor

donmccurdy commented Oct 13, 2022

The differences between three.js and babylon.js on punctual lighting are huge though, and babylon.js seems to look about right with Blender exports without any fix here ...

On further experiments, let me retract this. I'd made a mistake, and (with the right scene configuration) all three glTF viewers I'm testing (three.js, babylon.js, khronos viewer) are all pretty close to one another. However, using the conversion we've arrived at above, they're all far too bright compared to Blender. So either our conversion is wrong, or we've misunderstood what Blender actually does. I'm worried it may be the latter, and am adding a comment here:

https://developer.blender.org/T91035#1431632

I'm doing conversion checks in glTF-Transform, with the script below:

import { Light } from '@gltf-transform/extensions';

const scene = document.getRoot().getDefaultScene();

const LUMENS_PER_WATT = 683;

scene.traverse((node) => {
	const light = node.getExtension<Light>('KHR_lights_punctual');
	if (light) {
		// Blender currently exports light intensities without conversion
		// from its native radiometric units. See:
		// https://github.com/KhronosGroup/glTF-Blender-IO/issues/564
		const watts = light.getIntensity();

		// Convert radiometric to photometric units, by convention:
		// https://google.github.io/filament/Filament.md.html#units
		const candela = watts * LUMENS_PER_WATT / (4 * Math.PI);

		console.log(`Conversion: ${watts} W → ${candela} cd`);

		light.setIntensity(candela);
	}
});

@will-ca
Copy link
Author

will-ca commented Oct 13, 2022

So either our conversion is wrong, or we've misunderstood what Blender actually does.

@donmccurdy My assessment was actually the opposite: That nobody else is actually using the lumens-based units correctly either.

E.G. With some GLTF files where I skipped the conversion formula (old plugin), and instead just manually set the intensities to 5, 50, and 500:

5.gltf.txt

50.gltf.txt

500.gltf.txt

Text/JSON GLTF so you can open them to verify the intensities.

If the viewers were expecting lumens, then you would expect 5.gltf to be way too dark, and either 50.gltf or 500.gltf to look reasonable. Instead, 5.gltf looks normal, while 50.gltf and 500.gltf are way too bright.

(That is actually why I initially thought the solution should be to just switch the whole spec over to watts— It looked like that was what everyone else was basically already doing. However, as you have explained with that spreadsheet and I have found out in mrdoob/three.js#24788, that is apparently also not the case.)

I don't think this has anything to do with Blender behaviour, since we can manually verify that the intensities are appropriate for KHR_lights_punctual (per the spec as written) after the export.

@will-ca
Copy link
Author

will-ca commented Oct 13, 2022

I don't think the problem is on the Blender side as handcrafted/manually verified GLTFs also have the same exposure issues in every viewer I've tried, but some time ago I did write this guide on how to set up Blender to output physically proportional irradiance values as image data:

https://blender.stackexchange.com/questions/222959/computing-scene-luminance

(The question asked about Cycles, but it should work for EEVEE too— And besides physical parity between the two engines is an explicit(?) goal at the BF, IIRC.)

So if it is necessary to confirm that Blender's lighting model behaves internally self-consistently, one could set up a scene where lights of different types shine on different surfaces, and sample and verify that the brightnesses on each surface produced by each type of light are as one would expect given the positioning and nominal wattages of the lights.

@will-ca
Copy link
Author

will-ca commented Oct 13, 2022

@donmccurdy Right. I think I've got it.

The lights/physical example for Three.JS has these two lines:

https://github.com/mrdoob/three.js/blob/034343d97134c55559aa4edbcce3d405eae308da/examples/webgl_lights_physical.html#L237

renderer.toneMapping = THREE.ReinhardToneMapping;

https://github.com/mrdoob/three.js/blob/034343d97134c55559aa4edbcce3d405eae308da/examples/webgl_lights_physical.html#L281

renderer.toneMappingExposure = Math.pow( params.exposure, 5.0 ); // to allow for very bright scenes.

If you comment these lines out, then the result also looks very overexposed, similarly to the output from my patch.

So the answer then seems to be that all of the model viewers simply aren't configured to display KHR_lights_punctual-compliant GLTF files with sane exposures by default. Instead, they seem to expect you to let everything be grossly overexposed by raw physical irradiance values, but then apply a tonemapping operation to compress the dynamic range. Well, Three.JS seems to be doing that anyway, while I've never used Babylon.js (though it looks really cool too!) and I don't suppose the Khronos viewer implements tone mapping, but since the behaviour is described as being equivalent.

So.... I'm not sure what that means for the purposes of our discussion here. Everything is working as designed, but the design itself has a couple extra steps from what we expected, and it means that default behaviour of viewers for spec-compliant GLTFs is not very nice.

I don't have a web stack, so for every test I do I'm basically curling all the built files from the live examples. I suppose you could test to see if the Blender export patch looks right when a tone mapping step is applied as in the above example? But with the tone mapping and variable exposure, that's going to be pretty arbitrary anyway.

....And moreover, is it a problem that with the spec as written and all viewers as designed and implemented, the default behaviour for a standards-compliant GLTF/KHR_lights_punctual file is currently for everything to be extremely overexposed, and for the only way to figure out how to change that to apparently be to find one specific line of code in one specific Three.JS example? That doesn't seem very conducive to efficient workflow and wide adoption. It's certainly caused a lot of confusion here at least, and you'd expect this space to be most ready to figure it out.

So... I think that resolves the conversion ratio question. 683 should be fine, and should go into Blender and possibly/probably also the spec too. But I suppose it also exposes an implementation and workflow issue with how the spec is currently handled in the ecosystem.

Hm. @sebavan, @bghgary. Thoughts?

@donmccurdy
Copy link
Contributor

donmccurdy commented Oct 13, 2022

Your setup to output physically proportional irradiance values as image data looks really helpful, thanks for that! Have you by any chance done measurements to confirm that outputs to an open domain [0,∞] format like OpenEXR are consistent with what we expect here? For either one of Eevee or Cycles — I'm happy to assume they're already consistent with one another.

You're correct that three.js, babylon.js, and the Khronos viewer do not include a tone mapping curve by default. The babylon.js and Khronos viewers do have options to enable tone mapping and exposure. For the Khronos viewer, it's under "Advanced":

📸 screenshot Screen Shot 2022-10-13 at 1 33 15 AM

Let's suppose the difference here is really in default tone mapping or exposure. Should we be able to prove that's the cause — and not anything specific to punctual light sources — somehow? Possibly by using an OpenEXR image as a source in both Blender and e.g. three.js?

We could also set up export of a linear, open domain [0,∞] image in OpenEXR format from three.js, for any scene, if that's helpful. Or a viewer for such images, with or without tone mapping and exposure.


Aside 1 — Something still confuses me about Blender's units. They recommend using a 0.05 W point light to simulate a candela, and the only standard definition of a candle is 1 candela. But by our equation above, 0.05 W * 683 lm/W / (4 * PI m²) = 2.71 cd, not 1 cd.

Aside 2 — I've done some work-in-progress implementing Blender-like view transforms in three.js. This isn't part of official three.js today, but you can see some results at https://three-filmic.donmccurdy.com/reference.html. These are implemented as defined in Blender's OCIO config. I'm not sure that's relevant to what we're doing here, but FYI. The images on that page are just using an OpenEXR image as input (not a 3d scene), exported from Blender, then applying the filmic view transform externally in OCIO or three.js.

@will-ca
Copy link
Author

will-ca commented Oct 13, 2022

Have you by any chance done measurements to confirm that outputs to an open domain [0,∞] format like OpenEXR are consistent with what we expect here?

If I had before, I don't remember. I never got around to applying that technique much further than what I documented in that StackExchange answer.

However, if the goal is simply to show that Blender does in fact use the units it says it uses, then that should be easy enough. Spots and Points define energy the same way, so we can just check that they light up a plane the same:

Point and Spot Blender Physicality

PointGUI
SpotGUI
PointSample
SpotSample

(Pay attention to the sampled colour values in the render window screenshots.)

And a Sun with intensity of E.G. 2W/m**2 should irradiate exactly as much as a Point at E.G. 3 meters distance with total luminosity of 2W/(m**2)*(4*pi*(3m)**2)=226.19467W, which is also the case:

Sun and Point Blender Physicality

SunGUI
SunPointGUI
SunSample
SunPointSample

And lastly, to double-check, we can compute physical irradiance to pixel value ratios for all three lamp types to confirm that they all obey the same laws of physics given their nominal units:

Lamp Configuration Irradiance Formula Irradiance Pixel Ratio
Sun E=2W/m^2 E=E 2W/m^2 0.523 3.824
Point P=226.19W, d=3m E=P/A, A=4πd^2 1.99W/m^2 0.517 3.849
Spot P=300W, d=3m E=P/A, A=4πd^2 2.653W/m^2 0.687 3.862

(Fun fact: I actually typed out nearly this entire comment yesterday night assuming it would all line up, and then only actually did the experiments and filled in the data this afternoon. Speculative execution like this doesn't always work out for me, but in this case it did (...So far) lol.)

(Note: I set up a 1.0-albedo material for these experiments, but forgot to select it so the above are all at 0.8 albedo. Shouldn't matter since they're all scaled the same.)

Let's suppose the difference here is really in default tone mapping or exposure. Should we be able to prove that's the cause — and not anything specific to punctual light sources — somehow?

If we found a constant factor for the difference in exposure, would that prove it?

In KhronosGroup/glTF-Blender-IO#1760, I actually found that simply omitting the conversion factor seemed to visually create equivalence. However, that scene did not include directional lamps. Although, I seem to remember I have on separate occasions (visually, through trial and error) found... Contradictory other conversion factors at other times. (Okay— What actually happened before is that in trial and error I ended up splitting the 4 and the π terms across different light types.)

Anyway. Ignoring EXR for now, here's a simple PNG screenshot from Three.JS with linear output, and a render from Blender with linear output, including all three GLTF lamp types. I exported with my KHR_lights_punctual branch, but to keep them on comparable scales set WATTS_TO_LUMENS to 1.0 (so the only applied transform is W to W/sr, and resulting ratios will be off by a constant factor of 683):

Triple Lamp Blender and Three.JS Linear Views

TriLampGUI
TriLampsScreenshot_redacted
TriLampsScreenshot_cropped
TriLampRender

(Note: I used 0.5 albedo here. Again: Shouldn't matter.)

(Side note: It looks like ortho camera scale is handled differently too... At least that should be fairly straightforwards to harmonize both to the spec 🤷)

And here I was going to composite the two together with division, to show that the entire silhouette of Suzanne is flat with a constant ratio. But instead, they ended up so similar that it's easier to compare them based on which is brighter, in which we find that many areas are so similar that they're apparently within the precision limits of the image bit depth:

Three.JS Vs. Blender Inequality Comparison

TriLampsComparison

Although for good measure here's also a base 1.1 logarithmic comparison, I guess. Red should mean Blender is 9% darker. Blue should mean Blender is 10% brighter. Wireframe edges are due to not perfectly aligning the screenshot to the render. It looks like the only real difference is that Blender's spot lamp cone falloff is more of a rounded plateau, which is unrelated to exposure:

Three.JS Vs. Blender Logarithmic Comparison

TriLampsLogComparison

We could also try to replicate this experiment using an OpenEXR image, I guess, if you think that would help further show it's just the exposure rather an effect of punctual light sources. — But then, what's going to illuminate the EXR? Can't use lamps, since then we're just back here— And are we absolutely sure E.G. that material emissivity and ambient light also work the exact same? I guess we could jack into the pipeline. I personally don't know enough of the API to do that.

Barring that, since they turn out exactly the same without the W:lm conversion factor of 683, that means that with the conversion factor applied Three.JS basically has 683 times the exposure of Blender.

(...And I suppose that brings us back to why this conversation is happening on a PR to switch the spec over to watts— On strictly a physical and visual basis, it now appears that I was actually correct to surmise that existing applications have default exposure settings that are scaled to sanely show W/m**2 and W/sr rather than lm/m**2 and lm/sr.)

Funnily, this is also confirmed by the first ever test I did with KhronosGroup/glTF-Blender-IO#1760. When I first noticed (and expected, based on the conversion factor) that spec-compliant exports would inevitably be grossly overexposed compared to Blender, I added an option to divide all lamp strengths by roughly the same value as the WATTS_TO_LUMENS factor (at that point I used 594), and that produced basically the results I was looking for:

EEVEE Render Vs. Three.JS in lumens with additional divisor to cancel out lumens conversion

PunctualRendered
PunctualExportedLumens


Aside 1 — Something still confuses me about Blender's units. They recommend using a 0.05 W point light to simulate a candela, and the only standard definition of a candle is 1 candela. But by our equation above, 0.05 W * 683 lm/W / (4 * PI m²) = 2.71 cd, not 1 cd.

I actually gave my opinion on this on developer.blender.org when I saw it linked, but did not specifically tag you:

About this specifically, I think that's a red herring. "0.05" feels like the kind of number that's meant to give a ballpark estimate more than an exact definition. It might just be a big candle— Or a candle meant to show up better in renders that will inevitably be shown on low-dynamic-range monitors. Actually, if anything, I'd be inclined to interpret the fact that the conversion shifts the value by three (Base-10) orders of magnitude and still ends up at roughly one candela as a sign that the conversion ratio of 683 is appropriate for Blender.

May be worth pointing out too that the values in that table also all have vastly different conversion ratios from each other. The lowest ratio is 227, and the highest is 556. Even between lights of the same type there is significant variation. To me that feels like an artist at the Blender Institute/Foundation arrived at those exact values through trial and error and copied and pasted them from their personal notes as a rule-of-thumb guide when they were writing the documentation.

I suspect this is a case of the the guide being written for/by artists more than for developers— "2.71cd" sounds like roughly the level of creative liberty that I would want to take to make sure a physically plausible candle stands out more, and "0.05" sounds like a nice round number I'd settle on to get there.


Aside 2 — I've done some work-in-progress implementing Blender-like view transforms in three.js.

I don't think that's applicable here, but for me personally it could actually be hugely useful for one of my workflows, so good to know!


— Oh, wait. No. With the lamps 683 times brighter, then that means other light sources like emissive materials and the skybox are going to be invisible by comparison. So I think that means that Three.JS is actually wrong— You can't put a 683 light source right next to a 0.2 or what-have-you emission map and expect it to look right. Or— Actually, no. Three.JS is fine(?), because emissive strength is meant to be specified in cd/m**2, which means that actually what needs to happen is that Blender needs to apply a conversion factor for emission strengths too when exporting with spec-compliant lighting units? Anyway—

@donmccurdy This has been a lot of information. Altogether, I think what this means is that we can close this spec PR and then finish the Blender exporter using the conversion factor of 683 as previously discussed— Am I missing anything else?

@will-ca
Copy link
Author

will-ca commented Oct 14, 2022

Further finding: Exposure difference factor will be different depending on .physicallyCorrectLights… That changes the factor by π, I think, though I'm not sure and not planning to double-check as it should probably be on(?) in PBR cases anyway.

https://github.com/mrdoob/three.js/search?l=JavaScript&q=physicallyCorrectLights

https://github.com/mrdoob/three.js/blob/59247fa177b9b3637bd64835c8b0ea4218a63010/src/renderers/webgl/WebGLLights.js#L227

@donmccurdy
Copy link
Contributor

donmccurdy commented Oct 14, 2022

[physicallyCorrectLights] changes the factor by π ...

This, but also the decay/attenuation curve is different in non-PBR mode. I'd advise testing only in .physicallyCorrectLights=true mode for sanity. 😅


Just curious, are these scenes using the Standard, or Filmic, view transforms in Blender? I don't think it will make a huge difference but I'd be curious to see the fireplace scene with Standard, if it's currently using Filmic.


But then, what's going to illuminate the EXR? Can't use lamps, since then we're just back here— And are we absolutely sure E.G. that material emissivity and ambient light also work the exact same? I guess we could jack into the pipeline.

Exposure controls and tone maps cannot differentiate between sources of light, so light reflected from a surface coming from a point light, or light values plucked directly from an OpenEXR file, are indistinguishable passing into the later parts of the image formation process. The latter should be equivalent to plugging an Image Texture node directly into the Surface socket of a Material Output node in Blender. We can do a similar test in three.js if needed.

I don't think we're going to see that the exposure defaults differ by a factor of anything like 683, we will get very similar results. There may be any number of reasons for this.


which means that actually what needs to happen is that Blender needs to apply a conversion factor for emission strengths too when exporting with spec-compliant lighting units?

If you test the model at https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/EmissiveStrengthTest, you'll see that it looks pretty close imported into Blender, three.js, and Babylon.js. A factor of 683 won't work out here without an opposite adjustment in exposure.


so the only applied transform is W to W/sr, and resulting ratios will be off by a constant factor of 683...

Just to check – you mean a factor of 1/4π is applied at export?


Altogether, I think what this means is that we can close this spec PR and then finish the Blender exporter using the conversion factor of 683 as previously discussed— Am I missing anything else?

I'm fine with closing the PR and proceeding with those changes in Blender, since the results appear to be a considerable improvement. I'm still a bit stumped as to why we're seeing what we're seeing.

@will-ca
Copy link
Author

will-ca commented Oct 14, 2022

This, but also the decay/attenuation curve is different in non-PBR mode. I'd advise testing only in .physicallyCorrectLights=true mode for sanity. 😅

Ah, yeah. I just used the live version of the viewer on your website for the screenshots, so I suppose that defaults to .physicallyCorrectLights=true.


Just curious, are these scenes using the Standard, or Filmic, view transforms in Blender? I don't think it will make a huge difference but I'd be curious to see the fireplace scene with Standard, if it's currently using Filmic.

The experimental sample scenes are all using "sRGB" for "Display Device" but "Raw" for "View Transform", which should give linear results I think, then with "Save as Render" enabled when saving images and the colour space set to "Linear" in the compositor (visible in the screenshot) when loading them for the comparison. Where applicable I then also changed "outputEncoding" to "Linear" in the Three.JS viewer to match, which is visible in the screenshot(s).

The mouse scene is with "Standard", and also with "sRGB" for "Display Device".

All are "Look: None", "Exposure: 0.0", "Gamma: 1.0".


I don't think we're going to see that the exposure defaults differ by a factor of anything like 683, we will get very similar results. There may be any number of reasons for this.

Hm. Well, on a strictly numeric basis, the factor is basically 1.0 (and divided by 4π for points and spots). It's just that for a physically accurate conversion from Blender's watt-based units to lumens for KHR_lights_punctual, we have to multiply light intensities by 683, so that's where the factor comes from. — It's a result of assuming lumens-based units for GLTF but watt-based units for Blender, and thus having to feed Three.JS much larger numbers to start with.

The equivalent test for values read from an OpenEXR file in Blender and in Three would also have to include whatever the unit conversion factor there is, I think— We would have to feed a brighter EXR file to Three.JS because those units are also different. So then you would have the exposure "defaults" differ by whatever that factor is. But it's, like, due to how the units are handled (or not handled). (And we can't just treat the file as dimensionless, because then it loses all meaning for representing an amount of light.)


If you test the model at https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/EmissiveStrengthTest, you'll see that it looks pretty close imported into Blender, three.js, and Babylon.js. A factor of 683 won't work out here without an opposite adjustment in exposure.

However, Blender currently doesn't convert units on import either. If Blender were to handle imports correctly as per spec, in this case that file would appear several hundred times darker in Blender than in the other viewers.

Currently I have an input box allowing users to specify a scaling factor for all light intensities, and a tooltip advising that engines may have different default exposures and suggesting they input "1/600" (Would be "1/683".) there manually for many engines (E.G. if the exported model looks too bright).

I personally feel that would have to be the way forwards, assuming nothing else changes. We have to multiply by some factor around 500 to 700, because the spec says that light intensities are in lumens and that is the conversion factor from the units Blender uses. And in that regard, Blender is behaving correctly— A value like 700 lumens is what you can expect from a real-world lightbulb (10W LED, 13W compact fluorescent, or 60W incandescent), and roughly equivalent in practice to 1W of radiated power (as you might define in Blender), which Blender displays correctly/sanely with its default exposures. It's just that when you feed that as a physically accurate, spec-compliant lumens value into every other engine out there, they all display it way too bright with default exposure settings.

So barring, E.G. a spec change like this PR that would let us convert instead to a unit that works better with the rest of the ecosystem, the only thing that can be done from Blender is to convert to lumens as specified, and then give users an option to cancel it out/tweak the result if that doesn't play nice with their engine of choice.


Note that this also means that all of the example files— If they display "correctly" in the reference viewers— Are all actually ridiculous dark— And that is compensated for by all of the rendering engines having default exposures that are way too bright. E.G. the EmissiveStrengthTest file above has emissiveStrengths of 2, 4, 8, and 16. If these are in cd/m**2 (nits) as per the spec, then according to WolframAlpha the darkest one is lower than the "luminance of a twilight sky" and the brightest is still six times darker than the "a heavy overcast day". The lowest value is so dark, actually, that apparently it is outside of the range of human sight, at "0.7 × minimal luminance for human color vision". For comparison, LCD panels seem to range from 200 nits to over 1,000 nits. So the results of that example file definitely shouldn't look as bright and bloom-y as they do, with any kind of sane default exposure settings assuming units per spec.

Again, I think it is worth considering to maybe just change the entire dang spec over to W/m^2 and W/r^2 instead of lm/m^2 and lm/r^2, since that seems to be how the entire ecosystem, all reference viewers, and even the example files are actually behaving, even if they say they're using lumens.


Just to check – you mean a factor of 1/4π is applied at export?

To Point and Spot lamps, yes. That takes them from Blender's total, omnidirectional radiated power to power density per square radian, and brings them into proportion with Sun lamps, which do not require such a factor.


I'm fine with closing the PR and proceeding with those changes in Blender, since the results appear to be a considerable improvement. I'm still a bit stumped as to why we're seeing what we're seeing.

It's because the specs say to use lumens, and the reference implementations say they do use lumens, but in reality all the implementations and even the example files have been set up as if they're using watts. E.G. Even the example file you linked above is full of emissiveStrengths that are so insanely dark that they'd barely be visible under indoor lighting IRL if we assume they're in lumens nits. So when they receive a number that is actually in lumens, it isn't scaled to how they actually behave and it ends up looking way too bright.

I'm... Actually going to leave this PR open, and hold off a bit longer on finishing the changes in Blender. If it is possible, I've gone back to thinking that it would be a far, far cleaner and more stable solution in the long term to switch the spec over watt-based behaviour. Three and Babylon already act that way, and so does the Khronos Group viewer— The only real risks for disruption IMO are if (1) somebody downstream of Three/Babylon is using actual lumen values (improbable? I think? Since not even the example files use actual lumen values and without Blender support I'm skeptical whether there even exist any tools that can generate accurate lumen values) or if (2) somebody like Arnold or Dassault or whatever has their own implementation that is scaled to handle lumens correctly that we don't know about.


@donmccurdy Summary: Specs say to use lumens/candela/nits. Implementations say they're using lumens. They aren't actually. Even the example files don't make any sense if we take them as lumens-based values. If we implement correct lumens-based exports in Blender, Blender will AFAICT be the only tool in the entire ecosystem that handles the units spec correctly, which.... Could be a problem. I recommending considering changing the spec to watts. That is what everyone is actually already using, so that is the easiest way to bring everything into alignment with the spec without requiring massive downstream changes, and will also allow Blender export defaults to look the same as all other implementations' defaults by simply getting rid of the 683 lumen conversion factor.

@donmccurdy
Copy link
Contributor

donmccurdy commented Oct 14, 2022

Again, I think it is worth considering to maybe just change the entire dang spec over to W/m^2 and W/r^2 instead of lm/m^2 and lm/r^2, since that seems to be how the entire ecosystem, all reference viewers, and even the example files are actually behaving, even if they say they're using lumens.

It doesn't feel plausible to me that the whole ecosystem of photometric lighting pipelines is accidentally using Watts, not Lumens, for punctual lights... I don't know of any common renderers, other than Blender, claiming to use Watts? Even to Blender's users, converting their lights to any photometric system seems to be a source of confusion.


I am totally willing to believe that the default exposure in three.js might be poor for a typical indoor or daylight scene, though. It's unitless, and we should probably fix that. There's an interesting comment from @romainguy (google/filament#3338 (comment)) saying, in reference to Filament:

You can always use unitless lights and exposure. See https://github.com/google/filament/blob/main/filament/include/filament/Camera.h#L379

By using this API, you can then set your light intensities to be unitless (for instance "1") like you do in Blender, Unity, etc.

I hadn't heard the phrase "unitless" used to describe this situation before, but I think that's what's going on here? We've left exposure at 1, and as a result any comparison of the image on screen to the light intensities is useless. The image is not physically-based. Presumably this hasn't been an issue in general, because few tools export models including lights, and so most viewers have IBL setups with relative intensities that are chosen to match their exposure values.

If that's not it, then I've run out of useful ideas to contribute on this thread, and I'm hoping one of the authors of these lighting engines can chime in to correct me. 😇

@will-ca
Copy link
Author

will-ca commented Oct 15, 2022

I hadn't heard the phrase "unitless" used to describe this situation before, but I think that's what's going on here? We've left exposure at 1, and as a result any comparison of the image on screen to the light intensities is useless. The image is not physically-based. Presumably this hasn't been an issue in general, because few tools export models including lights, and so most viewers have IBL setups with relative intensities that are chosen to match their exposure values.

Yes! Actually, I think that's probably a way more accurate way to describe the situation than "accidentally using Watts". I suppose it's not that everyone is accidentally using the "wrong" units, but more that most tools simply do not account for the unit at all, and instead simply map the in-engine RGB values directly to the screen, and I guess nobody's noticed/complained to now because like you say assets and software haven't really made heavy use of lights with physically based strengths yet ....And to me personally that looks like "accidentally using watts", because I'm used to Blender and when Blender decided to move past their own unitless setup they just slapped a "Watts" label on everything (and that happened to work out with mostly sane exposures), so when I see the unitless results I associate it with Watts and and I call it "Watts", but in reality what's happening is that the pipelines are unitless, not that they're using accidentally the wrong unit. Thanks for pointing that out! Concluding the entire ecosystem was blithely using the wrong unit did feel a bit silly, but I wasn't sure how else to describe it.

Also, good to see that Filament is exposing renders based on lumens as specified, I think? If I can get that to run I can validate export results against it.

I am totally willing to believe that the default exposure in three.js might be poor for a typical indoor or daylight scene, though. It's unitless, and we should probably fix that.

That would also be great. Well, I was reluctant to suggest it as the sheer scale of Three has been a bit overwhelming to me whenever I've tried to fully dive into it, and I assume similar changes would also eventually have to be made to Babylon and possibly others, so.... Somehow trying to change the spec felt like the less disruptive option. 😅

@donmccurdy So.... I think that can settle it? As long as there's also an easy way for viewers like Three to expose it correctly, there's no issue with aligning Blender to the spec with 683, so I'll close this thread and polish off the patch?

@donmccurdy
Copy link
Contributor

So.... I think that can settle it? As long as there's also an easy way for viewers like Three to expose it correctly, there's no issue with aligning Blender to the spec with 683, so I'll close this thread and polish off the patch?

Agreed! I'm happy to call this thread 'solved' and go ahead with the your changes in KhronosGroup/glTF-Blender-IO#1760, including the 683 lumens per watt factor.

Getting auto exposure in viewers is probably not so easy, but seems worth trying to figure out. There's some good discussion in #2128 about what would be required for glTF exports to provide that information. In the meantime I'll at least get the "exposure" slider on my viewer fixed (donmccurdy/three-gltf-viewer#269), and look into auto-exposure implementations.

Thanks for working through all this with me, I've learned a few things here. 🙂

@will-ca will-ca closed this Oct 15, 2022
will-ca added a commit to will-ca/glTF-Blender-IO that referenced this pull request Oct 16, 2022
- Set `WATTS_TO_LUMENS` to 683 convention.
- Add compatibility modes for unitless pipelines and raw Blender units.
- Add explanation text. (Currently placeholder, may remove.)
- Renamed "Geometry" export subpanel to "Data".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants