godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Pass currently-rendering camera's cull mask to shaders #5563

Closed NumbuhFour closed 1 year ago

NumbuhFour commented 2 years ago

Describe the project you are working on

I am making a game where subviewports render the same scene in both "thermal vision" and "normal vision" simultaneously. Each object either renders with a heatmap texture or its albedo texture depending on which viewport is rendering it.

This would also be useful for a split-screen project which wants to show objects/entities in different colors based on team. e.g show entities in red if they are on the enemy team and blue if they are friendly relative to the observing player.

Describe the problem or limitation you are having in your project

There is no way for a shader to change its output based on the camera rendering it.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

We supply shaders with the cull-mask of the camera rendering the current pass, it can act as an identifier for the layer being processed so that the shader can change its output accordingly.

For the team example above, the workflow would be to have objects of team-A have mask layers 1 and 2, and objects of team-B have mask layers 1 and 3. Cameras for each team would match, and any other objects would just have layer 1 Shaders on team-specific objects would then check the camera's cullmask for 2 or 3 and show either red or blue according to their team. The result is that friendly objects appear blue and hostile objects appear red to you. The other player sees the inverse.

For the other example above, the normal camera would use layer 1 and the thermal camera would use layer 2. Objects would be on layers 1 and 2 and have a first-pass material which outputs its typical albedo texture. Then, a second pass displays a texture representing the "thermal vision" texture, but discards if the cullmask does not include layer 2.

The picture below shows an example scene where each camera sees the same object in different colors, as each camera has different cullmasks

Godot_VisibleLayers_Shader_example

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

The cull mask would be passed as a uniform adjacent to the other camera uniforms such as CAMERA_POSITION_WORLD and CAMERA_DIRECTION_WORLD. The UBO currently has 3 uints being used for padding and, fortunately, the visible_layers property on 3D cameras is also a uint. My proposal would be to simply grab that and add it to the UBO alongside everything else, replacing one of those padding uints.

I have gotten it working locally as pictured above.

If this enhancement will not be used often, can it be worked around with a few lines of script?

No, that information is not currently available to shaders in any form and cannot be passed per-rendering-pass (and if it could, it would be extremely slow).

The only workaround is to have two meshes with different materials occupy the same space anytime you need to switch based on active camera with each mesh on a different layer. This works for a small number of objects, but can quickly get out of control when you have either a lot of meshes that need this behavior or if you have objects with more complex states that need to be synchronized e.g animations, transforms, and parameters.

Is there a reason why this should be core and not an add-on in the asset library?

Add-ons are not capable of adding per-pass uniforms to shaders to my knowledge.

Furthermore, it is replacing a single uint which is currently being passed as padding anyway. It requires no additional processing to gather, but greatly expands the creative potential of 3D graphics in godot.

Zireael07 commented 2 years ago

Why are you using separate viewports for this? I am doing thermal camera in my FPS too, just swapping materials on meshes.

unfa commented 2 years ago

Probably a Global Shader Uniform could also be used? But that would need additional code and would not work per-camera.

Having this option would allow for some interesting rendering, though we may need a node that would decompose the Cull Mask into a set of Bools inside Visual Shader.

NumbuhFour commented 2 years ago

Why are you using separate viewports for this? I am doing thermal camera in my FPS too, just swapping materials on meshes.

Because my situation involves having both a thermal camera and the main camera visible on-screen simultaneously rather than switching from one camera mode to another. Like if the character had a thermal camera on a smartphone screen in their hand in 3D space.

Probably a Global Shader Uniform could also be used? But that would need additional code and would not work per-camera.

I agree, I think CAMERA_WORLD_POSITION uses global uniforms as well, and that's what I copied in my prototype.

Having this option would allow for some interesting rendering, though we may need a node that would decompose the Cull Mask into a set of Bools inside Visual Shader.

Visual Shader does have nodes for bitwise operations, though I agree, having an "unpack bytemask" node would be handy if this were implemented. I don't know how often that node would be used in visual shaders besides specifically for this cullmask though.

akien-mga commented 1 year ago

Implemented by https://github.com/godotengine/godot/pull/67387.