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

I’d like to pass a map options that holds a texture to my custom ShaderMaterial but I get an error, like this:

const material = new ShaderMaterial({
  // ... literally copied MeshLambertMaterial shader here, verbatim ...

  map: texture, // and I want to use a texture
})

// ...

renderer.render(scene, camera)

But when I try to do this, i get an error in console like

gl.getShaderInfoLog() fragment ERROR: 0:884: 'map' : undeclared identifier

When I check the shader output in the console, I see that USE_MAP is not defined.

I also tried this:

const material = new ShaderMaterial({
  // ... literally copied MeshLambertMaterial shader here, verbatim ...

  map: texture, // and I want to use a texture
})

// RIGHT HERE
material.uniforms.map.value = texture

// ...

renderer.render(scene, camera)

so set the uniform, which I thought would cause WebGLProgram to see the map and therefore to #define USE_MAP, but that doesn’t seem to happen.

I basically made an exact replica of MeshLambertMaterial by concatenating ShaderChunks, etc, so the result shader code is identical to MeshLambertMaterial’s shader code).

How do we apply a texture to a this shader that is built using all the default pars as MeshLambertMaterial, so that USE_MAP will be defined and the map variable will exist?

Here seems to be someone with possibly the exact same problem, but no solution yet: https://stackoverflow.com/questions/34542508/custom-shadermaterial-map-issue

Ah, seems all I had to add was:

material.map = texture

and it works! So,

const material = new ShaderMaterial({
  // ... literally copied MeshLambertMaterial shader here, verbatim ...

  map: texture, // and I want to use a texture
})

material.uniforms.map.value = texture
material.map = texture

// ...

renderer.render(scene, camera)

[SOLVED]

3 Likes

This worked. Thanks. Although the console yells at me =]

THREE.ShaderMaterial: 'map' is not a property of this material.

Ahh!!! It worked for me as well. Cheers!!

1 Like

I’m super confused as to how this works.

const material = new ShaderMaterial({
  map: texture, //this should cause absolutely nothing to happen 
})

This should cause nothing:

material.uniforms.map.value = texture //THIS is how you set a value on a uniform in a ShaderMaterial

This just slaps it onto the material. It is possible that WebGLRenderer gets confused about this and does your injection

material.map = texture

How do we apply a texture to a this shader that is built using all the default pars as MeshLambertMaterial, so that USE_MAP will be defined and the map variable will exist?

If you need a USE_MAP define in your ShaderMaterial a better way would be to provide that define.

new ShaderMaterial({defines: { USE_MAP: '' } })

Turns out the only things that are necessary are setting uniforms.map and defines.USE_MAP.

material.map does nothing except cause a console warning.

This should be better documented imo. One has to search forums / SO to find the answer to what I would imagine to be a somewhat common use case.

Well the only way to do that is to voice some opinions on github. You’ve been part of some discussions but excused yourself eventually. This way there wont be much improvement :cry:

Also, technically you dont have to use material.defines either. Preprocessor directives are just part of GLSL core
https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Preprocessor_directives

It’s enough to just add that GLSL. If you have a fragmentShader string you can do

fragmentShader = '#define USE_MAP\n' + fragmentShader 

I just read a 3 year backlog of NodeMaterial and related conversations. I see now why there was pushback on altering materials, seems like the future is not settled, and changes are therefor discouraged it seems.

My voice in that was to add instancing chunks (I still plan on mocking up a non-merge PR for that, but honestly just haven’t felt the drive, been busy). This is merely a documentation alteration in which I feel more confident about getting merged =]

What do you think needs to be added here?

https://threejs.org/docs/index.html#api/materials/Material.defines

Or here?
https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Preprocessor_directives

I was honestly thinking more here:

https://threejs.org/docs/#api/materials/ShaderMaterial

It’s the first placed I looked, before just googling the problem.

People are probably not going to know to look in defines, as they may not know that’s part of the issue. Plus you also have to set a uniform.

You don’t actually have to set a uniform, it will just be the same as having value: null.

My point is that, this is a ShaderMaterial, you use it almost exclusively to write glsl, with some additional three specific syntax (include).

OP correctly identified that some branch of that GLSL program does not exist, why, because it’s behind a preprocessor directive.

defines is just a convenient threejs thing.

My advice for people would be to read the GLSL spec before working with GLSL. Not everything has to be understood straight away, but knowing what you can encounter in code helps…

I was honestly thinking more here:

three.js docs

It’s actually not that super obvious but there is a mention that this is a sub class of Material

parameters - (optional) an object with one or more properties defining the material’s appearance. Any property of the material (including any property inherited from Material) can be passed in here.

This also indicates that relationship i think:
28%20PM

The point being it’s not enough to read the docs of ShaderMaterial if it inherits from Material in order to see the entire picture you need to read both.

Odd, but Material docs don’t actually mention that it inherits from EventDispatcher

I’m sure you are 100% correct and from the perspective of a seasoned three.js dev.

But for someone like myself who does VERY little actual glsl and just want to extend a shader or something, it would be nice to have an easy look up doc.

If something like “Trying to use a texture map? Do this:…” was on the Shadermaterial docs, I’m sure it would reduce the amount of SO questions being asked.

But, yea to get a full understanding of all the underlying mechanisms, they would need to read up on Material and all that.

You don’t actually have to set a uniform, it will just be the same as having value: null.

How do you push texture2d data to the shader without using a uniform?

Hi @pailhead, before posting this thread, I was completely aware of #define functionality of shaders, which are very similar to C/C++. This isn’t the issue.

The issue is this:

When I use a shader (not a custom one) defining map automatically makes the relevant shader code path available. Following this pattern, I thought that if I copied an existing material’s code, that perhaps the same pattern would apply. It’s the expectation that a certain threejs-specific functionality would have still applied in a custom material, but it didn’t.

It would be great if the existing patterns of existing materials could be re-used (inherited from Material or something) so that making custom materials affords certain behavioral patterns like map code being automatically enabled when a map is supplied.

That is what the root of the issue is, and not that I didn’t know how to make a define.

Hope that clarifies where we’re coming from. :blush:

This either wouldn’t apply because “Trying to use a texture map” with ShaderMaterial is just “please consult a GLSL tutorial / doc” or it would just be too many of these edge cases that would overwhelm the docs.
A wiki with a bunch of tutorials and extra docs could work, but three’s philosophy seems to be to stick it into poorly documented code examples :frowning:

So until someone from the community tackles a bunch of these examples, this wont really be documented. Although i dont see whats wrong with specific SO questions, incrementally, this would add up and someone could compile them into one thing or a post here.

This is a tricky thing and unlikely to happen due to a push for NodeMaterial. I think it’s actually bad to rely on internals of WebGLRender to set something like this, if it does indeed happen. You seem to have tricked it into doing so when you provided the map param. If a define is controlling that branch, it should be somehow documented and obvious. I still think that in this case it would have been more beneficial for you to have been aware of Material.defines but i think it wasn’t as obvious as it should be.

This part is tricky, i think the issue is that this happens, hence you make assumptions on other things. ShaderMaterial is pretty raw, it just injects some things but you still have to work with GLSL (define is part of that) and material.uniforms rather than properties. I’d be really confused if i saw code like that. I’d expect map to be uniforms: { map: texture }.

1 Like

I think I see your point. setting uniforms.map and expecting it to work would be odd as generally with shadermaterial you are writing your own glsl anyway. Having uniforms.map really only applies when extending existing three.js shaders. Right?