godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.11k stars 69 forks source link

Add support for custom decal materials #4938

Open and-rad opened 2 years ago

and-rad commented 2 years ago

Describe the project you are working on

A demo project showcasing Godot's capability for high-fidelity visuals

Describe the problem or limitation you are having in your project

Godot 4.0 introduces decals. The decal node exposes texture properties for albedo, PBR masks, normals, as well as a handful of other properties to control how decals are rendered. While this is a good base, the fact that there is no way for me to provide an entire material is severly limiting flexibility and efficiency. Things I can't do with the current decal implementation include:

The image below shows a decal atlas that I created for a previous game developed with Unreal Engine. Since It's possible to create decal materials in UE, I am able to control the UV region for individual decals right in the shader.

sign

Another example is this set of textures that I used in a different project to randomize the appearance of bullet holes on different surfaces. Since it's all in a single material, I can easily achieve that by randomizing the UV region and passing it to shader params.

impacts

The decals in the screenshot below are controlled by opacity masks and shader parameters, making it very easy to create different variations of damage / wear on the same decal without having to author different textures, which is what's required for the current implementation in Godot. You can even change the amount of damage as the game progresses, which is what actually happens in that project.

masks

All of these things would require authoring different texture sets and setting up a large number of different nodes and it would still not be on par with what would be possible if decals exposed a property where we could plug in a custom material or shader.

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

Instead of exposing individual texture properties on the decal node, there would be a single material slot. I could assign any compatible material[see the next point] to that slot and set shader parameters on the material. I would be able to set instance uniforms on individual decal nodes.

The video below shows how to work with that kind of setup in UE4. Notice how I control the overall decal opacity with a texture mask that I can easily switch out for another. I control the strength of the opacity mask with additional shader parameters.

https://user-images.githubusercontent.com/8700280/180196168-2fd51249-1e3d-4c9d-849b-650bde94d4e3.mp4

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

About the "compatible material" part: I am not familiar with how Godot handles decals and decal textures internally. It might very well be that not all shader types are supported or that there needs to be a new shader type for decal materials, similar to how there are sky and particles for sky and particle materials, respecitvely.

A decal shader could then look like this:

shader_type decal;

uniform sampler2d opacity_mask;
uniform vec3 base_color : source_color = vec3(0.1, 0.5, 0.3);
instance uniform float opacity_multiplier = 1.0;

void fragment() {
    ALBEDO = base_color;
    ALPHA = texture(opacity_mask, UV).rgb * opacity_multiplier;
}

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

Working around this limitation is not necessarily a coding problem, but an asset creation problem. Yes, some of the mentioned limitations are alleviated by writing code, but most of the extra work is spent creating large numbers of variations of the same asset and spending more time than necessary going back and forth between the engine and DCC tools.

In any way it's true that, given enough time, most but not all of the limitations can be worked around.

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

Decal rendering is part of the engine core and cannot be replaced in plugins as far as I'm aware. Decals are a widely used technology im game design, so a lot of Godot users would benefit from this enhancement.

Calinou commented 2 years ago

Related to https://github.com/godotengine/godot-proposals/issues/2188.

The way decal rendering works is designed to be highly efficient, at the cost of flexibility. Allowing the use of arbitrary decal shaders would likely have a big impact on rendering performance.

animated decals (running water / fresh paint / blood spatter)

You can change the texture frequently via code. This doesn't make flipbook blending possible, but it should be fast enough if its resolution isn't too high.

Using a ViewportTexture on a decal should technically be possible, although it's currently broken.

custom or randomized UV regions (atlas textures)

I wonder if UV offset/scale properties could be exposed, but this would also increase the size of the decal struct unless it can be packed more efficiently somehow.

This would also allow you to create animated decals without changing the actual texture, just the UV offset.

However, it should be noted that decals are already using an atlas internally. Therefore, using an atlas texture on your own probably won't improve performance.

and-rad commented 2 years ago

This would also allow you to create animated decals without changing the actual texture, just the UV offset.

