-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Conversation
src/scene/light.js
Outdated
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))); |
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.
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!
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.
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.
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.
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.
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.
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 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 :-)
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.
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
Solid angle when inner > 0 && inner < outer
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.
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.
I constructed a plot for an angular falloff attenuation model:
The angular falloff attenuation + smoothstep
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.
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.
@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.
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? |
@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? |
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 |
@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 🙂 . |
@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. |
@GSterbrant So its just a terminology thing and they should line up (given similar camera setup) great thats what I thought. |
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. I'm assuming this is just because its not done yet? p.s. great work as always everyone |
@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 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. |
@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. |
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. |
291331e
to
317f2ad
Compare
Added documentation references to the calculation for the lux calculation for the light sources. Changed from an if chain to switch to make it more clear the last condition is for directional lights.
If the scene changes the physical units flag after the light is added, the lights will need their final color calculated to reflect that.
93e91d6
to
71bb6c6
Compare
Thanks everyone for the comments and help! |
This is soooo cool. Can you be explicit about what new API is introduced in the PR description, please? |
Co-authored-by: Will Eastcott <[email protected]>
Co-authored-by: Will Eastcott <[email protected]>
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
scene.js
light/component.js