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
Does anyone have any suggestion?