Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Suggestion: Support Global Uniforms #16922

Open
gkjohnson opened this issue Jun 27, 2019 · 12 comments
Open

Feature Suggestion: Support Global Uniforms #16922

gkjohnson opened this issue Jun 27, 2019 · 12 comments

Comments

@gkjohnson
Copy link
Collaborator

I've been using the Line example in a recent project and I've found the resolution uniform on LineMaterial a bit difficult to work with. It's per-renderer-specific and must be maintained and updated on every LineMaterial if it changes. I know a comment mentions that the renderer could eventually set it but it seems like there could be a general solution to this type of issue.

I'm proposing adding a list of global uniforms to the WebGLRenderer that override a given uniform on all materials being rendered:

renderer.uniforms = {
    resolution: {
        value: new Vector2()
    }
};

Other uniforms such as environment maps, time incrementing variables, adn lighting variables could benefit from this pattern, as well. Unity implements something similar using the Shader.SetGlobalFloat() etc functions.

I think my big open questions in the API design would be:

  • How can an individual Material not be affected by a global uniform?
  • What should happen if the shape of the uniform values does does not match (Vector3 on global value, Vector2 on material)?
@greggman
Copy link
Contributor

just a suggestion but if you go down this path consider using a prefix, like all global properties are named t3dg_ or something? Pretty much every project that does something like this and doesn't choose a way to separate magic stuff from user stuff ends up regretting it further down the line or at least that is my experience.

@Usnul
Copy link
Contributor

Usnul commented Jun 27, 2019

I'm not thrilled about this suggestion. It will only work for some cases the way you envision it, but as a result you will be submitting unnecessary uniforms in every "normal" material. Performance hit will not be huge for a couple of uniforms, but there is no justification for it in most of the standard use-cases.

@gkjohnson
Copy link
Collaborator Author

I should clarify that this would be an entirely optional feature intended for the application developer to have complete control over. THREE should not automagically define global shader uniforms that override material values. This would simply be a tool of convenience to afford more control over how the scene and materials renders and materials would not require that global uniforms be set in order to function.

@greggman So in that case any prefixes would be defined as needed by the app developer and there shouldn't be any THREE magic going on.

@Usnul I don't see why unnecessary uniforms would have to be set on every material. A uniform should only be set if a material / shader requires it -- certainly the performance shouldn't be a whole lot different than now in that case?

@greggman
Copy link
Contributor

@greggman So in that case any prefixes would be defined as needed by the app developer and there shouldn't be any THREE magic going on.

I guess I mis-understood. I thought you wanted THREE to define some global uniforms

It sounds like you rather meant you just want to make it possible for the user to supply global uniforms, so three provides a way to define them but doesn't define any itself. Correct?

@Usnul, As for speed, if you're required to set needsUpdate when adding or removing any uniforms then the first time a material is used THREE could cache the intersection of uniform names on the shader vs uniform names on the material as well as global uniform names so at render time there's no extra work.

@Usnul
Copy link
Contributor

Usnul commented Jun 27, 2019

@greggman

As for speed, if you're required to set needsUpdate when adding or removing any uniforms then the first time a material is used THREE could cache the intersection of uniform names on the shader vs uniform names on the material as well as global uniform names so at render time there's no extra work.

Couple of points to that:

  • three.js uploads uniforms every frame, as far as I'm aware
  • what good is a system for global uniforms if it only works on three.js own ShaderMaterial and not RawShaderMaterial?
  • you end up compiling larger shaders if the uniforms are, indeed, global

One more point to all of that. Say you have a new material that you defined, and you're such a Mr Smarty-Pants that you made use of global uniforms, well, now others can't use your material without explicitly knowing that some of the uniforms are not declared on the shader but are instead pulled from global scope.

@gkjohnson
Copy link
Collaborator Author

@greggman

I guess I mis-understood. I thought you wanted THREE to define some global uniforms

It sounds like you rather meant you just want to make it possible for the user to supply global uniforms, so three provides a way to define them but doesn't define any itself. Correct?

That's right -- I realize I could have been more clear in the original issue so I've updated it a bit.

@Usnul

These all depend on the implementation and I think are solve-able problems.

three.js uploads uniforms every frame, as far as I'm aware

I think that's true but that doesn't mean that every global uniform needs to be uploaded for every material. A global uniform would be updated only if that uniform already exists in the Material.uniform object meaning there shouldn't be extra uniforms being uploaded unnecessarily.

what good is a system for global uniforms if it only works on three.js own ShaderMaterial and not RawShaderMaterial?

