Can't get a sampler2D uniform to work from DataTexture

I’m having a weird issue with a procedurally generated DataTexture. I don’t think it’s a bug, but can’t figure out what I’m doing wrong.

The procedure I’m following goes more or less like this. I create my texture of side N, with an array of size 4N^2. This is done by filling in an array and then passing it to a DataTexture:

var dTex = new THREE.DataTexture(data, N, N, THREE.RGBAFormat);
dTex.needsUpdate = true;

Here data is a Uint8ClampedArray. Then I pass this to my ShaderMaterial as a uniform:

var mat =new THREE.ShaderMaterial({
transparent: true,
depthTest: true,
uniforms: THREE.UniformsUtils.merge([{
    color: { type: 'v3', value: new THREE.Vector3(1, 0, 0) },
    alpha: { type: 'f', value: 0.7 },
    shift: { type: 'v2', value: [0., 0.] },
    ditherN: { type: 'f', value: dN},
    ditherTex: {type: 't', value: dTex}
}, THREE.UniformsLib.lights]),
vertexShader: vertShader,
fragmentShader: fShader,
lights: true,
});

while the fragment shader looks like this:

fShader = " \
uniform float alpha; \ 
uniform vec3 color; \
uniform vec2 shift; \
uniform float ditherN; \
uniform sampler2D ditherTex; \ 
varying vec3 vNormal; \
\
void main() \
{ \
  gl_FragColor = texture2D(ditherTex, vec2(0.5,0.5));\
  //gl_FragColor = vec4(1, 1, 0, 1); \
}";

(for now this is just a test but it already reproduces the problem).

Long story short, ditherTex appears to be completely black and transparent, despite me being sure it’s been filled with finite values. If I pass it as an alphaMap to a different material it works too. The fragment shader itself works if I uncomment the last line, so it’s not like it isn’t compiling or anything. Literally the only thing missing is the texture, which for some reason seems to not arrive properly. Any clues as to what I might have been doing wrong?

Are you sure you mean to use vec2(0.5,0.5) on the texture lookup? Shouldn’t it be mapped with UV values so each fragment draws a different part of the texture?

When I get to the final version, yes. As I said this is just a test. I used vec2(0.5, 0.5) because I know for a fact the colour should not be black at that spot. I stripped the fragment shader of all the superfluous detail while still reproducing the problem.

Here, you can check this basic CodePen example:

I set the whole data texture to red, so when combined with the fragment shader it should make the cube yellow, but it’s green instead.

Hi!
In your codepen, when you set values of
data.push(1);
shouldn’t it be like
data.push(255); ?

1 Like

Yes, I fixed that now. Still doesn’t work.

Ehm.
What do you mean with “still doesn’t work”?
Your cube became yellow, instead of green, it means that texture2D(tex, vec2(0., 0.)) reads vec4(1, 0, 0, 1) from your texture and then you set its green channel to 1, thus you’ll get vec4(1, 1, 0, 1) (yellow). At least I see the cube as yellow :slight_smile:

I don’t. I see it green still. What’s your browser, OS and hardware? This seems like it might be some weird hardware or software dependent issue. I still haven’t tried it anyway outside of my own laptop, so it could be my graphic card (it’s an nVidia), but I’ve seen the problem both on Linux and Windows 10, and both on Firefox and Chrome.

Yeek :slight_smile:
It works for me in Firefox, but it’s still green in Chrome.
And in Chrome’s console I see these warnings:
ChromeWarnings

After changing data = new Uint8ClampedArray(data); to data = new Uint8Array(data); I’ve got that cube as yellow :slight_smile: Without any warning in the console.

You’re right, works in Chrome and Firefox if I change the type. I was pretty sure I had tried this too but for some reason it must have not worked when I did. Still seems pretty arbitrary and unpredictable behaviour - especially considering that Uint8ClampedArray is the default type ImageData stores its bytes in. Thanks!

An answer to this question was provided here:

1 Like

Ok, I realised this problem happens again even with a Uint8Array and the reason why I kept having it in my original code was a different bit that here I thought was not essential. I’ve edited the pen:

Basically what I’ve done now is add lights: true to the material’s parameter, and merging the light uniforms. This should not change anything, and it doesn’t if I use a different shader, but I go back to the problem of not being able to read the contents of the Sampler2D I just passed.

The problem here is subtle. When using UniformsUtils.merge(), all uniforms are cloned. That means for textures that you have to set needsUpdate to true again, otherwise the texture data are never uploaded to the GPU.

In general, you normally use UniformsUtils.merge() when defining your shader like in this example file. After creating the material, you assign the actual uniform values.

Demo: https://codepen.io/anon/pen/vPXMpz

2 Likes

An instance of ShaderMaterial should always be created like so:

const customMaterial = new THREE.ShaderMaterial( {
    defines: Object.assign( {}, THREE.CustomShader.defines ),
    uniforms: THREE.UniformsUtils.clone( THREE.CustomShader.uniforms ),
    vertexShader: THREE.CustomShader.vertexShader,
    fragmentShader: THREE.CustomShader.fragmentShader
} );

// now assign uniform values

In this way, defines and uniforms are cloned so you don’t overwrite them when you have multiple instances of your custom material. THREE.CustomShader contains the actual shader definition.

2 Likes

I’m not using an object to gather all the shader at all though. I’m also not passing any defines, and this happens for the first instance of the Material I create. I agree this seems like a cleaner pattern, I’m just not convinced it has anything to do with my problem.

Well, I’ve fixed the problem by setting the texture after the uniform clone. I’m pretty sure this was the issue…

2 Likes

Oh, ok. So what should I do? Clone the uniforms, then set the texture where? Directly in the properties of ShaderMaterial?

I would do it after creating the instance of ShaderMaterial like so:

customMaterial.uniforms.myTexture.value = texture;

BTW: The type property of a uniform is not necessary anymore. three.js automatically derives the type from the uniform definition in the shader.

1 Like