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

Physically based light units #4618

Merged
merged 27 commits into from
Sep 23, 2022
Merged

Physically based light units #4618

merged 27 commits into from
Sep 23, 2022

Conversation

GSterbrant
Copy link
Contributor

@GSterbrant GSterbrant commented Sep 5, 2022

Fixes #3252

Description

Enables the use of physically based light units, by having the physicalUnits enabled on the scene, and setting the luminance (not intensity) parameter on the lights. For spot lights, this also means the strength of the light is dependent on the aperture (outer cone angle), meaning a narrower angle will concentrate its energy on a smaller surface, thus making that area brighter than if it was using a wide angle.

Also contains an example scene showing off physically based light and camera units in a typical scene with a strong sun, sky and a bunch of local lights, called Physical Light Units.

Also fixes support for KHR_punctual_lights where we previously clamped the light intensity.

Kapture.2022-09-05.at.13.01.20.mp4
Kapture.2022-09-05.at.13.06.56.mp4

Public API changes

light.js

  • luminance (get/set) strength of light in lumen

scene.js

  • skyboxLuminance (get/set) strength of skybox light in lux (lm/m^2)
  • ambientLuminance (get/set) strength of ambient light in lux (lm/m^2)

light/component.js

  • luminance (get/set) strength of light in lumen of light component

if (this._scene?.physicalUnits) {
if (this._type === LIGHTTYPE_SPOT) {
const angleAsRadians = this._outerConeAngle * Math.PI / 180.0;
i = this._luminance / (2 * Math.PI * (1 - Math.cos(angleAsRadians / 2.0)));
Copy link
Contributor

@querielo querielo Sep 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure but shouldn't we use "_innerConeAngle" here?

Maybe i = this._luminance / (2 * Math.PI * (1 - (this._innerConeAngleCos + this._outerConeAngleCos) / 2.0))); or something similar.

Also, I'm just curious why do we use Math.cos(angleAsRadians / 2.0) and not Math.cos(angleAsRadians) / 2.0?

Nice PR!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so the outer cone angle controls how much energy should transmit through the light volume, such that we can convert the light in lm to lm/m^2, so it would only make sense to use the outer angle as the inner angle is only used to feather the edge of the light.

As for the math, I would defer to the filament documentation.

Copy link
Contributor

@querielo querielo Sep 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds a little strange to me that energy does not change for innerConeAngle === 0 and innerConeAngle === outerConeAngle. Filament evaluates this as if the spot light has uniformly distributed intensity.
Screen Shot 2022-09-07 at 00 20 24

But between innerConeAngle and outerConeAngle we have the angular falloff attenuation ((cos(angle) - _outerConeAngleCos) / (_innerConeAngle - _outerConeAngleCos) or something like this). So, maybe multiplier has to use linear combination of cos(innerConeAngle) and cos(outerConeAngle) or something between them.

Copy link
Contributor

@querielo querielo Sep 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how pbrt approximates it: https://github.com/mmp/pbrt-v4/blob/faac34d1a0ebd24928828fe9fa65b65f7efc5937/src/pbrt/lights.cpp#L1463

The code was added here

Rendering can now be performed in absolute physical units with modelling of real cameras as per Langlands & Fascione 2020. Code contributed by Anders Langlands & Luca Fascione Copyright © 2020, Weta Digital, Ltd.

Anyway I have no idea on what their approximations are based :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When thinking about this a bit, the inner angle basically defines a border outside of which the spot light cone is in the shape of solid angle, where no inner == outer means we have a perfect cone and inner == 0 means we have a perfect solid angle. If we take that into account, the amount of energy in the volume is dependent on the inner angle, where there is a bit of energy lost if the inner angle is less than the outer.

In these illustrations I use a hemisphere to indicate the solid angle cutout of the spot light. I hope they make sense.

Solid angle when outer == inner
image

Solid angle when inner == 0.
image

Solid angle when inner > 0 && inner < outer
image

So we can definitely look into accounting for that energy loss, I don't think it makes a ton of difference but it's definitely worth looking into.

Copy link
Contributor

@querielo querielo Sep 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I constructed a plot for an angular falloff attenuation model:

The angular falloff attenuation + smoothstep

Screen Shot 2022-09-12 at 13 00 15

Dash lines show luminous intensity factor. As you can see on the model, the purple area is equal to the blue area. So, the green area plus the purple area are equal to the green area plus the blue area, and whole area is area of the rectangle. And we get similar result to the formula from PBR book).

But of course, possibly, I misunderstood it all.

Copy link
Contributor

@querielo querielo Sep 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GSterbrant
I don't know if it is correct. So, here is an issue in FIlament. Since, they don't use the smoothstep function their falloff attenuation function is linear.

Also, I don't understand why angles in PBR book are not divided by two. Since the whole integral integrates from 0 to theta_outer / 2.

@MAG-AdrianMeredith
Copy link
Contributor

This is fantastic! As a light unit layman I noticed that punctual lights uses candela (lm/sr) but we're using lm here as the unit type. is that correct? what needs to be done as a user to get consistent results?

