[SOLVED] How do we use map uniform with custom ShaderMaterial?

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.

Long story short:

If you’re working with ShaderMaterial adhere to it’s interface. Which is myMaterial.uniforms.uniform.value and vertexShader = "#define MY_DEFINE\n" + myVertexShader;.

2 Likes

Imagine what the docs would have to be if three.js considered all the possible defines and prop combinations from all the built-in materials.

ShaderMaterial allows you to write arbitrary GLSL programs and use it with three.js

But, know that foo,bar,baz,qux,quz… are reserved.

This list would be extremely long and would have to be updated every time any of the built-in materials change.

Right now is:

But, i’ll give you some super generic variables for convenience, that you can’t possibly want to name different, and that you will most likely want to use - like modelMatrix or position.

1 Like

I’m experimenting with a material system refactor, and i can indeed confirm this. Just having a property .map on any Material including ShaderMaterial will trigger the defines to be injected.

The checks are here

This is no bueno IMHO. ShaderMaterial should probably document these sideffects - make these prop names reserved.

2 Likes