Describe the project you are working on:
Top-down 2D rpg with heavy use of visual effects, post-processing and custom depth tests.
Describe the problem or limitation you are having in your project:
I'll often have to create shaders that read from a certain buffer or texture and then write to it or viceversa.
This is not possible in a single pass shader as it is technically not supported by graphics cards.
So a multi-pass shader is needed.
Multi-pass materials rendering is also currently not supported for canvas_item shaders which is another related limitation.
This is however a proposal for multi-pass shaders, here's an example of how they are defined in Unity:
Shader "ShaderName"
{
Properties
{
// define shader properties
_TextureName("Inspector Description", Type) = "white" {}
_FloatSlider("Float Slider Description", Range(0, 1)) = 0.5
}
SubShader
{
// Tags, Identifiers, etc...
// first pass
Pass
{
// Tags, Identifiers, etc...
// define blend mode, ztest mode, zwrite mode etc.
// define used properties/uniforms
// define input and/or output structs (appdata, s2f, FragmentOutput...)
v2f vert (appdata v)
{
// vertex function
}
FragmentOutput frag (v2f i)
{
// fragment function
}
}
// this second pass is performed after the first one
Pass
{
// Tags, Identifiers, etc...
// define blend mode, ztest mode, zwrite mode etc.
// define used properties/uniforms
// define input and/or output structs (appdata, s2f, FragmentOutput...)
v2f vert (appdata v)
{
// vertex function
}
FragmentOutput frag (v2f i)
{
// fragment function
}
}
}
// define optional fallback
}
The FragmentOutput struct can also contain render targets (even multiple ones, allowing MRT) and/or the depth buffer target, to do depth writing/checks in fragment shaders.
Describe the feature / enhancement and how it helps to overcome the problem or limitation:
Being able to define multi-pass shaders everywhere allows the user to do things he just can't do right now. Reading and writing to the same buffer can't be performed in the same pass and multiple Viewports or post-processing can't have access to the rendering object's data.
At the optional performance cost of performing an additional pass, the user has maximum power.
I think the rendering code rewrite in Vulkan is the perfect opportunity to add this feature, together with #495.
Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
A simpler and more Godot-shading-language style version of the Unity "ShaderLab" code above can be a good example of how multi-pass shaders could be achieved at the user level.
Unity also permits the definition of tags into shaders and into passes to better manage the rendering of specific objects and to have more granular control over the pipeline. (Very useful in custom rendering pipelines)
If this enhancement will not be used often, can it be worked around with a few lines of script?:
No.
Is there a reason why this should be core and not an add-on in the asset library?:
Multi-pass shaders cannot be added with an add-on or asset library.
you can already do this in Godot, but its different than in Unity. In Godot, you just set the "next pass" property of materials and just put another material.
@reduz Multi-pass materials currently only work with spatial shaders (and don't know why since they are useful for the other types too).
The editor doesn't let you add a next pass material to a ShaderMaterial if the shader assigned is not a spatial.
It does if the material is a CanvasItemMaterial, but doesn't work:
https://github.com/godotengine/godot/issues/17461
https://github.com/godotengine/godot/issues/27726
Adding next pass support for canvas_item shaders etc. could mitigate the limitation for now.
However, the idea of multi-pass shaders is to have all the passes grouped in a single shader definition because it makes sense to have them all in one place. e.g to make the rendering flow clear, to better share parameters between passes etc.
This feature would be so useful! I struggle making some shaders in one pass and often end up not making it at all... The viewport alternative seems hacky.
The best example is a blurred drop shadow for a control. For now, you can only add a hard drop shadow but not blur it as it requires to reuse the result of the hard shadow as input for the blur step.
Must be said that multi-pass shaders serve less purpose if there's no MRT support and custom render targets cannot be freely defined.
The best example is a blurred drop shadow for a control. For now, you can only add a hard drop shadow but not blur it as it requires to reuse the result of the hard shadow as input for the blur step.
Note that StyleBoxFlat offers blurred drop shadows out of the box since Godot 3.1. These shadows can also be offset since 3.2.
This feature would be very useful. There are times when I need to render something both transparent and opaque (for screen-space effects). It has to be in two passes. Having to set the same parameters in two shaders feels a bit inconvenient. It would be nice to access the same uniforms for multiple passes.
Can say I've also come across @MartinGrignard 's problem. Viewports as passes does seem hacky to me. Same situation came up on my end. I needed a way to share some uniforms across multiple shaders. Syncing them through script is one way around, but something like Unity's would make stuff easier and more natural.
I needed a way to share some uniforms across multiple shaders
Global shader uniforms will be usable for this purpose in 4.0.
This would be useful to me, as it would enable me to add multiple visual effects to my item icons in the inventory of in my rpg game: Outline-shader with a color, and glint-shader on the same sprite (but maybe not influencing each other? so the glint doesnt run over the outline-shader, but maybe this would only be dependent on the order of the shaders), not requiring any kind of workaround.
(But maybe i can already do this in just one shader, by combining the code of both shaders, but might be finnicky and non-modular)
@Calinou I see. Global uniforms would work for cases affecting the entire game. As for individual meshes, I see that per-instance uniforms would solve my problem, but unfortunately, it says it doesn't support textures, which I'd need to share across multiple passes.
Most helpful comment
@reduz Multi-pass materials currently only work with spatial shaders (and don't know why since they are useful for the other types too).
The editor doesn't let you add a next pass material to a ShaderMaterial if the shader assigned is not a spatial.
It does if the material is a CanvasItemMaterial, but doesn't work:
https://github.com/godotengine/godot/issues/17461
https://github.com/godotengine/godot/issues/27726
Adding next pass support for canvas_item shaders etc. could mitigate the limitation for now.
However, the idea of multi-pass shaders is to have all the passes grouped in a single shader definition because it makes sense to have them all in one place. e.g to make the rendering flow clear, to better share parameters between passes etc.