mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
101.91k stars 35.32k forks source link

Selective lighting #5180

Open goodsign opened 10 years ago

goodsign commented 10 years ago

I'm not sure whether this is already a planned feature or maybe even a finished one, so I'll try to explain the task first.

I need to do selective lighting for different rooms. For example, I have two rooms. One light must only affect objects and inner walls of one room. The second room must not be affected by this light.

Currently, if I add a light to the scene, it affects all objects in its distance. And I get strange effect when light comes "through the wall" of the second room.

So I think I need some kind of groups or channels for lighting, so that I could set the objects that are affected by one light source and objects that are affected by another.

I haven't found anything like that in either lights or objects, so I thought that maybe it could be a good feature to have.

And, btw, if it is not implemented yet, what is the recommended approach to solve such tasks using current state of three.js?

goodsign commented 10 years ago

What I am saying is basically having a channel field in THREE.Light and lightChannel field in THREE.Mesh or something like that. If the latter is null then it is affected by all light sources. If the latter is not null then it is affected only the the light channels with the same value.

Or maybe it could be added not to the Mesh itself, but to separate faces of the mesh geometry.

mrdoob commented 10 years ago

Sounds like you want to use shadows?

goodsign commented 10 years ago

Well, I could achieve something like that with shadows (and I've already tried), but it gives different side effects and I feel that it is a hack, because I don't need to light some objects and then cast shadow on them. I must not light them in the first place.

What I need is that a specific light source doesn't affect one set of meshes, but affects the other set.

Usually I do this using a technique like that: Example discussion thread:

glEnable(GL_LIGHT0);
//...
glEnable(GL_LIGHTn);

// Draw the walls to room 1
DrawWalls(room[0]);

// Draw the contents of room 1
DrawContents(room[0]);

glDisable(GL_LIGHT0);
//...
glDisable(GL_LIGHTn);

// Draw the walls to room 2
DrawWalls(room[1]);

// Draw the contents of room 2
DrawContents(room[1]);

So it looks like a different feature than just shadows. Or am I missing something?

mrdoob commented 10 years ago

Yes... I think this can indeed be handy. Not sure how the API for it should look like though.

goodsign commented 10 years ago

Maybe the most simple solution could be the most effective here:

  1. Add a channel (or group) to THREE.Light
  2. Add a affectedByLightChannel (or affectedByLightGroup) to THREE.Mesh (Or maybe even to a face in the geometry)

What do you think?

goodsign commented 10 years ago

Maybe the 'affectedByLightChannel' is too long and something like 'lightChannel' would do, but I think it would be convenient: just channel numbers on light source and receivers.

Like that:

    light = new THREE.PointLight(0xFFF7D6, 1.0, 15)
    light.channel = 123
    testScene.add(light)

    testScene = new THREE.Scene
    geometry = new THREE.BoxGeometry(2,2,2)
    material = new THREE.MeshLambertMaterial 
        color: 0xffffff

    cube = new THREE.Mesh(geometry, material)
    cube.lightChannel = 123
    testScene.add(cube)
goodsign commented 10 years ago

If lightChannel is equal to 0, then it is affected by all channels. If channel is equal to 0, then it affects all meshes.

So it would be fully backward compatible with current behavior.

mrdoob commented 10 years ago

That may be a bit too hard to understand... Maybe it's better something like this:

cube.lightInfluences = [ light1, light2 ];
goodsign commented 10 years ago

Seems absolutely fine to me.

Maybe it is a bit more difficult to use in some cases, but it is easier to understand, obviously.

satori99 commented 10 years ago

What about a simple integer mask property for meshes and lights?

light = new THREE.PointLight(0xFFF7D6, 1.0, 15)
light.mask = 0xffffffff; // default mask
testScene.add(light);

cube = new THREE.Mesh(geometry, material)
cube.mask = 0xffffffff; // default
testScene.add(cube);

Objects are then only lit by lights if the logical AND of their masks is non- zero. This would allow lights to be influenced by more than one channel, with no extra methods on the objects.

A mask default of 0xffffffff would not affect existing code.

WestLangley commented 10 years ago

What @satori99 said.

Although, I think mask should be a property of Light and Mesh*Material, instead. (Only those materials affected by lights.)

Also, the property could alternatively be named lightMask, lightChannel, or channel.

mrdoob commented 10 years ago

The problem of the channel/mask approach is that the user will need to understand bitwise operations. A bit too intense if you compare it to the rest of the API.

What's something you can do with masks that you can't do with the array approach?

goodsign commented 10 years ago

I could give an example from the task with two rooms above.

The main point of using the channel approach over the array approach is that simple operations like 'move light1 from room 1 to room 2' become more complicated if you use arrays.

Instead of just setting

light1.channel = 2

(previously it was set to 1)

you would have to find all objects in room 1 that had light1 in lightInfluences array previously, then remove the light from their arrays, then add it to all objects in the room 2.

Same story with the simple operation like 'move object 1 from room 1 to room 2'. Instead of setting its influenceChannel from 1 to 2, you would need to find all the lights in that room, then remove them from its influence array, then find all lights in room two and add them.

It's not that it can't be done, that's why I said that the lightInfluences approach is absolutely fine to me. But the channel stuff would be the first thing that I would implement above it for myself just to make common operations as simple as 1 assignment.

WestLangley commented 10 years ago

I think it should be implemented as a mask. (Whether is it implemented on the CPU or GPU is an issue for later discussion.)

We can show via examples how to set it, and users can follow the pattern.

If you think that is still too complicated, then we can create a THREE.Channels API for it.

light.channels = new THREE.Channels();
...
light.channels.clear();
light.channels.add( channel );
light.channels.remove( channel );
light.channels.all();

Same methods for Mesh*Material.

mrdoob commented 10 years ago

I like that API :) I can see this working for objects and lights, but how do you see it working for materials?

