Updates to lighting in three.js r155

The upcoming release r155 will contain a major change in context of lighting. The legacy lighting mode will be disabled by default which affects all light intensities and how point and spot lights decay.


What does that mean for me?

The release changes the default of WebGLRenderer.useLegacyLights from true to false and also deprecates the property. Meaning it will be eventually removed from the engine with r165.

Setting WebGLRenderer.useLegacyLights to false is one important prerequisite for achieving physically correct lighting in apps.

Disabling legacy lighting mode has two effects:

  • Light intensities are not internally scaled by factor PI anymore. This so called artist-friendly light intensity scaling factor was in place to make it easier to achieve good looking lighting with intensities around 1. However, in context of physically correct lighting such scaling factors are incorrect and make it impossible to use proper SI units.
  • Point and spot lights now decay in physically correct ways.

If you are not already disabling the legacy lighting mode in your app, you will have to update light intensities when upgrading to r155.


Migrating

If you can’t effort a migration now, you can simply set the value of WebGLRenderer.useLegacyLights back to true. Since the property is going to be removed in the future, you should consider to update the lighting in the next months.

The intensities for ambient, hemisphere, directional lights and light maps can be restored by multiplying PI with the existing light intensity values.

The intensity of point and spot lights is measured in candela (cd) now which usually requires much higher intensity values than before. Restoring their decay to the previous behavior isn’t possible because of different computations in the shader. So scenes with point and spot light will look slightly different with r155 even with updated light intensities.

It’s important to understand that using the new lighting mode is just one prerequisite for physically correct lighting. You also have to:

  • apply a real-world scale to your scene (meaning 1 world unit = 1 meter).
  • not change the default decay values of 2 for all spot and point lights in your scene.

Only then you can actually consider SI units for point, spot and area lights. Ambient and hemisphere lights (which are special kind of lights and essentially simplified models of light probes) as well as directional lights do not use SI units.


Motivation

The goal of this change is to provide a single lighting mode that supports physically correct lighting. We believe the engine’s lighting should have been implemented like this in the first place meaning without internal scaling factors or custom decay implementations.

Besides, WebGPURenderer will only come with a single (physically correct) lighting mode. We are convinced it’s better to make this change in WebGLRenderer now so a switch to WebGPU will become easier.


Questions

For general questions, please reply to this thread. If you would like help updating some existing code, consider starting a new thread with the full context, then including a link in this thread.

We also recommend to check how the official examples and the manual will be updated to get a better impression of the migration tasks. You can do that by simply comparing the example code between r155 and 154.

21 Likes

Everyone who wants a sneak preview here are the examples from the dev branch which are already migrated to the new lighting: three.js examples

1 Like

not sure i’ve found an error here but the results look completely blown out…

I’m guessing “sneak preview” is the operative term here and will take a while to update all the examples to demonstrate the benefits of this update?

1 Like

Unfortunately, you get a cached version of the old build.

I’ve updated the link so it uses a commit hash which prevents caching issues. Thanks for the feedback!

2 Likes

right that makes sense, examples look really great now! np :slight_smile:

1 Like

Couldn’t this be done with a scaling constant setting in the renderer? There are valid reasons to use a different world scale.

1 Like

If it is not possible for you to scale the scene (which would be some sort of top-level scale), then you can’t use SI values. I don’t think it’s reasonable to introduce a separate scale factor to the renderer again.

I mentioned it before in another topic i guess, but Unreal Engine and afaik other engine have an option for world scale too, it would be generally useful for future as well as other usage such as for physics engines conversion of metrics etc. UE also uses 1 unit = 1 centimeter as default for instance, i assume it would be nothing more than scaling a distance value.

I’m fine with just patching it in but it would be generally nice to have the option. There are for instance apps and games required being in a generally small or large scale or both, these couldn’t use physical correct lighting out of box then.

1 Like

I am yet to understand and experience the effect of the planned changes. However, I see three things, that I personally consider advantages:

  • Using SI units (for distances, for lights, etc) is a form of standardization in Three.js, making it adhere to the whole scientific world. This increases interoperability and conformance. If all 3D software decides to become compatible … the SI units are the best meeting point.

  • Using SI units also makes it easier to transform Three.js programs into VR/AR, as there is no need to rescale and reposition everything. This also simplifies “reading” a program as numbers have absolute meanings, instead of being relative quantities.

  • It looks likes this change will simplify the core.

Having all above said, I also:

  • Understand users might have to change some of their programs.

  • Users may find things that are impossible to do (e.g. with the recent changes of color encoding I have cases where I still cannot make some scenes look good with the new style, so I revert to the old style).

  • I assume that it still be possible for people to use other units (e.g. 1 unit = 1mm), they only have to adjust other parameters too, although it will not be possible to completely emulate the non-physical model.

  • It might be good to have some form of flexibility by coefficients, controllable by the user. For example, if the model uses f(x)=e-x², it could be made to use f(x)=eax²+b, where a=-1, b=0, but both of them can be changed by the user.

6 Likes

That’s right!

@Fyrestar Did we ever discuss the “world scale” topic at Github so far? I did not find an issue but I’m not sure. If not, it might be worth to shift this particular discussion to GitHub.

4 Likes

I’m not sure it has been discussed a couple times over past years, mostly here i think, like here or another recently but yes, better to discuss on Github directly.

Cool!

which examples showcases the update really obviously ? or has the intensity values been tuned so that examples from both r155 and r154 look almost same ?

2 Likes

That one. Overall, the scenes almost look identical.

1 Like

can you not just scale the scene object?

1 Like

Exactly, why not just set scene.scale.multiplyScalar(100) to eg go from cm to m?

1 Like

No, because that changes the size of the scene in the 3D world, but not the world itself, due to a Scene being merely a top-level node in the transform graph (any transform you apply to it is currently equivalent to the same transform as if it were an Object3D). It could be possible, but would need to be implemented.

Once always-physical lights is released and well tested, then it might make sense to think about adding options (if maintainers agree with the added complexity), f.e. changing the SI units to other units, f.e. cm instead of m for distances, or alternative light intensity/power units, etc.

One step at a time.

In the meantime you have to convert units yourself.

// this 1000cm value will be converted to 10m for the engine
mesh.position.x = cmToM(1000)

where cmToM is your own helper.

I’m trying to work out how to convert ambient and directional lights to work in r155. I haven’t started on the directional lights, but here’s what I’ve found with the ambient lights:

With an ambient light with color = 0x666666, intensity = 1, and several MeshStandardMaterials:

Material Name | Material RGB  | Rendered RGB (r149) | Rendered RGB (r155)
Red           | 1, 0, 0       | 0x660000            | 0x3a0000
Gray          | 0.5, 0.5, 0.5 | 0x333333            | 0x282828
Black         | 0.1, 0.1, 0.1 | 0x0a0a0a            | 0x0e0e0e

So the change must be more than a simple multiplication by pi. Bright colors are darker in r155, and dark colors are brighter. What else changed?

PCL or physically correct lighting is based on the PBR (or physically based rendering) workflow, whereby the scale of the scene determines the physically correct decay of lighting, so you’re correct, for instance, if your scene is 4000 meters wide, a multiplication by pi would not make much of a difference to the lighting effects…

The colors are exactly the same in different regions of my scene. Large objects, small obects, near objects, distant objects, they all render exactly the same RGB value (when the only light in the scene is an ambient light).