Using DataTexture to pass world data into a shader

Hi, I am pretty new to Threejs and I need some help.
Apologies if this topic is in the wrong section.

I want to pass a value to all the materials in my scene so that if they find themselves in a specific world position in the scene they have higher or lower saturation.

I split the scene in a grid and I am just using x and y as coordinates to do this.
So as example, every material close to position x=2 y=2 should have saturation 0.9, while everywhere else it should have saturation 0.

To do so I thought about passing a DataTexture as a uniform to my vertex shader. Then inside the shader I can calculate the world position of the vertex, find the value in the texture (say using the r channel in rgba), and then pass it along to the fragment shader to set the saturation of the color.

I am having issues getting the values I store in the DataTexture inside the vertex shader.

Here are some snippets of my code:

class WorldCoordsShaderDataMap {
  public data: { [key: number]: { [key: number]: number } } = {};
  public minX = 0;
  public maxX = 0;
  public minY = 0;
  public maxY = 0;

  public toDataTexture(): DataTexture {
    const width = this.maxX - this.minX;
    const height = this.maxY - this.minY;

    const size = width * height;
    const data = new Uint8Array(4 * size);

    for (let i = 0; i < size; i++) {
      const stride = i * 4;
      data[stride] = 0;
      data[stride + 1] = 0;
      data[stride + 2] = 0;
      data[stride + 3] = Math.random(); // This should be the value for the saturation
      // ignoring the content of this.data for now
    }

    // used the buffer to create a DataTexture
    const texture = new DataTexture(data, width, height);
    texture.needsUpdate = true;
    return texture;
  }
}


// somewhere in the code I create a material that is correctly applied to the mesh as follows

    const saturationMap = new WorldCoordsShaderDataMap();

    for (let x = -SIZE; x < SIZE; x++) {
      saturationMap.data[x] = {};
      for (let y = -SIZE; y < SIZE; y++) {
        saturationMap.data[x][y] = Math.random();
      }
    }

    saturationMap.minX = -SIZE;
    saturationMap.maxX = SIZE;
    saturationMap.minY = -SIZE;
    saturationMap.maxY = SIZE;

    const material = new ShaderMaterial({
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      transparent: true,
      uniforms: {
        uFrequency: { value: new Vector2(0.8, 0.2) },
        uTime: { value: 0 },
        uColor: { value: new Color("orange") },
        uTexture: { value: texture },
        saturationMap: { value: saturationMap.toDataTexture() },
        saturationMapMinX: { value: saturationMap.minX },
        saturationMapMinY: { value: saturationMap.minY },
        saturationMapMaxX: { value: saturationMap.maxX },
        saturationMapMaxY: { value: saturationMap.maxY },
      },
    });

Here is my shader that tries to check if the data is passing through changing the vertex z value instead of the saturation.

uniform vec2 uFrequency;
uniform float uTime;
uniform sampler2D saturationMap;
uniform int saturationMapMinX;
uniform int saturationMapMaxX;
uniform int saturationMapMinY;
uniform int saturationMapMaxY;

varying vec2 vUv;
varying float vSaturation;

float random (vec2 st) {
    return fract(sin(dot(st.xy,
    vec2(12.9898, 78.233)))*
    43758.5453123);
}

float getSaturation(int x, int y){
    vec2 saturationMapPosition=vec2(x-saturationMapMinX, y-saturationMapMinY);
    vec4 color = texture2D(saturationMap, saturationMapPosition);
    return color.a;
}

void main() {
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    int x = int(modelPosition.x);
    int y = int(modelPosition.y);
    modelPosition.z += getSaturation(x, y);

    vSaturation=1.0;

    vec4 viewPosition = viewMatrix * modelPosition;
    vec4 projectedPosition = projectionMatrix * viewPosition;
    gl_Position = projectedPosition;
    vUv=uv;
}

At this point, if I return a random value in the getSaturation function I get a change in the z for the vertexes. However with this code I am not able to get the values that I store in the DataTexture. The model with the material applied is in 0,0 and it’s a large plane so I expect its vertexes position to fall in the texture coords values.

I am pretty sure the issue is in how I use the modelPosition in order to retrieve the texture2D color but I am feeling a bit lost here :sweat_smile:

Does anyone have any suggestion?

I didn’t get why you use int values with texture2D.
But if you want to obtain data from a specific texel, then use texelFetch. :thinking:
Something like this (haven’t tested):

float getSaturation(int x, int y){
    ivec2 saturationMapPosition=ivec2(x-saturationMapMinX, y-saturationMapMinY);
    vec4 color = texelFetch(saturationMap, saturationMapPosition, 0);
    return color.a;
}