godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.05k stars 65 forks source link

Implement `#extends "path"` #9622

Closed RadiantUwU closed 2 weeks ago

RadiantUwU commented 2 weeks ago

Describe the project you are working on

Sci-fi game

Describe the problem or limitation you are having in your project

Trying to overlay multiple shaders on top of one another without having to re-draw the same object.

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

This would work by creating a new preprocessor instruction named #extends "shader.gdshader".

This would allow overlaying multiple shaders on top of one another.

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

To overlay, we just copy the code from the old shader, add on top of everything defined by it (unless overridden or referenced by the extended shader), and do the same with the vertex, fragment and light functions.

Overlaying them is not a single task, this feature proposal says:

Under the hood during preprocessor when #extend is ran it will run the preprocessor on the base file with the same definitions from the extended file (similar to #7870 ), then get all of the definitions inside the base shader file. Once that criteria is met, it verifies for references or overrides. If it finds a reference, it will not rename it, if it finds an override, it verifies if the new definition can substitute the old one, and if so, it will remove the definition in the base file and introduce the new definition in the same place where the old definition should have been. If it cannot substitute, it gives a warning about having the same name and how it failed to substitute, and thinks that those 2 are not related. This will just rename the base references and definition and continue with no error.

Example: base.gdshader

vec3 gimme_color() {
  return vec3(1.0,0.0,0.0);
}
float gimme_alpha() {
#ifdef ALPHA_GIVEN
  return 0.5;
#else
  return 0.0;
#endif
}

void vertex() {
  VERTEX+=NORMAL;
}

void fragment() {
  ALBEDO=gimme_color();
  ALPHA=gimme_alpha();
}

extended.gdshader

#define ALPHA_GIVEN

#extend "base.gdshader"

OVERRIDE_BEFORE(gimme_color, bool some_bool_function() {
  return true;
})
vec3 gimme_color() {
  if (some_bool_function()) {
    return vec3(0.0,1.0,0.0);
  }
  return vec3(0.0,0.0,1.0);
}

void fragment() {
  ALPHA=0.3;
}

The output from this:

#define ALPHA_GIVEN

bool some_bool_function() {
  return true;
}

vec3 gimme_color() {
  if (some_bool_function()) {
    return vec3(0.0,1.0,0.0);
  }
  return vec3(0.0,0.0,1.0);
}

float gimme_alpha() {
#ifdef ALPHA_GIVEN
  return 0.5;
#else
  return 0.0;
#endif
}

void vertex() {
  VERTEX+=NORMAL;
}

void fragment() {
  ALBEDO=gimme_color();
  ALPHA=gimme_alpha();
  ALPHA=0.3;
}

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

This could be worked around by having both the base and the extended shader use a shader include.

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

N/A

AThousandShips commented 2 weeks ago

Shaders aren't classes, IMO this doesn't make sense as shader functions aren't inheriting anything, this would be extremely unusual and AFAIK isn't available in any other shading languages, do you have any practical examples of something else doing this? To me this seems like something extremely unorthodox and unintuitive

It also isn't inheritance, not properly, you don't call the parent method anywhere, so this means you can't control the order of composition, unlike GDScript for example with super.foo()

RadiantUwU commented 2 weeks ago

Shaders aren't classes, IMO this doesn't make sense as shader functions aren't inheriting anything, this would be extremely unusual and AFAIK isn't available in any other shading languages, do you have any practical examples of something else doing this? To me this seems like something extremely unorthodox and unintuitive

It also isn't inheritance, not properly, you don't call the parent method anywhere, so this means you can't control the order of composition, unlike GDScript for example with super.foo()

Do you have any idea of a better way to do overlaying?

AThousandShips commented 2 weeks ago

Do you have any idea of a better way to do overlaying?

That isn't really constructive or relevant, right? I'm pointing out things that I find are unusual and potentially confusing and hard to use in your suggestion, not claiming to have a better solution.

RadiantUwU commented 2 weeks ago

To be honest, this instead implements an extendable system and does treat shaders as classes. I am also thinking of an alternative solution to overlay.

Calinou commented 2 weeks ago

One of the goals of Godot's shader language is to be as close as possible to standard GLSL (with common preprocessor functions such as #include and #if). This is done so that you can port GLSL shaders you can find online to Godot's shader language quickly (and vice versa - if you need to reuse the shader outside Godot, there's less of a lock-in effect).

#extend doesn't exist in any preprocessor I know of, so this feature would be contrary to that goal.