bevyengine / bevyengine/bevy

Make extending `StandardMaterial` wgsl shader easier for common cases

Open
#17,845 0 comments 16 reactions 0 assignees View on GitHub
A-Rendering C-Feature D-Complex S-Needs-Design
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## Preambula

Beforehand I just want to note that this is not a critic of bevy's approach, I'd just like to see some ease-of-use improvements.

I will be talking only about `StandardMaterial` because it is the most widely used and I constantly see a need to extend it (might be a problem with me) while retaining most of its functionality.

I also don't know if Naga will support this or if it is even Naga's responsibility. This is only a general approach that I'd like to see.

## What problem does this solve or what need does it fill?

In short, all code written and maintained by the user should be minimized as much as possible. Ideally, only the custom logic should be written, without **_any_** other noise.

Currently extending standard material shader requires tons of boilerplate code and copy-pasting from examples even for very **_trivial_** modifications. IMHO I'm not even sure if someone can just write it all by hand without examples/sources. No proper IDE support (code completion, suggestions, etc.) when working with `wgsl+imports` exacerbates this problem even more.

Lets see a simple comparison of the `bevy` example vs almost the same thing done in `godot`.
```
One thing to note here is that bevy example modifies color after the shading pass, while godot before.
Bevy provides more customizability this way but my use-cases constantly demand specifically pre-shading customizations (godot approach).
In any case my proposal allows both case to be handled.
```
![Image](https://github.com/user-attachments/assets/b03651e7-4c6a-40d7-a879-dfd2e3052c28)
```
Red marks all of the code that I consider boilerplate.
```

Now let see how to do the "same" thing in `godot`:
```gd
shader_type spatial;

uniform int quantize_steps = 5;

void fragment() {
float steps = float(quantize_steps);
ALBEDO = vec3(round(UV * steps) / steps, 0.0);
}
```
Amount of the code difference is just staggering.
Bevy's implementation doesn't even fit on my screen and it is hard to see the actual custom logic. Not to mention amount of errors developers can make and amount of shaders you need to update in case of internal changes.

## What solution would you like?
What I'd suggest is instead of "custom logic wraps internal" we add "internal logic wraps custom".

What do I meen by this?
Currently shader that we load is the actual user's shader. Because of this all imports, shading and other stuff needs to be handled by the user.

Instead I suggest for standard material shader to just allow some kind of "hooks" to customize its behaviour, so we can write it like:
```wgsl
@group(2) @binding(100)
var quantize_steps: u32;

fn fragment_before_shading(in: PbrInput) -> PbrInput {
let steps = f32(quantize_steps);
in.material.base_color = vec4(round(in.uv * steps) / steps, 0.0, 1.0);
return in;
}
```

## What alternative(s) have you considered?

The only alternative so far is to write all of the above boilerplate code. I didn't find any easier way so far.

## Additional context

I'm not proposing to remove current `ExtendedMaterial`, but instead to add special "hooks" to shaders.

Same problem happens when extending `vertex` stage. I'm not even talking when we need to add a single custom vertex attribute...

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.