WestLangley commented 10 years ago

Only materials respond to lights. This would have to be a property of the material, I would think.

gero3 commented 10 years ago

I agree on this with @westlangley. Lights are dependent on materials.

WestLangley commented 10 years ago

Same story with the simple operation like 'move object 1 from room 1 to room 2'.

Well, that's a problem. Channels would not be object-based.

goodsign commented 10 years ago

Well, that's a problem. Channels would not be object-based.

But why? Is it a technical limitation?

Because it kind of invalidates the whole idea of all that. Because there can be different objects, which reuse the same material, but one of them should be lit and the other -- shouldn't.

WestLangley commented 10 years ago

But why? Is it a technical limitation?

No. It is because objects do not respond to lights. Only materials do.

Because there can be different objects, which reuse the same material, but one of them should be lit and the other -- shouldn't.

You can use the same material for all objects in the scene -- just clone the material for objects whose material requires different uniform values. There should still be only one shader program shared by all.

mrdoob commented 10 years ago

I think it should be implemented as a mask. (Whether is it implemented on the CPU or GPU is an issue for later discussion.)

Can this be handled somewhat easily in the GPU directly?

WestLangley commented 10 years ago

Can this be handled somewhat easily in the GPU directly?

Yes, you would need to pass in the additional channels uniforms for the lights and materials.

pailhead commented 10 years ago

How about a layer management system? I'd group the meshes into layers and apply the masks from there (could affect lights, shadows, visibility, etc), unity would be a good example?

goodsign commented 10 years ago

Shadows is also a related topic. I think there should also be something like selective shadow casting. Like 'receiveShadowFrom = ...' (and list of sources) instead of just 'receiveShadow = true'.

Because when you set lights that only affect a specific room (in my example), you would also immediately want those lights to cast shadows only on this room objects.

satori99 commented 10 years ago

Shadows attributes should really be on the materials and not the objects for the same reasons as above in this thread.

goodsign commented 10 years ago

Shadows attributes should really be on the materials and not the objects for the same reasons as above in this thread.

Yes, it makes sense!

goodsign commented 10 years ago

Are there any plans for including this feature (like a planned target release for the first draft)?

WestLangley commented 10 years ago

As a work-around, can you achieve most of your requirements by having a separate scene for each room (and its lights)?

renderer.autoClear = false;
...
renderer.render( scene1, camera );
renderer.render( scene2, camera );
goodsign commented 10 years ago

Hmm, I'll try this approach!

ghost commented 9 years ago

Selective lighting / shadows is a must for future versions IMO.

rohan-deshpande commented 9 years ago

BIG +9999 for this one, I would love to be able to select if a material casts a shadow from light source A or from light source B. Anyone got a solution currently other than two scenes? That's gonna make things pretty painful for me...

makc commented 8 years ago

holy crap how is this still not implemented? after +9999 from rohan )

rohan-deshpande commented 8 years ago

Haha my guess is that it's kind of hard to implement @tsone can you provide some info on that commit? Browsing on a phone client right now

makc commented 8 years ago

how is it hard to implement if these are just uniforms they pass to materials. all they have to do is substitude global list of lights with the one defined in material, if it exists, somewhere deep in webgl renderer.

Zob1 commented 8 years ago

Here is a simple JSFiddle that tests the Dev branch's Layers implementation: https://jsfiddle.net/Angrypickle/t8f7q3vo/4/ Unfortunately, as of this moment, it doesn't look as though it's working properly. Or am I doing something wrong?

mrdoob commented 8 years ago

Yes. Layers don't work with lights yet. They do work with camera/object though! 😊