Like a sprite sheet basically? This would certainly be enough for the animated use cases that I outlined above.

The way decal rendering works is designed to be highly efficient, at the cost of flexibility. Allowing the use of arbitrary decal shaders would likely have a big impact on rendering performance.

Do you have any pointers on where to start if I wanted to experiment with this?

Calinou commented 2 years ago

Like a sprite sheet basically?

Yes :slightly_smiling_face:

Do you have any pointers on where to start if I wanted to experiment with this?

No, I don't know how decal shaders could be implemented.

and-rad commented 2 years ago

No, I don't know how decal shaders could be implemented.

I meant more like where and how decal rendering is handled right now in Godot.

clayjohn commented 2 years ago

We use clustered forward decals. Which means that we compile a list of the decals, then we assign them to clusters (like lights), then when an object is drawn, it looks up which decals might intersect it and then simply runs a for loop over those decals and adds their contribution. What makes this efficient is that all the decals are treated the same and can be added using a dynamic for loop. If decals have custom materials, this won't work. You would need to have a shader version for every combination of decal/mesh which would be a disaster for performance.

So we can't do that. We also can't do the traditional "deferred decal" as we don't have g-buffers because Godot uses a Forward Clustered renderer not a deferred renderer.

What we could do is expose two modes to the decal:

  1. Current configuration, provide textures for use by decal
  2. Material mode: provide a material slot and a texture size. Material is used to render PBR textures that are currently exposed. So the internal rendering could work the same, and your DecalMaterial would only need to run when updated so it could be very efficient.

Right now you can achieve something similar to 2 using a Viewport node and assigning a ViewportTexture to the texture slot in the Decal.

SirManfred commented 1 year ago

I must agree that the current Decal material is very basic and doesn't fit all projects. And I understand why different custom materials on different decals won't work in this implementation. But how about allowing a custom global shader for decals? That way the user would be able make a decal shader perfect for their specific project.

Calinou commented 1 year ago

But how about allowing a custom global shader for decals?

I'm afraid that's not feasible either due to how decals are implemented. Decals do not run their own shader (even globally) – they are part of the standard scene shader.

In future 4.x releases, there will be more low-level renderer access so you could replace this standard scene shader entirely (without having to recompile the editor and export templates). This will remain something for advanced users only, though.

clayjohn's implementation idea sounds good, although it may have more limitations than if the decal was truly able to run its own shader.

In the meantime, you can always use a MeshInstance3D + QuadMesh with a BaseMaterial3D or custom shader applied to it. This looks good enough on (mostly) flat surfaces, works with any rendering method (including Compatibility) and is actually faster to render than a Decal node.

If you're looking to use decals to render something like bullet holes (which are small), quads tend to be more suited for this purpose. If you need to use this approach on a large non-flat surface, it's feasible to create an add-on to procedurally generate a mesh that suits the surface in question. This could be done in editor (for static decals) or at run-time (for decals created during gameplay).

QbieShay commented 1 year ago

Right now you can achieve something similar to 2 using a Viewport node and assigning a ViewportTexture to the texture slot in the Decal.

How much of a perf hit is this one?

Calinou commented 1 year ago

How much of a perf hit is this one?

It likely depends a lot on the decal's size, but I assume it has a significant cost. I would likely limit its use to a few decals at most, and also use custom code to disable viewport updating when not in the camera frustum or far away. (VisibleOnScreenNotifier3D Max Distance isn't implemented yet it in 4.x, but it can be done.)

solitaryurt commented 1 year ago

Also want to second the fact that the lack of materials on decals is highly limiting.

Calinou commented 1 year ago

@solitaryurt Please don't bump issues without contributing significant new information. Use the :+1: reaction button on the first post instead.

eddieataberk commented 3 months ago

Is there any update on this or has anyone found a workaround? https://godotshaders.com/shader/decal-shader-4-0-port/ i found this solution which works great to create animated decals etc. however since godot doesn't have a stencil buffer or any way to pass layer ids to shaders it can't mask the objects you don't want to receive decals.