Open atirut-w opened 3 years ago
Not working on anything, but could be useful for rendering foliages where transparency have to be used instead of alpha-scissor/cutout rendering.
Foliage shadows are generally well-covered by alpha-tested (alpha scissor) shadows, since foliage doesn't have partially translucent areas by nature. Dithered shadows would be more useful in situations where you have partial transparency, such as smoothly fading an object in and out along with its shadow.
Nonetheless, I think this feature is worth looking into for Godot 4.x. I'm not sure if it'll be possible to sample the albedo texture's alpha channel though – we may be limited to per-material opacity only (or even per-mesh opacity).
Foliage shadows are generally well-covered by alpha-tested (alpha scissor) shadows, since foliage doesn't have partially translucent areas by nature.
And what about clothes and fabrics?
And what about clothes and fabrics?
Cloth and fabric also rarely has large semi-transparent areas. I think dithered shadows will be more useful for materials such as (stained) glass, in addition to fading objects (e.g. due to LOD or gameplay reasons).
(stained) glass
Time for colored shadows? :eyes:
Time for colored shadows? :eyes:
Lights in 3.x
can cast colored or transparent shadows with the Shadow Color property, but this is done on a per-light basis rather than a per-mesh or per-material basis. Also, Light's Shadow Color property has been removed in the master
branch for performance reasons. (It may be possible to reintroduce this feature with no performance cost if you don't use it thanks to Vulkan specialization constants, but it probably won't happen in time for 4.0.)
By colored shadows, I mean colored, semi-transparent objects casting colored shadows like this:
By colored shadows, I mean colored, semi-transparent objects casting colored shadows like this:
For use cases such as this one, you can also use projector textures which are supported for both SpotLights and OmniLights.
projector textures
Sounds like a PITA to set up especially because you have to bake the correct colors into the texture, but how do you even do that?
It's actually pretty simple to set up per object transparent shadows in Godot.
All you need to do is duplicate the object with the shadow, disable shadow casting on the original, and use a dithering shader on the shadow caster (You must also set the dupe's shadow mode to Shadow Only
). Something like this.
shader_type spatial;
uniform float alpha : hint_range(0.0, 1.0);
uniform sampler2D bayer;
void fragment() {
if (texture(bayer, FRAGCOORD.xy / 4.0).r > alpha) {
discard;
}
}
It's also worth noting that the effect is absolutely awful without shadow filtering.
Edit: It's also also worth noting that with the spot light, the shadow seems to be more transparent the closer the shadow is to the light source; perhaps it's due to the dithering pattern being more compressed there, but that's just a guess.
Edit: While playing with the shader, I also discovered a disadvantage of this technique in which multiple transparent object shadows don't darken each other as they would IRL.
I tried solving by giving each object a unique sampling offset of dithering pattern, but it only works in specific cases.
use a dithering shader on the shadow caster
Exactly the implementation that I had on my mind.
Also, about coloured shadows, I think one solution would be to have three lights, one for each colour component, and set three object dupes to selectively render in each shadow. I cannot test this theory right now though, 'cause I'm currently limited to GLES2. Even then, the combined lights would have some colour fringing due to one or two of the shadow maps having different resolutions, plus you'd be rendering three shadow maps, which will be expensive in any 3D project.
Frankly, if a pull request were made that implemented what was shown in this article, it would be much more elegant and efficient. https://wickedengine.net/2018/01/18/easy-transparent-shadow-maps/
Edit: I thought I'd mess with the shader some more, and I think caustics would be another great application of this proposal.
caustics
HOW.
I NEED SAUCE.
It's quite simple really. 🙂
shader_type spatial;
uniform float caustic = 5.0; // The higher this is, the more the light is focused in the effect.
uniform sampler2D bayer;
void fragment() {
if (texture(bayer, FRAGCOORD.xy / 4.0).r > (1.0 - pow(dot(NORMAL, VIEW), caustic))) {
discard;
}
}
The trick is to make the assumption that the caustic strength is directly proportional to the angle in which the light shines through the object. I use the same technique to fake caustics in blender eevee.
@Calinou any chance of this proposal making it into 4.x?
@Calinou any chance of this proposal making it into 4.x?
This proposal needs to be discussed in a proposal review meeting first. It won't be considered for 4.0 due to feature freeze, but we need to evaluate whether this makes sense to support in core for a future 4.x release.
However, we are currently not reviewing proposals that are not critical for 4.0 in an effort to focus on releasing 4.0 first. This proposal also already has a workaround available, making it less critical to implement in core.
It would still be nice to have this as a built-in "Dithered" transparency mode, though. A QoL thing.
Dithering should use blue noise instead of plain white noise for smoother visual appearance. Please see this good explanation on why.
Dithering should use blue noise instead of plain white noise for smoother visual appearance.
We already use interleaved gradient noise for distance fade dithering and shadow map rendering. It's a cheap approximation of a noise that focuses on high-frequency patterns (instead of low frequency) :slightly_smiling_face:
I've tried both noise patterns.
Downloaded the Free blue noise textures from this article: http://momentsingraphics.de/BlueNoise.html
Loaded the 512_512/LDR_RGBA_0.png
one as the dither_noise
texture (512x512 pixels).
uniform sampler2D dither_noise;
// See https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/
float ign(vec2 px)
{
float t = floor(mod(TIME * 60.0, 1024.0));
float x = px.x + 5.588238f * t;
float y = px.y + 5.588238f * t;
return mod(52.9829189f * mod(0.06711056f * x + 0.00583715f * y, 1.0f), 1.0f);
}
float noise(vec2 px)
{
return texelFetch(dither_noise, (ivec2(px) + ivec2(int(TIME * 397.0) % 8191)) & ivec2(511), 0).r;
}
void fragment() {
...
// Here transparency is the color and transparency (alpha) of the glass material in the mesh
ALBEDO = transparency.rgb;
ALPHA = transparency.a >= noise(SCREEN_UV * VIEWPORT_SIZE) ? 1.0 : 0.0;
ALPHA_SCISSOR_THRESHOLD = 0.5;
}
The noise
function gives slightly smoother result than the ign
one. Of course it is at the cost of an extra memory read per transparent pixel. Also, I had to tweak the IGN algo a bit to adopt to Godot having only TIME and no FRAME_NUMBER in shaders. TAA works like magic and makes it smooth quite quickly if staying still. But with FXAA ign()
gives a smoother result and does not care about movements (no time domain), however at the cost of quite jittery edges.
However, it looks completely wrong when animated (slowly rotated) in front of a camera...
Perfected a bit how we use TIME and got it working both with FXAA and TAA with real good results:
const int D = (1 << 13) - 1; // 13 is prime to have a cyclic group
float ign(vec2 p)
{
float v = mod(52.9829189f * mod(0.06711056f * p.x + 0.00583715f * p.y, 1.0f), 1.0f);
return mod(v + float(int(TIME * 11003.0) % D) / float(D), 1.0); // 11003 is a prime which works well
}
How to use it:
void fragment()
{
...
if (!opaque) {
// transmittance is the light can be transmitted through the transparent material
ALBEDO = 1.0 - transmittance.rgb;
ALPHA = transmittance.a >= ign(SCREEN_UV * VIEWPORT_SIZE) ? 1.0 : 0.0;
ALPHA_SCISSOR_THRESHOLD = 0.5;
}
}
Tested with alpha from 0.1 to 0.9 in 0.1 steps. Under 0.2 it looks really spotty, 0.3 works pretty well as a glass window and above it all good. Best combined with FXAA or TAA, certainly, but even work without. The TAA result is basically the same as real transparency after watching it still for less than a second. FXAA works reasonably well, but jitters a little bit at certain transparency values. I guess the best values can be selected and using only those for in-game materials would minimize the effect.
No smoothing, alpha 0.3: (the stripes are not visible, they are moving fast and cannot be tracked with the eye, so still look smooth)
FXAA, alpha 0.3:
any updates on this one?
any updates on this one?
To my knowledge, nobody is currently working on this.
It's quite simple really. 🙂
shader_type spatial; uniform float caustic = 5.0; // The higher this is, the more the light is focused in the effect. uniform sampler2D bayer; void fragment() { if (texture(bayer, FRAGCOORD.xy / 4.0).r > (1.0 - pow(dot(NORMAL, VIEW), caustic))) { discard; } }
The trick is to make the assumption that the caustic strength is directly proportional to the angle in which the light shines through the object. I use the same technique to fake caustics in blender eevee.
As someone who just started using Godot literally today, what do I need to put in the Bayer property? I'm just trying to get this shader to work on a translucent sphere and not seeing it make a real impact. There's a tiny hard edge around the sphere's perimeter but it does nothing to the shadow
Describe the project you are working on
Not working on anything, but could be useful for rendering foliages where transparency have to be used instead of alpha-scissor/cutout rendering.
Describe the problem or limitation you are having in your project
As of currently(3.x, did not test the master branch), you cannot have semi-transparent shadows and thus are limited to Opaque Pre-Pass if you want shadows for your semi-transparent objects. This obviously won't look correct.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
An option to use dithered shadows that try to emulate semi-transparent shadows.
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
Use the alpha channel to dither between opaque and completely transparent, that's it. Basically how Unity does it.
Example: https://github.com/mrdoob/three.js/issues/10600#issuecomment-471152023
If this enhancement will not be used often, can it be worked around with a few lines of script?
Currently not possible to implement as a script or an addon.
Is there a reason why this should be core and not an add-on in the asset library?
Currently not possible to implement as a script or an addon.