Zob1 commented 8 years ago

Roger that sir! Here is an updated JSFiddle that uses overlapping cameras to achieve selective lighting with layers: https://jsfiddle.net/Angrypickle/t4a1eusL/ It seems to work properly on desktop and mobile. Anyone see anything profoundly bad with this approach? At least until lights are tied into the layers functionality?

makc commented 8 years ago

Anyone see anything profoundly bad with this approach?

of course it is bad. rather than having single camera navigating the scene, people will now have to do things camera1.add( camera2 ); I mean this is WTF when I look at it. Like... what? camera in my camera? what if I have dozens of rooms that need to be individually lit? how many cameras do I need? and layers... there were no layers yesterday, right, and now I have to l̲e̲a̲r̲n̲ about them.

rant over, not to say I have the perfect solution for this. e g alternativa3d people used to put lights on stuff that was in light's bounding boxes. this had an advantage of next-to-0 setup for end users, but it was falling apart when the border between lights had to be at the angle. but still, if I had to solve this problem in actual project right now, I would most probably resolve to scraping used standard materials into ShaderMaterial-s and passing the lights I want to by hand there.

mrdoob commented 8 years ago

@makc layers are actually super simple (and powerful)! @Zob1's approach is definitely not the right approach. Hopefully it won't take long until layers also work with lights.

rohan-deshpande commented 8 years ago

Guys just to make sure I am following correctly; let's say I have a single scene and a "main" light source. Is it possible with this technique to have certain materials ignore this main light source, while also casting shadows from a different light source?

mrdoob commented 8 years ago

Well, for that we'll also have to add layers support to materials.

What we can do at the moment is show/hide objects from the camera:

Say that you are doing a game and, in the editor, you use spheres to display colliders. Those spheres could be set to layer 1, the camera's editor could have layers 0 and 1 enabled, but the game camera could have be set to layer 0. That way, in your editor you see all the dummies, but in the game they're gone.

I think this will make more sense whenever I add the functionality to the editor itself. We should probably do some examples too.

rohan-deshpande commented 8 years ago

Right! So basically what I am wanting to do is, I have 2D characters in my Three JS world. These characters need "spot" shadows to make them look like they are part of the environment. I am currently achieving this using transparent black circle geometries placed at their feet and some fairly hacky stuff to get it working at angles. Even then it doesn't work well at all on complex surfaces.

My original idea was to have invisible circle geometries places as "halos" above each character and create a light that is pointing directly down, covering the whole scene. These halos would only cast shadows from this light source while everything else in the scene would cast shadows from the "main" light source.

I guess for this idea to work, we need layers right?

WestLangley commented 8 years ago

These halos would only cast shadows from this light source while everything else in the scene would cast shadows from the "main" light source.

We used to have a shadowOnly option, but it was removed.

FWIW, there is this approach to creating shadows...

rohan-deshpande commented 8 years ago

Ah but shadowOnly wouldn't really work because then the halos would cast shadows from both light sources, I only want them to cast shadows from one.

I will check out that example, on the phone right now but looks promising.

Edit Hmm but if that sphere changed its position in the scene and the ground mesh had varying heights, would its shadow match the surface normals of the ground correctly?

mrdoob commented 8 years ago

Nope...

rohan-deshpande commented 8 years ago

Yeah thought so. Need layers! Hehe

erichlof commented 8 years ago

Hi @rohan-deshpande ,

Yes for your use case, you're going to need layers of some sort. I was the one who implemented the shadowmesh feature a while back. They were meant to be used for a scene containing a flat floor or ground, because they are single-planar shadows. However, if you ever need fast, cheap (but correct) single plane shadows, these are hard to beat performance-wise. The reason I added these was because even for simple demo scenes, shadowmaps dropped my framerate considerably. Shadowmeshes on the other hand run fast even on my phone.

You can check out my game that uses them here:
https://github.com/erichlof/3dLightCycles

I'm rendering the scene twice through 2 different viewports and all game objects (cycles and trail walls) have correct shadows. It runs silky-smooth on my smartphone, which would be impossible with shadowmaps for 2 scene renderings every frame.

+1 here for layers in the future :)

rohan-deshpande commented 8 years ago

@erichlof game looks and runs great on iPhone6 man.

Okay I guess I'll wait for layers. Until then my hacky solution will have to suffice.

tarun29061990 commented 8 years ago

Hey guys Is this thing implemented? http://stackoverflow.com/questions/33689781/casting-shadows-based-on-a-specific-light?noredirect=1#comment55153706_33689781

rohan-deshpande commented 8 years ago

No it hasn't been implemented yet. You'll have to wait for layers or try one of the solutions listed in ITT (separate scenes for example).