Lets consider the following statement (from the ShaderMaterial
docs)
A material rendered with custom shaders. A shader is a small program written in GLSL that runs on the GPU.
GLSL runs not just on WebGL, so if we consider this we may interpret this as:
ShaderMaterial
allows you to write arbitrary GLSL programs and use it with three.js
Ok, now lets consider a simple fragment shader program, one that someone might want to use with three.js, but could come from some other graphics (non webgl) framework.
uniform sampler2D map;
varying vec2 myVaryingUV;
void main(){
gl_FragColor = texture2D( map, myVaryingUV );
}
There isn’t a lot going on, a sampler is looked up by a varying variable. So there are two contracts -> map
and myVaryingUV
. Lets disregard the varying for this explanation and focus just on the uniform. It’s there, it’s declared, it won’t break the shader. It’s just a matter now of somehow passing data to it (three.js does this).
This is all it takes to validate the shader, anywhere (it doesnt actually use any of the injections that ShaderMaterial
does over RawShaderMaterial
. IE. this string will compile as a fragment program not just with three.js not just with WebGL.
Ok, now the obvious thing is that there are no preprocessor directives, no branching, no defines. USE_MAP
is not part of this shader. My map
uniform works fine with no defines.
I wrote many ShaderMaterial
in many projects, and never once named a uniform map
. I would name it uMap
or u_map
. However, lets say i wrote a shader, or copied one, that did have uniform sampler2D map
. There is no way of telling that USE_MAP
is coupled with that uniform. The assumption should be that there is no automagical coupling happening because i might want to use USE_MAP
for something else.
Consider a shader like this:
//imagine i decid to arbitrarily use USE_MAP in my shader
//i may have copied it from somewhere, and i may be totally
//oblivious to the fact that its used in some of the built-in materials
//in three.js this means "inject this map"
//in my shader it means "run a loop and look up some random value texture"
#ifdef USE_MAP
uniform vec2 myCustomMapThing[16];
uniform sampler2D myDataMap;
#endif
uniform sampler2D map;
varying vec2 vUv;
void main(){
vec4 tex = texture2D( map, vUv );
#ifdef USE_MAP
for ( int i = 0 ; i < 16 ; i ++ ){
tex *= someLogic();
}
#endif
gl_FragColor = tex;
}
I can’t express enough in text how HORRIBLE(!) this would be if, THREE.WebGLRenderer
randomly decided to inject some defines
based on some arbitrary uniform name that you might choose. It’s actually really bad if it currently somehow picks up the .map
property and automatically injects that. It would basically break this shader since it was written specifically to work with that pre processor directive, but without knowing that USE_MAP
is used somewhere else.
So, my map
uniform just works in GLSL. I might want to consider simplifying this:
myMateria.uniforms.map.value = tex
to
myMaterial.map = tex
with
Object.defineProperty(myMaterial, map, { get: ()=>{myMaterial.uniforms.map.value}})
If THREE.WebGLRenderer
picks this up and injects USE_MAP
it will break my shader.