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

Make Light targets optional #14658

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open

Conversation

mbredif
Copy link
Contributor

@mbredif mbredif commented Aug 7, 2018

(This PR is the geometric counterpart of #14621)

This enables the use of custom user-provided OrthographicCameras for DirectionalLights and PerspectiveCameras for SpotLights, by making optional the 2 hardwired updates that were updating the cameras at each frame :

  1. Light.target was unconditionnaly tracking an Object3D using its matrixWorld
  2. SpotLightShadow.update was modifying the shadowCamera to have (most notably) its fov fit its cone angle.

Possible use-cases are, combined with #14621, to do image-based rendering by backprojecting images as textured lights, with photogrammetrically-estimated camera parameters, or to more simply implement a slide or video projector with user-provided intrinsics/extrinsics (focal, aspect, position, rotation...).

Proposals :

  1. make target tracking opt-in : the default is undefined (no tracking). examples have been modified to either make a light.lookAt or assign a target object
  2. make cone fitting opt-out : SpotLightShadow.cameraAutoUpdate is introduced (default: true) to disable the cone fitting of the camera.

WDYT?

--
Edit : item 2 moved to its own PR (#14663)

@looeee
Copy link
Collaborator

looeee commented Aug 7, 2018

In simpler terms, after this PR there would be two ways to change the direction of these lights:

  • for once off redirection, use light.lookAt( vector3 )

  • for per frame redirection use light.target = object3D. In this case the target needs to be added the scene graph, just like the current situation.

I think that this would be an improvement over the current situation which causes a lot of confusion. On the other hand, at least for DirectionalLight I prefer #5079 (remove target completely and use rotation to set light orientation).

In the case of #5079 a lookAt method would still be desirable though.

@mbredif mbredif changed the title Make Light targets and SpotLight cone fitting optional Make Light targets optional Aug 8, 2018
@mbredif mbredif force-pushed the LightTargetOptional branch from 3e2eb41 to e7c597c Compare August 8, 2018 10:18
@mbredif
Copy link
Contributor Author

mbredif commented Aug 8, 2018

PR updated : item 2 moved to its own PR #14663.

Thanks @looeee for the pointers, I was not aware that the removal of the target feature had been debated ! So my proposal is a smoother breaking change than complete removal : instead of removing the feature, it is only disabled by default (the user has to create the target object to enable it).

for once off redirection, use light.lookAt( vector3 )

If I got it right, it is more than that : the shadow camera will get exactly the matrixWorld of the light object and the light direction is extracted from this matrixWorld (as it is already done currently for the position). Thus setting it with lookAt is only one option, the user may manipulate the light object as any object3D (position, rotation...), similar to how a regular camera may be moved.

@looeee
Copy link
Collaborator

looeee commented Aug 8, 2018

@mbredif see also #13373 and my comment there. One compelling reason for removing the target completely is that three.js is the odd one out - nearly every other app doesn't have targets for their directional lights. I'm not sure what the situation is for spot lights.

Your solution look like it may give us the best of both worlds though, we can set the light's rotation directly as other apps do and have a target if we want it.

@mbredif mbredif force-pushed the LightTargetOptional branch 9 times, most recently from c166e46 to 9ab0e93 Compare August 8, 2018 15:56
@mbredif
Copy link
Contributor Author

mbredif commented Aug 8, 2018

src

I have reworked the approach and I am now quite satisfied with having light.matrixWorld as the single point of contact for the light geometry :

  • shadow cameras are no longer added to the scene, they just copy light.matrixWorld and apply further transforms (inversion, camera convention, projectionMatrix...) to get the shadow matrix
  • the world light position should be accessed everywhere using position.setFromMatrixPosition( light.matrixWorld );
  • the world light direction should be accessed everywhere using light.getWorldDirection( direction ).negate();
  • SpotLight and DirectionnalLight are now rotated, so that there is no need to rotate their helpers.

examples

I have tried to update all examples, by :

  • creating the target : light.target = new THREE.Object3D();
  • or doing a lookAt : light.lookAt( 0, 0, 0 ), which is only necessary if the light.position is modified.

Results look good and I see no errors left, but I find it hard to verify all examples by hand. Is there an automated way of doing it ?

test

npm run test fails on JSON serialization/deserialization (mismatching quaternion/positions).
I need help here !

@WestLangley
Copy link
Collaborator

the world light direction should be accessed everywhere using light.getWorldDirection( direction ).negate();

Unfortunately, that is a pretty inefficient method. I would avoid using it.

Maybe try something like this:

spotlight._target = new THREE.Object3D();
spotlight._target.position.set( 0, 0, 1 );
spotlight.add( _target );

You can then get the world direction of the spotlight from the world matrices of spotlight._target and spotlight.

@mbredif
Copy link
Contributor Author

mbredif commented Aug 8, 2018

Good point, but IMHO that only means that the current getWorldDirection implementation is suboptimal...

Provided matrixWorld is in sync, isn't it just a matter of reading the 3rd column ?

new THREE.Vector3(spotLight.matrixWorld.elements[8], spotLight.matrixWorld.elements[9], spotLight.matrixWorld.elements[10])

I can do another PR for that.

@WestLangley
Copy link
Collaborator

Yes, that is much better. Normalize it, though, because the matrix may be scaled.

@mbredif
Copy link
Contributor Author

mbredif commented Aug 9, 2018

See #14677 for the getWorldDirection optimization.

Any clue on the test correction and on the example verification ?

@mbredif mbredif force-pushed the LightTargetOptional branch 3 times, most recently from 9ebdbd4 to e54fd2a Compare August 9, 2018 11:44
@mbredif
Copy link
Contributor Author

mbredif commented Aug 9, 2018

Ok I figured out to fix the test (this.matrix was out of sync).

This PR is now ready for testing/review !

@mbredif mbredif mentioned this pull request Aug 10, 2018
@mbredif mbredif force-pushed the LightTargetOptional branch from ddbdbaa to 4ff4805 Compare August 13, 2018 08:02
@mbredif
Copy link
Contributor Author

mbredif commented Aug 13, 2018

As discussed in #14688, use transformDirection instead of getWorldDirection when matrixWorld is already up to date.

@mbredif mbredif force-pushed the LightTargetOptional branch from 4ff4805 to 8558967 Compare August 13, 2018 08:35
@looeee
Copy link
Collaborator

looeee commented Aug 17, 2018

Well, if we are ok with a breaking change here in order to be in line with other apps, that seems like the better option rather than removing the target completely.

That way when people have to update their code that uses a light target, they just have to add 'light.targeted = true'. If we remove the target completely then they may have to do considerable refactoring.

@mbredif
Copy link
Contributor Author

mbredif commented Oct 4, 2018

Up !
@WestLangley @mrdoob @looeee , any decision on the possible deprecation of the target functionality ?
I can remove it, make it opt-out or make it opt-in, just tell me.

@WestLangley
Copy link
Collaborator

@mrdoob is going to have to decide what features he wants, then we can debate the implementation details.

Also, any changes made in this PR should be consistent with #14935.

@WestLangley
Copy link
Collaborator

This was a feature that was suggested by @mrdoob years ago -- removing targets from lights.

@mbredif has invested a lot of effort in this PR. If the feature is still desired, we should push to integrate it.

@WestLangley
Copy link
Collaborator

As @WestLangley said:

Removing targets from lights was suggested by @mrdoob years ago.

@mbredif has invested a lot of effort implementing @mrdoob's suggestion.

I think @mbredif is entitled to a response to #14658 (comment).

@Mugen87 Would you also be willing to provide your feedback? (I expect you will have to read the entire thread carefully because a lot of issues were discussed.)

I see @donmccurdy may also support this. In light of the discussion in #14935, perhaps you have a preferred implementation.

//

Please also see #14621.

@mrdoob
Copy link
Owner

mrdoob commented Jul 27, 2020

One approach to maintain backwards compatibility would be by doing something like this:

Light -> FreeSpotLight -> SpotLight

FreeSpotLight extends from Light and has no target.
SpotLight extends from FreeSpotLight and adds a target.

I took the "Free Spot Light" name from 3D Studio Max but open to better names.

@mrdoob mrdoob added this to the rXXX milestone Jul 27, 2020
@donmccurdy
Copy link
Collaborator

What would you think of reversed naming? SpotLight would have no target, but be extended by a subclass that adds the target?

  • TargetedSpotLight
  • TrackingSpotLight
  • LegacySpotLight
  • ...

The latter could be moved to examples/jsm, eventually. I realize that's a breaking change, but at least we can simplify the migration with warnings when .target is accessed on a plain SpotLight. If we want the end state to be untargeted by default, it seems like that should be associated with the more intuitive name.

@LeviPesin
Copy link
Contributor

@mbredif Can you please update the PR?

@hybridherbst
Copy link
Contributor

Still an issue! I think the target properties are about the soon-be-deprecated for at least 5 years now 🥲

@hybridherbst
Copy link
Contributor

@mrdoob @donmccurdy do you think you can agree on a naming for new light types? If so, which?

@donmccurdy
Copy link
Collaborator

donmccurdy commented Sep 10, 2023

Personally I'm fine with any of these proposals, but perhaps happiest if we have a solution compatible with moving the targeting logic (maybe, eventually) move into addons/jsm/*.

One more idea to throw into the hat, add a LightTarget class, which can be bound to any compatible light source: DirectionalLight, SpotLight, IESpotLight, or RectAreaLight.

import { SpotLight } from 'three';
import { LightTarget } from 'three/addons/lights/LightTarget.js';

const light = new SpotLight();
light.position.set( 2, 1.5, 3 );
scene.add( light );

const target = new LightTarget().bind( light );
target.position.set( 0, 0, 0 );
scene.add( target );

@Mugen87
Copy link
Collaborator

Mugen87 commented Jun 18, 2024

Multiple times I've played around with the idea of changing the current way target works for directional and spot lights and I'm not really happy with the alternatives. I think what we have now is already good.

My latest branch where I have tested removing targets from light is here: Mugen87@2361111

There are two options:

a) Removing targets from lights completely.
b) Making them just optional but the default is "untargeted".

Both of them result in more code in three.js apps since you end up with additional lookAt() calls to get directional and spot lights right. This is noticeable when a light's position is animated like in webgl_lights_spotlights(see Mugen87@2361111#diff-66cb30d39214e3d336c0a3211271b06b5ba91b5b4ead298eef319e99af5d479b).

In the meanwhile, I have the feeling targeted lights actually make it easier to setup typical lit scenes (especially for beginners). I would not vote to completely remove targeted lights from three.js anymore. And I'm not sure it would be a good decision to make it optional and change the default from "targeted" to "untargeted".

In fact, directional and spot lights can already be "untargeted" by using the following setup:

light.target.position.set( 0, 0, - 1 );
light.add( light.target );

Now you can rotate the light by changing its rotation or quaternion property (directly or via methods like lookAt() or rotateTowards()) and get the expected result. This approach is already used by GLTFLoader.

For testing: https://jsfiddle.net/ouqwsp64/2/

Maybe we can put the above code in new convenient methods for DirectionalLight and SpotLight. Something like disableTargeting(). enableTargeting() would just undo this operation. Alternatively, a new property called targeted could be implemented as a getter/setter.

@hybridherbst
Copy link
Contributor

Does that "untargeting" approach work with three's scene serialization? Last time I checked the three.js editor always breaks when saving and opening a scene that has lights loaded from glTF because then they are targeted again and look in the wrong ways.

(for the record, I'm still voting for untargeting by default as it is handled in all other 3D apps that I'm aware of)

@Mugen87
Copy link
Collaborator

Mugen87 commented Jun 18, 2024

Does that "untargeting" approach work with three's scene serialization?

Not yet, but #28696 should add support for it.

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.

8 participants