Reaction Diffusion Shader Implementation (problems)

Hi there! I’m working on an implementation of the well-known reaction-diffusion model with p5 shaders. However, I’m experiencing issues because the model seems to work, but after a few iterations, it gets stuck and stops progressing. I don’t think it’s a performance issue since I’ve implemented it without shaders, and it doesn’t get stuck in the same way. Does anyone have any ideas on why this might be happening? I don’t think it’s a matter of miscalculating something. I suspect it might be related to the shaders, but honestly, I have no clue. Any insights would be greatly appreciated!

--------------------------------------------------- sketch.js ---------------------------------------------------------------------

let golShader;
let prevFrame;

function preload() {
  golShader = loadShader('gol.vert', 'gol.frag');
}

function setup() {
  
  createCanvas(400, 400, WEBGL);
  pixelDensity(1);
  noSmooth();
  background(255,0,0);
  stroke(0,0,255)
  
  fill(0,0,255)
  rect(0,0,50,50);
  shader(golShader);
  golShader.setUniform("normalRes", [1.0 / width, 1.0 / height]);
  
}

function draw() {
  
  golShader.setUniform('tex', get());
  rect(-width / 2, -height / 2, width, height);

}

------------------------------------------------------------gol.vert-------------------------------------------------------------------

attribute vec3 aPosition;
attribute vec2 aTexCoord;

varying vec2 vTexCoord;

void main() {
  // copy the texcoords
  vTexCoord = aTexCoord;

  vec4 positionVec4 = vec4(aPosition, 1.0);
  positionVec4.xy = positionVec4.xy * 2.0 - 1.0;

  gl_Position = positionVec4;
}

---------------------------------------------------------gol.frag---------------------------------------------------------

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform sampler2D tex;
uniform vec2 normalRes;

float laPlacian(vec2 uv, float m) {
  float laplacian = 0.0;
  float mult ;
  for (float i = -1.0; i < 2.0; i++) {
    for (float j = -1.0; j < 2.0; j++) {
      
      float x = uv.x + i * normalRes.x;
      float y = uv.y + j * normalRes.y;

      if (i == 0.0 && j == 0.0) {
        mult = -1.0;
      } else if (i == j || i == -j) {
        mult = 0.05;
      } else {
        mult = 0.2;
      }
      
      if (m == 0.0) {
         laplacian += texture2D(tex, vec2(x, y)).r * mult;
      } else {
         laplacian += texture2D(tex, vec2(x, y)).b * mult;
      }  
      
    }
  }

  return laplacian;
}

void main() {
  vec2 uv = vTexCoord;
  uv.y = 1.0 - uv.y;

  
  float Da = 1.0;
  float Db = 0.5;
  float f = 0.055;
  float k = 0.062;
  
  
  vec4 col = texture2D(tex, uv); 
  float a = col.r;
  float b = col.b;
  
  float new_a = a + Da * laPlacian(uv, 0.0) - a * b * b + f * (1.0 - a);
  float new_b = b + Db * laPlacian(uv, 1.0) + a * b * b - (k + f) * b;

  gl_FragColor = vec4(new_a, 0.0, new_b, 1.0);
}

You posted your javascript code twice instead of your glsl fragment code.

Having not seen your code, let me take a guess that it’s a numerical precision issue. On the CPU, perhaps you were using floats but on the shader you’re using color values which, although they are a float vec3 on the shader, are only stored as 8-bit colors in the framebuffer. If you try to add too small of a value to an 8-bit value, anything less than 1.0/256, it’ll just disappear.

Hi, thanks for the help. I’ve already fixed the post. I’m new to shader programming, and how can I increase the numerical precision of the buffer?

p5 now has a Framebuffer that supports float datatypes. I haven’t used it so can’t give you the details.

The problem with your code might not be precision, but that the default, byte-based images only support values from 0 to 1, whereas your math functions are assuming full floats with negative values. You could try subtracting 0.5 from each texture() call and adding 0.5 to the values before writing them to the gl_FragColor.

Using float framebuffers is more likely the better solution.

I just tried subtracting and adding 0.5, and it didn’t work :frowning:

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform sampler2D tex;
uniform vec2 normalRes;

float laPlacian(vec2 uv, float m) {
  float laplacian = 0.0;
  float mult ;
  for (float i = -1.0; i < 2.0; i++) {
    for (float j = -1.0; j < 2.0; j++) {
      
      float x = uv.x + i * normalRes.x;
      float y = uv.y + j * normalRes.y;

      if (i == 0.0 && j == 0.0) {
        mult = -1.0;
      } else if (i == j || i == -j) {
        mult = 0.05;
      } else {
        mult = 0.2;
      }
      
      if (m == 0.0) {
         laplacian += texture2D(tex, vec2(x, y)).r - 0.5 * mult;
      } else {
         laplacian += texture2D(tex, vec2(x, y)).b - 0.5 * mult;
      }  
      
    }
  }

  return laplacian;
}

void main() {
  
  vec2 uv = vTexCoord;
  uv.y = 1.0 - uv.y;

  
  float Da = 1.0;
  float Db = 0.5;
  float f = 0.055;
  float k = 0.062;
  
  
  vec4 col = texture2D(tex, uv); 
  float a = col.r - 0.5 ;
  float b = col.b - 0.5 ;
  
  float new_a = a + Da * laPlacian(uv, 0.0) - a * b * b + f * (1.0 - a);
  float new_b = b + Db * laPlacian(uv, 1.0) + a * b * b - (k + f) * b;

  gl_FragColor = vec4(new_a + 0.5, 0.0, new_b + 0.5, 1.0);
  
}

I suppose this is what you are referring to.

In the laplacian, you need parentheses around the (texture2D().r - 0.5) (and again for .b) before the * mult.

And subtracting 0.5 will give you values in the range from -0.5 to 0.5. You might need a greater range than that so you would also need to multiply when reading and divide before writing to scale up/down. I don’t know what the expected range of values is for the laplacian.

Mmmm, according to the tests I have conducted in the iterative implementation, the values of ‘a’ and ‘b’ should always be in the range of 0 to 1.

Here if I set the range of new_a and new_b from 0 to 1. Exactly the same thing happens

void main() {

 vec2 uv = vTexCoord;
 uv.y = 1.0 - uv.y;

 float Da = 1.0;
 float Db = 0.5;
 float f = 0.055;
 float k = 0.062;
 float scale_factor = 2.0; // Adjust this value based on your needs
 
 vec4 col = texture2D(tex, uv); 
 float a = col.r ;
 float b = col.b ;
 
 float new_a = a + Da * laPlacian(uv, 0.0, scale_factor) / scale_factor - a * b * b + f * (1.0 - a);
 float new_b = b + Db * laPlacian(uv, 1.0, scale_factor) / scale_factor + a * b * b - (k + f) * b;
 
 new_a = clamp(new_a, 0.0, 1.0);
 new_b = clamp(new_b, 0.0, 1.0);

 gl_FragColor = vec4(new_a , 0.0, new_b , 1.0);

}