Lights with shadows - dynamic implementation instead of inline
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Lights with shadows - dynamic implementation instead of inline
Hi all!
Why not to have alternative dynamic implementation of support for lights with shadows? I mean implement them in the same way as Forward3D lights in the hlms shaders, using dynamic branching instead of shader code inlining for each point and spot light.
In large or/and dynamic scenes with many lights of both types(spot and point) we can get very unstable nearest point and spot lights combinations.
For example, at one position in the scene we can have 10 point-lights and 6 spot-lights, and at another point we can get 4 point-lights and 10 spot-lights, etc.
Current inlining technique, in theory, may generate (N+1)(N+2)/2 shader variants, where N is total lights(point+spot) number. Actually, we'll get even more shader variants if count all material differences. Imagine N==32, then we can get up to 561 shaders!!!
Our app, Live Home 3D https://livehome3d.com , is affected. Indeed, our app is an editor where users may design any scene and place many light sources. So, we have to take in account only nearest lights and preserve their shadow maps if possible to minimize shadow maps updates. When the user walks through the scene in 3D view, we have to generate new shaders each time we find a new lights combination and this takes time.
What do you think about this, guys?
Why not to have alternative dynamic implementation of support for lights with shadows? I mean implement them in the same way as Forward3D lights in the hlms shaders, using dynamic branching instead of shader code inlining for each point and spot light.
In large or/and dynamic scenes with many lights of both types(spot and point) we can get very unstable nearest point and spot lights combinations.
For example, at one position in the scene we can have 10 point-lights and 6 spot-lights, and at another point we can get 4 point-lights and 10 spot-lights, etc.
Current inlining technique, in theory, may generate (N+1)(N+2)/2 shader variants, where N is total lights(point+spot) number. Actually, we'll get even more shader variants if count all material differences. Imagine N==32, then we can get up to 561 shaders!!!
Our app, Live Home 3D https://livehome3d.com , is affected. Indeed, our app is an editor where users may design any scene and place many light sources. So, we have to take in account only nearest lights and preserve their shadow maps if possible to minimize shadow maps updates. When the user walks through the scene in 3D view, we have to generate new shaders each time we find a new lights combination and this takes time.
What do you think about this, guys?
-
- OGRE Team Member
- Posts: 5436
- Joined: Sat Jul 21, 2007 4:55 pm
- Location: Buenos Aires, Argentina
- x 1343
Re: Lights with shadows - dynamic implementation instead of inline
Hi!
Eugene tells me you're already using HlmsPbs::setUseLightBuffers.
I've been remembering and also checking the code again.
Our current implementation has hlms_static_branch_lights and setMaxNonCasterDirectionalLights; but it is (currently) only working for directional non-caster lights and uses static lighting to pass an arbitrary number of lights:
Note: just to clarify; static branching are branches (and loops) where the values are sent via uniforms and is therefore equal for all pixels within the draw call; and this is enough to get the feature working. "Dynamic" branching means the branch/loop can vary per thread or pixel, which is overkill.
We'd need to do the same for shadow mapped lights, with a few caveats or things to watch out:
Alternatively you can take on compromises and make the solution much easier:
The main problem right now is that if light types switch (i.e. you had 10 spot + 10 points; and now you have 11 spot + 9 points) you will need a new shader. If you instead limit it to 10 spots + 10 points separately (i.e. "this shadow map only accepts spots", "this shadow map only accepts points"; which is something already possible in the Compositor as you must tell the compositor which shadowmaps accept which light types) you can do the following:
Eugene tells me you're already using HlmsPbs::setUseLightBuffers.
I've been remembering and also checking the code again.
Our current implementation has hlms_static_branch_lights and setMaxNonCasterDirectionalLights; but it is (currently) only working for directional non-caster lights and uses static lighting to pass an arbitrary number of lights:
Code: Select all
@property( !hlms_static_branch_lights )
@foreach( hlms_lights_directional_non_caster, n, hlms_lights_directional )
@insertpiece( ObjLightMaskCmp )
finalColour += BRDF( light0Buf.lights[@n].position.xyz, light0Buf.lights[@n].diffuse.xyz, light0Buf.lights[@n].specular, pixelData );@end
@else
for( int n=0; n<floatBitsToInt( light0Buf.numNonCasterDirectionalLights ); ++n )
{
int i = @value( hlms_lights_directional ) + n;
@insertpiece( ObjLightMaskCmpNonCasterLoop )
finalColour += BRDF( light0Buf.lights[i].position.xyz, light0Buf.lights[i].diffuse.xyz, light0Buf.lights[i].specular, pixelData );
}
/// Increase fineMaskLightIdx to keep it working with spot/point lights
@insertpiece( ObjLightMaskCmpNonCasterLoopEnd )
@end
We'd need to do the same for shadow mapped lights, with a few caveats or things to watch out:
- Each shadow can arbitrary live in any atlas. The solution to this is that
- Lights that share the same atlas are grouped together (i.e. one static loop per atlas type and inside the loop we check whether it's spot or point and execute its code), or....
- Textures are statically indexed i.e. shadowmapAtlas[ i ]. This may limit HW supported (I think this is FL 11? I'm can't remember if FL10 can do this). The main issues to encounter is that our GL3+ RS unnecessarily always wants to set the textures units from C++ (this is a bit of a PITA) and Metal SL 1.0 did not support arrays of textures (it does now, in more modern versions of Metal SL; back then compatibility would've been a huge problem)
- The UV borders from shadow atlas (i.e. hlms_shadowmap0_uv_param) would probably have to be sent through uniforms rather than hardcoded
Alternatively you can take on compromises and make the solution much easier:
The main problem right now is that if light types switch (i.e. you had 10 spot + 10 points; and now you have 11 spot + 9 points) you will need a new shader. If you instead limit it to 10 spots + 10 points separately (i.e. "this shadow map only accepts spots", "this shadow map only accepts points"; which is something already possible in the Compositor as you must tell the compositor which shadowmaps accept which light types) you can do the following:
- Modify Hlms to optionally allow always generating 10 spots + 10 points; and wrap each light in a static branch that toggles it on / off.
- As a workaround you can test this idea right now using CompositorShadowNode::setLightFixedToShadowMap to manually assign lights to their shadow maps, and you can ensure your max point/spot lights are never exceeded, and when you have fewer lights than necessary (e.g. you only have 4 spots) you assign a black light to that shadow map. This will burn CPU & GPU but it will show you a proof of concept
-
- OGRE Team Member
- Posts: 5436
- Joined: Sat Jul 21, 2007 4:55 pm
- Location: Buenos Aires, Argentina
- x 1343
Re: Lights with shadows - dynamic implementation instead of inline
I just realized there is one more alternative:
If you want to support 20 lights (any type), we could write code for 20 spot lights + 20 point lights (40 lights in total).
If we have the 10 spot + 10 points in the scene then:
Theoretically we can mitigate this scaling problem in two ways:
If you want to support 20 lights (any type), we could write code for 20 spot lights + 20 point lights (40 lights in total).
If we have the 10 spot + 10 points in the scene then:
- 10 spot branches will be true
- 10 spot branches will be false
- 10 points branches will be false
- 10 points branches will be true.
Theoretically we can mitigate this scaling problem in two ways:
- Generate multiple shaders for multiple increments. For example if only 10 lights are active (any type) we use a shader that supports 10 spot + 10 point lights (20 in total). If you add 1 more point light; we jump to a version that supports 10 spot + 20 point (30 in total). There will be shader hiccups, but it won't be the combinatorial explosion we have today.
- Async generation. Basically the ubershaders that Dolphin uses. Use the generic solution with 40 lights while more specialized Hlms shaders are compiled in the background (this solution is not mutually exclusive with the previous one)
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
Hi Matias!
Thank you for quick response!
You are right, we can implement that using static branching - all necessary variables (point lights number and spot lights number as well as UV borders for shadow atlas) can be passed via uniforms.
All we need is to get one or few(depending on material differences) shaders for set of N lights-shadow casters instead of (N+1)(N+2)/2.
In our app we keep lights presorted and grouped by type using prepareShadowsForWorkspace() callback and using setLightFixedToShadowMap() to preserve shadow maps for already known lights(with alive shadows from previous frames).
Next week I'll try to implement this using static branching as you suggested.
I'm not sure if it'll be possible for all RS, currently we use Metal and DX FL11. I believe we'll use Vulcan as well for Android port, but right now I don't have any testing Android device.
Anyway I'll probably create a new property to turn this technique and disable other properties to avoid shaders duplications. I'm not sure if I can use hlms_static_branch_lights property for this, may be it's better to create another one.
Thank you for explanations!
I hope static branching for shadow mapped lights will be useful for many Ogre users.
Thank you for quick response!
You are right, we can implement that using static branching - all necessary variables (point lights number and spot lights number as well as UV borders for shadow atlas) can be passed via uniforms.
All we need is to get one or few(depending on material differences) shaders for set of N lights-shadow casters instead of (N+1)(N+2)/2.
In our app we keep lights presorted and grouped by type using prepareShadowsForWorkspace() callback and using setLightFixedToShadowMap() to preserve shadow maps for already known lights(with alive shadows from previous frames).
Next week I'll try to implement this using static branching as you suggested.
I'm not sure if it'll be possible for all RS, currently we use Metal and DX FL11. I believe we'll use Vulcan as well for Android port, but right now I don't have any testing Android device.
Anyway I'll probably create a new property to turn this technique and disable other properties to avoid shaders duplications. I'm not sure if I can use hlms_static_branch_lights property for this, may be it's better to create another one.
Thank you for explanations!
I hope static branching for shadow mapped lights will be useful for many Ogre users.
-
- OGRE Team Member
- Posts: 5436
- Joined: Sat Jul 21, 2007 4:55 pm
- Location: Buenos Aires, Argentina
- x 1343
Re: Lights with shadows - dynamic implementation instead of inline
Yes, when you get some progress post the github link so I can take a look. This is a requested feature, particularly by applications similar to yours (user-edited scenes with lots of lights)
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
Sure, I’ll create a fork on GitHub.
-
- Goblin
- Posts: 248
- Joined: Thu Aug 12, 2021 10:06 pm
- Location: San Diego, CA, USA
- x 18
Re: Lights with shadows - dynamic implementation instead of inline
I don't know enough at this point to contribute to a solution. But I just wanted to say that I too have user-edited scene with (potentially) lots of lights, and I wish that it were easier to get started with shadows. I know that there are various shadow algorithms and parameters that can be tweaked, but it would be great if there were some default mode that one could just turn on and get half-decent shadows from any lights designated as shadow-casting.dark_sylinc wrote: ↑Wed Oct 27, 2021 6:14 pm This is a requested feature, particularly by applications similar to yours (user-edited scenes with lots of lights)
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
Hi all!
It's taken a while. I was busy with my project.
Nevertheless I've started implementation and almost done it.
I started with Mac platform and thanks to cross-platform nature of HLMS shaders it looks almost done for Metal, DirectX and Vulkan but I have to test it on Windows platform to be sure.
It'll take time to setup Ogre on my new Windows PC, but I'm sure the final implementation will be soon.
I work in my Ogre fork here:
https://github.com/dyunchik/ogre-next/t ... _branching
So, everyone can have look at the code. Any suggestions are welcome
It's taken a while. I was busy with my project.
Nevertheless I've started implementation and almost done it.
I started with Mac platform and thanks to cross-platform nature of HLMS shaders it looks almost done for Metal, DirectX and Vulkan but I have to test it on Windows platform to be sure.
It'll take time to setup Ogre on my new Windows PC, but I'm sure the final implementation will be soon.
I work in my Ogre fork here:
https://github.com/dyunchik/ogre-next/t ... _branching
So, everyone can have look at the code. Any suggestions are welcome
-
- OGRE Team Member
- Posts: 5436
- Joined: Sat Jul 21, 2007 4:55 pm
- Location: Buenos Aires, Argentina
- x 1343
Re: Lights with shadows - dynamic implementation instead of inline
Awesome!
I'm currently full on fixing warnings and C++11-izing the codebase on roadmap-2.4 branch (some of it has already been merged with master).
I want to try this, so probably after I'm done I'll merge your stuff into its own branch for me to test.
Btw if you test Vulkan you may find to some inexplicable error because you're now using arrays of textures for the shadow maps, and we need to set that in the Root Layout.
This is fixed by adding to HlmsPbs::setupRootLayout:
The details are covered in the manual. I'm telling you so you don't waste precious time figuring out wtf is going on.
Cheers
I'm currently full on fixing warnings and C++11-izing the codebase on roadmap-2.4 branch (some of it has already been merged with master).
I want to try this, so probably after I'm done I'll merge your stuff into its own branch for me to test.
Btw if you test Vulkan you may find to some inexplicable error because you're now using arrays of textures for the shadow maps, and we need to set that in the Root Layout.
This is fixed by adding to HlmsPbs::setupRootLayout:
Code: Select all
rootLayout.addArrayBinding( DescBindingTypes::Texture, RootLayout::ArrayDesc( shadowmapTexSlot, numElementsInArray ) );
Cheers
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
Hm, I started on 2.3.
"master" has moved further since my branch "sm_lights_branching" start.
I use a hack to access textures in loop by index:
That produces following shader code:
The main requirements: each shadow map should be universal and support both light types: point and spot.
So, the shadowmap setup should be like this:
I've modified ShadowMapFromCode sample for debug and testing purposes. Now it uses USE_STATIC_BRANCHING_FOR_SHADOWMAP_LIGHTS macro to turn on SM lights branching.
"master" has moved further since my branch "sm_lights_branching" start.
I use atlases instead of arrays of textures for the shadow maps.Btw if you test Vulkan you may find to some inexplicable error because you're now using arrays of textures for the shadow maps, and we need to set that in the Root Layout.
I use a hack to access textures in loop by index:
Code: Select all
@insertpiece( TEXTURE2DSHADOW ) shadowmap[@value(hlms_num_shadow_map_lights)] =
@property( syntax == glsl || syntax == glsles || syntax == glslvk )
@insertpiece( TEXTURE2DSHADOW )[@value(hlms_num_shadow_map_lights)](
@else
{
@end
@foreach( hlms_num_shadow_map_lights, n ) hlms_shadowmap@n ,
@end
@property( syntax == glsl || syntax == glsles || syntax == glslvk )
);
@else
};
@end
Code: Select all
depth2d<float> shadowmap[7] =
{
hlms_shadowmap0 ,
hlms_shadowmap1 ,
hlms_shadowmap2 ,
hlms_shadowmap3 ,
hlms_shadowmap4 ,
hlms_shadowmap5 ,
hlms_shadowmap6 ,
};
So, the shadowmap setup should be like this:
Code: Select all
shadowParam.addLightType( Ogre::Light::LT_POINT );
shadowParam.addLightType( Ogre::Light::LT_SPOTLIGHT );
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
Oops!
Looks like my hack with sm indexing doesn't work on glsl/glslvk
I've already managed it for hlsl, but stuck with glsl/glslvk - syntax doesn't allow local actual arrays with texture2D objects
I have to find a solution.
Looks like my hack with sm indexing doesn't work on glsl/glslvk
I've already managed it for hlsl, but stuck with glsl/glslvk - syntax doesn't allow local actual arrays with texture2D objects
I have to find a solution.
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
Ok, I figured out how to overcome the problem.
And now I understand better what Matias talked about:
And now I understand better what Matias talked about:
I’m going change shadow maps uniforms to an array.Btw if you test Vulkan you may find to some inexplicable error because you're now using arrays of textures for the shadow maps, and we need to set that in the Root Layout.
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
I've fixed sm lights branching for glslvk.
So, now this works for Metal, DirectX and Vulkan, old inline technique is used as fallback for OpenGL.
Now I plan to do some optimizations and make sure unnecessary hlms properties are removed, that finally decreases possible shaders variants from (N+1)(N+2)/2 to N.
So, now this works for Metal, DirectX and Vulkan, old inline technique is used as fallback for OpenGL.
Now I plan to do some optimizations and make sure unnecessary hlms properties are removed, that finally decreases possible shaders variants from (N+1)(N+2)/2 to N.
-
- Halfling
- Posts: 59
- Joined: Tue Jan 10, 2006 12:52 pm
- Location: Ukraine
- x 6
Re: Lights with shadows - dynamic implementation instead of inline
Finally I've done this sm lights branching technique!
Brief description:
SM lights branching technique decreases possible shader variants for point and spot shadow-casting lights. The technique is very suitable when you have many regular (point or spot) shadow-casting lights in the scene and their combination may change in different scene locations. Default inline technique requires new shaders for every possible shadow-casting lights combination.
SM lights branching technique creates two separate loops in PS shader - one for point shadow-casting lights and one for spot lights. Make sure your lights are ordered and grouped correctly(point lights first, then - spots) if you have your own lights management.
Current implementation works for Metal, DirectX and Vulkan render systems. Default inline technique is used for OpenGL/OpenGLES.
To enable SM lights branching technique use Ogre::HlmsPbs::setMaxShadowMapLights() method before any PBS materials creation.
I've modified Sample_ShadowMapFromCode for demo and debug purposes. Use USE_STATIC_BRANCHING_FOR_SHADOWMAP_LIGHTS macro to turn on/off the technique.
My fork with implementation can be found here:
https://github.com/dyunchik/ogre-next/t ... _branching
I've created pull request:
https://github.com/OGRECave/ogre-next/pull/255
Brief description:
SM lights branching technique decreases possible shader variants for point and spot shadow-casting lights. The technique is very suitable when you have many regular (point or spot) shadow-casting lights in the scene and their combination may change in different scene locations. Default inline technique requires new shaders for every possible shadow-casting lights combination.
SM lights branching technique creates two separate loops in PS shader - one for point shadow-casting lights and one for spot lights. Make sure your lights are ordered and grouped correctly(point lights first, then - spots) if you have your own lights management.
Current implementation works for Metal, DirectX and Vulkan render systems. Default inline technique is used for OpenGL/OpenGLES.
To enable SM lights branching technique use Ogre::HlmsPbs::setMaxShadowMapLights() method before any PBS materials creation.
I've modified Sample_ShadowMapFromCode for demo and debug purposes. Use USE_STATIC_BRANCHING_FOR_SHADOWMAP_LIGHTS macro to turn on/off the technique.
My fork with implementation can be found here:
https://github.com/dyunchik/ogre-next/t ... _branching
I've created pull request:
https://github.com/OGRECave/ogre-next/pull/255