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

Add support for shadow map atlasing #2102

Merged
merged 2 commits into from Feb 14, 2021
Merged

Conversation

N8n5h
Copy link
Contributor

@N8n5h N8n5h commented Feb 4, 2021

With this it will now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene: #1289

This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved.

The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap().

More details about the implementation (WIP): https://github.com/N8n5h/armory/wiki/Shadow-map-atlas-for-maintainers

How to use: https://github.com/N8n5h/armory/wiki/Shadow-map-atlas-for-users

image

Blender files I've used for testing:
files-for-testing.tar.gz

Notes:

As explained in the detailed article for maintainers, this is completely governed by the #arm_shadow_map_atlas compiler flag to isolate issues related to it within it until it's mature enough, so it should be relatively safe to push even if it wasn't thoroughly tested as I initially planned... but not up to me to decide.

  • Tested mostly in Krom Linux because of time constraints.

Please test it if you can.

  • Tried to test it on Krom C but I get this when I try to launch even the default cube [Android (C)] custom material_shaders (not working) #2067

  • I tried to test it on windows but unfortunately my windows installation is acting up so couldn't confirm if it works there,
    I have the suspicion that point lights might bring problems because of the inverted faces, but since the mapping is done manually all platforms should have the same "cubemap standard" so maybe it's safe. Also I suspect this Only one pointlight at a time possible #1772 might bring problems too but maybe the issue it's circumvented by the fact that no array of shadow map samplers is used for atlases.

  • HTML5 seems to not work with multiple point lights for whatever reason. One light works but more than one breaks shadows. Don't seem to be connected to the shadow map size from my tests but could be wrong. I've tried to debug it but unfortunately couldn't make Spector nor webgl-inspector to work, so not sure what is going on in the shader side.

  • Tried to implement dual paraboloid but unfortunately I got stuck and couldn't make it work, here is my progress in case anyone want to check it out armory / iron. There is no doc for this but it should be pretty clear since it's an (attempt of) implementation of the things suggested by the articles and gamedev post linked in the commit message.

This depends on this from iron armory3d/iron#111

With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by
grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was
done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that
access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply
using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this
limitation was solved.

The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map
can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic
to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available
space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a
modified version of drawShadowMap().

Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice
that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the
tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial
4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth
levels is added or not when compiling.

the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner
subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a
linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make
some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that
utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
@MoritzBrueckner
Copy link
Collaborator

MoritzBrueckner commented Feb 5, 2021

This is absolutely epic! Must have been a ton of work...

I tried to test it on windows but unfortunately my windows installation is acting up so couldn't confirm if it works there,
I have the suspicion that point lights might bring problems because of the inverted faces, but since the mapping is done manually all platforms should have the same "cubemap standard" so maybe it's safe. Also I suspect this #1772 might bring problems too but maybe the issue it's circumvented by the fact that no array of shadow map samplers is used for atlases.

Tested it and it "solves" #1772 when the new atlasing option is enabled. But, unfortunately point lights indeed seem to be problematic:

This is how the scene looks in Blender:

This is how it looks in Krom on Windows (there is no visible difference when using shadow LOD or a single atlas map):

And with spot lights:

Very similar artifacts are visible in the browser (Firefox) when running with html5.