@GSterbrant
Copy link
Contributor Author

@MAG-AdrianMeredith We support point lights in lumen. However, you can always write a converter candela -> lumen and feed that value instead. I thought we might want to write a few converters as well, so you'd be able to basically feed whatever value you'd like and you'd get back a value with the units we are using, would that be useful?

@MAG-AdrianMeredith
Copy link
Contributor

Thanks for the update, I'm just wondering what the reason is for not following gltf's definition of lights seems to be a pretty common definition for light intensities https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md

@GSterbrant
Copy link
Contributor Author

@MAG-AdrianMeredith Ah, so from my understanding, glTF provides light intensities in candela because it represents luminous power per solid angle. From a user perspective, using candela makes little sense since you set the luminous power and either have a point/omni light, for which the angle is a fixed 4 PI, or use a spot light with explicit cone angles.

However, in the glTF, the lights angles are already known, so they can pre calculate that value and therefore provide the implementor with those fixed values. I hope that makes sense 🙂 .

@GSterbrant
Copy link
Contributor Author

@querielo I implemented your suggestion, huge thanks for all the legwork there! To test it, I used a spot light with a 0 inner angle and 90 degree outer angle, which when having the same luminous power as 1/4 of a point light gave identical light intensity, which makes a lot of sense since a point light could be conceptualised as having 4 x 90 degree spot lights in terms of energy per square meter.

@MAG-AdrianMeredith
Copy link
Contributor

@GSterbrant So its just a terminology thing and they should line up (given similar camera setup) great thats what I thought.

@MAG-AdrianMeredith
Copy link
Contributor

MAG-AdrianMeredith commented Sep 13, 2022

One question, how does IBL and lightmaps fit into this equation? I'm currently testing against this PR in a fork (I've been tasking with supporting physical light units for a project if you haven't noticed already ;) ). Currently enabling this plunges everything into complete darkness (including the gltf_punctual_lights) and boosting the skyboxIntensity settings produces a strange result where it looks like only the specular component is lit.
setting the aperture to 0.05 seems to produce a similar result but is obviously not physically accurate

I'm assuming this is just because its not done yet?

p.s. great work as always everyone

@GSterbrant
Copy link
Contributor Author

@MAG-AdrianMeredith In this PR, I would suggest the lights-physical-units example which shows off a scene using the GLB with punctual lights and all that. That GLB does have very weak lights, you need to reduce the strength of the IBL, sun, spot, point and area light and turn the exposure settings up to see it.

Would it be possible to post a picture showing off the issues you're seeing?

@MAG-AdrianMeredith
Copy link
Contributor

It seems to me that the default camera sensitivity is two orders of magnitude out from what you'd expect. From what I can see a really bright bulb is around 1000 lumens. in the default settings this wont even cast any light and the example is set to 100000 and is still really dim.

lumens-bltdirect
Light-Output-Lumens-Chart-Meant2be-Co-1

@GSterbrant
Copy link
Contributor Author

@MAG-AdrianMeredith The default camera settings are adjusted for outdoors lighting like the sun and sky. In proportion to the sun at around 100k-120k lm/m^2, a 100W light bulb is something like 1100/4*PI = 91.6 lm/m^2 given no attenuation, so it makes sense that those settings are poorly adjusted for indoor lighting.

I found this link giving a list of approximate lux (lm/m^2) readings: https://en.wikipedia.org/wiki/Lux.

@GSterbrant
Copy link
Contributor Author

@MAG-AdrianMeredith So we will definitely need to implement automatic exposure correction to adjust for what I just mentioned. That's going to be a separate PR though 🙂, for which we created this issue: #4650.

src/scene/light.js Outdated Show resolved Hide resolved
@mvaligursky
Copy link
Contributor

mvaligursky commented Sep 20, 2022

Just a note here, that the clustered lights store light colors in 2 bytes per channel (so not full floats). So for physical lights, where the color range is really large, this would mean a pretty low precision. But I suspect in reality this could be just fine.

@mvaligursky mvaligursky added the release: next minor Ticket marked for the next minor release label Sep 21, 2022
src/scene/light.js Outdated Show resolved Hide resolved
@GSterbrant
Copy link
Contributor Author

Thanks everyone for the comments and help!

src/scene/scene.js Outdated Show resolved Hide resolved
src/scene/scene.js Outdated Show resolved Hide resolved
@willeastcott
Copy link
Contributor

This is soooo cool. Can you be explicit about what new API is introduced in the PR description, please?

mvaligursky and others added 2 commits September 23, 2022 12:24
Co-authored-by: Will Eastcott <will@playcanvas.com>
Co-authored-by: Will Eastcott <will@playcanvas.com>
@mvaligursky mvaligursky merged commit 1da43a4 into main Sep 23, 2022
@mvaligursky mvaligursky deleted the gsterbrant_pb_light_units branch September 23, 2022 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: gltf area: graphics Graphics related issue release: next minor Ticket marked for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement support for physically based light intensity units
5 participants