I'm not sure why it couldn't work for RawShaderMaterial. Uniforms need to be specified for RawShaderMaterial like any other material so they should behave the same way.

you end up compiling larger shaders if the uniforms are, indeed, global

Again uniforms should not be auto-injected into shaders. This won't require any changes to the existing shaders.

Say you have a new material that you defined, and you're such a Mr Smarty-Pants that you made use of global uniforms, well, now others can't use your material without explicitly knowing that some of the uniforms are not declared on the shader but are instead pulled from global scope.

I'm imagining the global uniform being used would need to exist in the Material.uniforms object, as well, meaning that the shader would continue to function even if the global uniform is not defined.

Maybe the feature is better described as "uniform overrides"?

@Usnul
Copy link
Contributor

Usnul commented Jun 27, 2019

CUS or Cascading Uniform Sheets?

I understand what you are saying. If the Material.uniforms defines a uniform, only then it will be squashed by a global uniform. What if I want to override a global value and have an "exception" where I want to use a different value just for 1 material?

If you do this kind of an override - the problem is largely the same, you are doing something in one place, and there is an impact somewhere else without a clear trace. In three.js you can use an overrideMaterial on a WebGLRenderer, but that produces very clear and noticeable results, I'm not convinced that global uniforms would be as clear. I'm not against it on principle, people can have whatever they want, as long as common usecases are not impacted negatively, I would not use this feature, however, because I see it as error-prone, you use it and later find yourself trying to figure out why your application behaves in a certain way, because you forgot about this cause-effect link.

For you, I would recommend having some kind of a factory for Materials where you can inject some common/global/cascading uniforms, it would likely serve your usecases and not require you to wait for this feature's potential/possible introduction into the library.

@greggman
Copy link
Contributor

Not the solution you were looking for but a 10% solution is to share the values

const resolution = new THREE.Vector2();
const m1 = new THREE.SomeMaterial(...);
const m2 = new THREE.SomeMaterial(...)
m1.uniforms.resolution.value = resolution;
m2.uniforms.resolution.value = resolution;

now there is just one resolution to update

I actually don't know why three.js uniforms are in the form of {value: ...}. I think that's left over from when you used to have to specify the type. Looking through a running example it appears it would be safe to make your own object so for single value uniforms like time you could make a simple class

class SingleUniformValue {
  const(v = 0) { this.set(v); }
  get value() { return this.v; }
  set value(v) { this.v = v; } 
}

const time = new SingleUniformValue();
const m1 = new THREE.SomeMaterial(...);
const m2 = new THREE.SomeMaterial(...)
m1.uniforms.time = time;
m2.uniforms.time = time;

And now you have one place to update time.

It's not nearly as convenient as global uniforms would be but at least there is only one place to update.

@gkjohnson
Copy link
Collaborator Author

@Usnul

What if I want to override a global value and have an "exception" where I want to use a different value just for 1 material?

Yes this is the big gap in the idea at the moment and without a solution I don't think this would really be viable. Unity, for example, uses a default > global > per-material uniform fallback so if a uniform is defined on a material it uses that otherwise it falls back to the global or a default value for that datatype. Three doesn't have that concept built in so it would need something like an ignoreGlobalOverride flag per uniform to tell the renderer not to use global overrides, which isn't all that elegant. Other ideas welcome!

If you do this kind of an override - the problem is largely the same, you are doing something in one place, and there is an impact somewhere else without a clear trace.

For you, I would recommend having some kind of a factory for Materials where you can inject some common/global/cascading uniforms

There are definitely more manual solutions to this problem but I'm writing a framework for other users to quickly and easily write visualizations in and in that case I think there's a utility to things transparently just working.

@greggman
Thanks for the suggestion I've considered that, as well!

If there were a way to track all meshes in a scene being rendered then this type of transparent global uniform behavior could be implemented in a non-invasive way. My PR #16934 enables this type of behavior, which I may actually prefer to this idea because it seems more generally useful.

Thanks again!

@greggman
Copy link
Contributor

Nothing has been implemented so there is no reason if it was implemented that local uniforms couldn't override global ones.

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 8, 2019

UBOs could be useful for this scenario. #21558 introduced a new API called UniformsGroup that enables such a feature.

@DavidPeicho
Copy link
Contributor

DavidPeicho commented Dec 22, 2020

This sound also useful for iTimeDelta or those kind of uniforms. It's indeed possible to modify materials, but sometimes there is just no good way except looping through all of them, or keep an object reference like @greggman suggested.

UBOs look like a great fit for it, but it should also be easy to do in the current state. This is already done on a per-scene basis for things like fog, lights, etc...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
5 participants