Test file and RenderDoc captures (exported to two formats because I don't know if sharing a capture works): lights.zip. I hope it helps a bit :)

@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 5, 2021

This is absolutely epic! Must have been a ton of work...

I tried to test it on windows but unfortunately my windows installation is acting up so couldn't confirm if it works there,
I have the suspicion that point lights might bring problems because of the inverted faces, but since the mapping is done manually all platforms should have the same "cubemap standard" so maybe it's safe. Also I suspect this #1772 might bring problems too but maybe the issue it's circumvented by the fact that no array of shadow map samplers is used for atlases.

Tested it and it "solves" #1772 when the new atlasing option is enabled. But, unfortunately point lights indeed seem to be problematic:

This is how the scene looks in Blender:

This is how it looks in Krom on Windows (there is no visible difference when using shadow LOD or a single atlas map):

And with spot lights:

Very similar artifacts are visible in the browser (Firefox) when running with html5.

Test file and RenderDoc captures (exported to two formats because I don't know if sharing a capture works): lights.zip. I hope it helps a bit :)

Ok thanks, yeah I was afraid that directx would be problematic. I have to reinstall windows and play with it to sort it out.

@knowledgenude
Copy link
Collaborator

I don't know about rendering, but the "artifact" in the second gif reminds me a similar bug that happens to sun lamps. Looks like some wrong calculation of the camera clip start, but i am not stating nothing. It occurs on OpenGL (not tested in DirectX), here is an example file: test.zip

Sun lamp (sorry for the brightness):
Captura de tela de 2021-02-05 19-12-54

The same bug does not occur with other types of lamps, see point lamp:
Captura de tela de 2021-02-05 19-13-23

Maybe it is not a bug of the atlas map but of Armory?

@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 5, 2021

Maybe it is not a bug of the atlas map but of Armory?

If the issue can be reproduced with the Shadow Map Atlasing option disabled, then it's an issue with armory. Toggling that option off should remove all code related to the feature :)

@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 7, 2021

Ok, it took a full evening but I managed to reinstall windows and be able to test on it 😄. The first thing I've tried is just one point light in the scene and surprise, surprise, it works... so it's the same thing that happens in HTML5. Now I've to guess and find whatever is wrong with the implementation that breaks multiple lights for not-opengl targets 😅

@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 9, 2021

After some poking around, I figure that the issue might have to do with this...

image
(from http://thedev-log.blogspot.com/2012/07/texture-coordinates-tutorial-opengl-and.html)

So I suppose it works with one light because of the coincidence that the face was just in the perfect spot when mirrored

@skial skial mentioned this pull request Feb 10, 2021
1 task
See http://thedev-log.blogspot.com/2012/07/texture-coordinates-tutorial-opengl-and.html,
the "opengl coordinates" where inverted for proper support of direct3d texture coordinate system.
@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 11, 2021

Pushed a commit that seems to solve the issue with spot and point lights on Windows from my brief testing. Please test if you can to confirm if it's finally working :)

@MoritzBrueckner
Copy link
Collaborator

MoritzBrueckner commented Feb 11, 2021

Works like a charm now, thank you so much!

Regarding html5: if I'm not mistaken, webgl uses a screen coordinate system in the range [-1, 1] with (0, 0) being the center. Maybe that's what causing the issues there? That would explain why the artifacts look similar but different.

Edit: the issue when the camera is too close to an object still persists unfortunately (can be seen in the gif above when the camera is close above the plane), is that due to a wrong setting somewhere or is it a bug?

@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 11, 2021

Regarding html5: if I'm not mistaken, webgl uses a screen coordinate system in the range [-1, 1] with (0, 0) being the center. Maybe that's what causing the issues there? That would explain why the artifacts look similar but different.

Will check it out!

Edit: the issue when the camera is too close to an object still persists unfortunately (can be seen in the gif above when the camera is close above the plane), is that due to a wrong setting somewhere or is it a bug?

Yeah, I noticed it when testing it too, but it seems it's not related to atlas from what I've seen. Can you confirm this too?

@MoritzBrueckner
Copy link
Collaborator

Will check it out!

I think it's just the screen/canvas coords, texture coords should be in the usual [0, 1] range.

Yeah, I noticed it when testing it too, but it seems it's not related to atlas from what I've seen. Can you confirm this too?

Oh, indeed. Maybe there is something wrong with the clustering algorithm in general?

@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 11, 2021

I think it's just the screen/canvas coords, texture coords should be in the usual [0, 1] range.

Yes, I tested it by simply commenting this line https://github.com/N8n5h/iron/blob/91728e210b0703822ce42c38b3a79840a9452b72/Sources/iron/object/LightObject.hx#L550 which I think should have been enough to make spot lights work, but it didn't :(

Oh, indeed. Maybe there is something wrong with the clustering algorithm in general?

Could be, but also could be the algorithm that computes lighting for spot lights too.

I will try playing around with the shaders to see if clusters have anything to do with it, but getting a file where the issue can be reliably reproduced so it's easy to quickly test for changes probably is a better priority. Also I've noticed when using windows that renderdoc allows debugging direct3d, so that probably could be worth looking too, to compare what was going on on a broken pixel versus a working one...

@luboslenco luboslenco merged commit 49a599d into armory3d:master Feb 14, 2021
@MoritzBrueckner
Copy link
Collaborator

Hi, I found a small issue with the new atlasing option: when there is a transparent material (like ArmoryPBR with opacity < 1), the following glsl compiler error is raised:

ERROR: [...]\build\compiled\Shaders\std/clusters.glsl:6: 'clusterNear' : undeclared identifier
ERROR: [...]\build\compiled\Shaders\Material_translucent.frag.glsl:3: '#' : preprocessor directive cannot be preceded by another token
ERROR: [...]\build\compiled\Shaders\Material_translucent.frag.glsl:3: '' : compilation terminated
ERROR: 3 compilation errors.  No code generated.

When Shadow Map Atlasing is disabled, everything works fine. Should I open an issue (+ example file) for this?

@N8n5h
Copy link
Contributor Author

N8n5h commented Feb 14, 2021

Hi, I found a small issue with the new atlasing option: when there is a transparent material (like ArmoryPBR with opacity < 1), the following glsl compiler error is raised:

ERROR: [...]\build\compiled\Shaders\std/clusters.glsl:6: 'clusterNear' : undeclared identifier
ERROR: [...]\build\compiled\Shaders\Material_translucent.frag.glsl:3: '#' : preprocessor directive cannot be preceded by another token
ERROR: [...]\build\compiled\Shaders\Material_translucent.frag.glsl:3: '' : compilation terminated
ERROR: 3 compilation errors.  No code generated.

When Shadow Map Atlasing is disabled, everything works fine. Should I open an issue (+ example file) for this?

This should solve it #2109. Didn't spot that compiled.inc gets no special treatment when handling includes(which I think it's smelly since it has macros for other shaders to work (?)) so with add_include_front() (solves other issue with parsing cluster shaders for forward) I unknowngly let cluster.glsl to be on top of compiler.inc, and that's what's causing the shader compiling error.

@MoritzBrueckner
Copy link
Collaborator

Thanks!

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.

None yet

4 participants