Open gkjohnson opened 5 years ago
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.
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.
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 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.
@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:
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.
@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"?
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 Material
s 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.
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.
@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!
Nothing has been implemented so there is no reason if it was implemented that local uniforms couldn't override global ones.
UBOs could be useful for this scenario. #21558 introduced a new API called UniformsGroup
that enables such a feature.
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...
I've been using the Line example in a recent project and I've found the
resolution
uniform onLineMaterial
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:
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: