Add directional light projector#106294
Conversation
1ddd98c to
daeb93d
Compare
|
Will this work with volumetric shadow? I just encountered the need for such a feature and I'm curious 😁 |
Currently projectors does not affect volumetric fogs, but since the work is close, I also started working on it (Still WIP and based on this branch) : #106395 |
daeb93d to
7a1d375
Compare
|
Can the atlas bilinear bleed be circumvented with adding one pixel padding around the uv |
That was my first approach to resolve this issue but I don't think I found a way to do it that does not deform too much the texture, maybe I did it wrong, but I'm still working on it, if you have any idea of how to implement it, or any other ideas, I would be happy to try it |
There was a problem hiding this comment.
Tested locally, it works as expected in Forward+ and Mobile. This is a great start 🙂
Some feedback:
-
When using Compatibility and a projector texture is set, a node configuration warning should be emitted to state that it's not supported. This is already done in OmniLight3D and SpotLight3D.
-
The scale property in the inspector should have
| PROPERTY_HINT_LINKso that it has a "link" icon you can click to change the X and Y axes proportionally to each other. -
The scale shouldn't have a unit defined in the inspector, just like the offset. Neither are actually defined in pixels. The offset is relative to texture size, with
1.0being a full offset that ends up looking identical to0.0.
- Regarding seams at texture edges, I could improve this somewhat for projector textures sized by at least 33×33 using this code in
light_storage.cpp:
Rect2 rect = texture_storage->decal_atlas_get_texture_rect(projector);
TextureStorage::Texture *texture = texture_storage->get_texture(projector);
float inv_width = 1.0f / texture->width;
float inv_height = 1.0f / texture->height;
// Inset a bit to avoid black lines on texture edges (at the cost of seams).
// NOTE: The offset is halved here, this may be wrong.
light_data.projector_rect[0] = rect.position.x + inv_width * 0.5;
light_data.projector_rect[1] = rect.position.y + rect.size.height - inv_height * 0.5; //flip because shadow is flipped
light_data.projector_rect[2] = rect.size.width - inv_width;
light_data.projector_rect[3] = -(rect.size.height - inv_height);However, while it fixes the black lines between repeating patterns, it still leaves visible seams. These seams remain visible even when using nearest filtering, so maybe there is a way to (mostly) get rid of them.
The seams are only visible on one axis, even if the light is perfectly perpendicular to the ground. This is how the decal atlas looks with a 512×512 NoiseTexture:
Note that for the decal atlas texture to be represented with a correct aspect ratio, you need to resize the 3D editor viewport to have a 1:1 aspect ratio (use View Information in the Perspective menu to make this easier).
I've also tried to base the offset based on the decal atlas' texture (rather than our projector texture), which arguably makes more sense:
TextureStorage::Texture *texture = texture_storage->get_texture(texture_storage->decal_atlas_get_texture());
float inv_width = 0.0;
float inv_height = 0.0;
if (texture) {
inv_width = 1.0f / texture->width;
inv_height = 1.0f / texture->height;
}But it seems texture is always null when used at this point.
Having a definitive fix for this issue would also make it possible to fix #89440, which is due to the same cause.
|
@Calinou there's also the edge dilation technique where pixels are copied outward from the seam so it interpolates with the same color instead of the black (grey in gif below), which would solve the ticket you linked. However atlas items are rectangular so it would be simpler to compute copying the edges outwards. And for the seams, maybe they could be solved by tiling instead of dilating? If the decal gets marked as tiled/repeated it could copy over n pixels from the right side over to before the left side and vice versa for both axis and corners diagonally. I think it would make bilinear interpolation amount to the same color on each of the opposite sides making the seam invisible including higher mipmaps.
|
7a1d375 to
66491b0
Compare
|
Thanks @Radivarig for pointing me to this direction, this is the solution I implemented and it seems to work well @Calinou for the moment I left the comments and the arbitrary values for the projection matrix because I don't have any reason to put these values, it just give a good size for the effect (even if you can then use the scale property), near and far does not have any impact and size will give the initial size of the projected texture on the scene. I felt like 50 or 100 was doing great but it will vary from a project to another. In the code I edited, I'm a bit worried by copy_to_fb. I haven't took the time to verify were it is used and if it can have some impact on other part of the engine, I put my modification in NOT_USE_MULTIVIEW branch as I felt it was the more coherent and also I write the offset in the color push_constance since it look unused in the branchs that use source texture. If there is any other draw call that may use this shader non correlated with the decal atlas, way may want to add a specific push constant value or manually set color[1 and 2] to 0.0 to prevent residual values in the memory ? I also checked and this fix #89440 I left some comments as question to the reviewers that we will want to remove as soon as this PR is no longer a draft Edit: Now that I think about it, maybe we could left the projection matrix size as a parameter in the directional light ? |
66491b0 to
a4b91af
Compare
|
I just updated the branch, the code should be clean now |
a4b91af to
d0d62ac
Compare
d0d62ac to
3828859
Compare
I think freeing up the memory is probably more important than having two separate positions that effectively do the same thing, that being said I'm sure the user-friendliness of just modulating that will appeal to a fair number of people.
In my case, and I think most use cases would be similar, I will be using it for cloud shadows which will be driven via compute. As such having it go to full light in the distance would likely be ideal, but I can control that by putting the edge pixel of the image to black or white as necessity requires, for this purpose the ideal solution is use the last pixel, that way if It's overcast I can fade off distance directional light on everything outside of the play area by just a value 0.0-1.0 by controlling that last pixel.
Absolutely! Thank you so much for the effort your putting into this, I'll test it when I get back today, right now I'm away on family events, but I am excited xD Edit: One thing I would need to do what I want to do is have a dirty function to trigger to let the directional light know that the texture has been updated in order to pass that update to the decal sheet. (Speaking of which I feel like this would be useful for decals as well, as having a clean way to notify it of changes would allow for animated decals so long as the framerate isn't too high) |
DrawableTexture should be able to handle this automatically thanks to #115653. As for other texture types such as ViewportTexture, this is being tracked in #73400. |
Made-with: Cursor
…6294) Made-with: Cursor
|
I've been eyeing this PR for a while, it's a feature I'd like to use, but it seems a bit stale lately. How open are you to implementing this by bypassing the decal atlas? DirectionalLight3D lights entire levels so repeatability is essential for the projector texture. Seems like atlas is making this a bit difficult. Skipping the atlas seems inefficient on paper but realistically how many directional lights does a scene have? Even the engine caps them at 8. One or two extra texture depending on the scene might be a fair trade-off. The other upside of a standalone texture is that it opens the door to dynamic projectors. I did a quick test on top of this PR in a separate branch , bypassing the atlas and allowing ViewportTexture as projector, it shifts two textures with different movement patterns and uses them as a projector; directional-projector-demo.mp4I'm not sure if a dedicated texture binding is the best long-term approach though. A dual path could work: static textures go through the decal atlas, dynamic ones (ViewportTexture, DrawableTexture) get a standalone slot in a texture2DArray binding that bypasses it. This avoids unnecessary bindings for static projectors while not forcing dynamic textures through an architecture that wasn't designed for them. Same pattern could extend to spot/omni or even decals (#73400, #74352, #74353) Either way, I think dynamic projector support is worth pursuing. There's clear demand for it (godotengine/godot-proposals#8237). I know there's also work being done to support dynamic textures within the atlas itself (#115653), so maybe that ends up being the preferred path. Just wanted to share what I've been experimenting with. project: directional-light-projector.zip |
|
I just tested the implementation from @mertkasar merged to 4.6.1 to create a cloud projection based on two noise textures in a SubViewport. It's exactly what I'm looking for, thank you! SubViewportCloudProjection.mp4 |
|
Thanks for your contribution to this PR @mertkasar. Using an a standalone texture was my first guess to bypass the repeat atlas problem and in the current state of this branch, it work well and no artifact can be found (at least in my testing). As said by @Calinou :
In 4.7 using DrawableTexture, it should work automatically with this PR to have animated textures in the decal (Even if in the long term, I think having a way to inject our own shadow code in the main shader should be the optimal way to go for this) |
- Added projector size / offset in directional light - Added directional light matrix, size and offset in forward shaders - Added computation logic of the projection in the forward and forward mobile shaders - Computing projector matrix in the update_light_buffers and pushing it to the gpu - Adapted light models to have projector size and offset stored (even in gles3 for the future inclusion of projectors) - Added a sub group for the directional light projector properties - Added directional projector pipeline specialization constant for mobile and forward rendering - Edited the way decals store texture to now repeat in the border instead of storing black color - Added projector warning for DirectionalLight3D in compatibility mode (Moved the projector warning to Light3D level since all can use projector now)
3828859 to
b12ceaf
Compare
|
I've just updated the PR to be up to date with the current master. |







Added projector texture to the directional light (godotengine/godot-proposals#8237)
Done in this commit:
Exemple :
There is still one main problem, since it use the decal_atlas, the texture don't repeat well when using a projector filtering other than nearest, see this image :
I'm looking for help to find a solution to this problem, maybe we should use separate textures for the directional light projectors ?
Here is my test project to test this PR : TestProject.zip