Reaction-Diffusion algorithm and FBO techniques

posted by on 2018.06.08, under Processing

Reaction-Diffusion algorithms are very fascinating, since they are capable of producing incredibly organic patterns. They can also be computationally expensive if the grid of choice is fine enough. In a nutshell, we regard every pixel of an image as a cell containing two types of chemicals in different proportions, and whose combination produces a given color on the screen. The “diffusion equation” is such that, as time goes on, the proportion of the two chemicals changes according to that of the neighborhood cells. Since the algorithm is pixel* based, at its finest, we might think this is a job for a fragment shader. And that’s indeed the case! We have to be careful though concerning two aspects. First, the algorithm uses information about the adjacent pixels, and we know that a fragment shader only treats fragment by fragment information, it does not allow sharing among fragments. This is solved by using a texture to store information about the chemicals. This brings us to the second point: we need to store the previous state of the chemical proportions to compute the next one. On the other hand, a shader is not “persistent”, in the sense that all the information it has concerning fragments is lost on the next frame. Enter FBO and ping-pong technique! Framebuffer objects allows what is called “off-screen rendering”. In other words, instead of rendering the pixels directly to screen, they are rendered to a buffer, and only later displayed to the screen. Hence, we can pass the FBO as a texture to the shader, use, say, the red and green values of the texture at the given fragment coordinate as our chemicals percentage, and set the color of the fragment using the new values of the percentages. This technique is usually referred to as “ping-pong technique”, because we go back and forth from the buffer to the screen. It is particularly useful for modelling particle systems directly on the GPU. In Processing, a FBO is an object described by the class PGraphics, and the shader becomes a method that can be sent to the object.
Here’s the code

PGraphics pong;
PShader diff;

void setup(){
  size(800, 800, P2D);
  pong = createGraphics(width, height, P2D);
  diff = loadShader("diffFrag.glsl");
  pong.background(255, 0, 0);
  diff.set("u", 1.0/width);
  diff.set("v", 1.0/height);

  pong.fill(0, 255, 0);
  pong.ellipse(width/2, height/2, 10, 10);

void draw(){

  pong.image(pong, 0, 0);
  image(pong, 0, 0);

//// diffFrag.glsl

varying vec4 vertColor;
varying vec4 vertTexCoord;

uniform float u;
uniform float v;

uniform sampler2D texture;

float laplaceA(in vec2 p, in float u, in float v){
float A = 0.05 * texture2D(texture, + vec2(-u,-v))[0] + 0.2 * texture2D(texture, + vec2(0,- v))[0] + 0.05 * texture2D(texture,  + vec2(u,-v))[0] +
 0.2 * texture2D(texture, + vec2(-u,0))[0] - 1.0 * texture2D(texture, + vec2(0,0))[0] + 0.2 * texture2D(texture, + vec2(u, 0))[0] +
0.05 * texture2D(texture, + vec2(-u,v))[0] + 0.2 * texture2D(texture, + vec2(0,v))[0] + 0.05 * texture2D(texture, + vec2(u,v))[0];
return A;

float laplaceB(in vec2 p, in float u, in float v){
float B = 0.05 * texture2D(texture, + vec2(-u,-v))[1] + 0.2 * texture2D(texture, + vec2(0,- v))[1] + 0.05 * texture2D(texture,  + vec2(u,-v))[1] +
 0.2 * texture2D(texture, + vec2(-u,0))[1] -1.0 * texture2D(texture, + vec2(0,0))[1] + 0.2 * texture2D(texture, + vec2(u, 0))[1] +
0.05 * texture2D(texture, + vec2(-u,v))[1] + 0.2 * texture2D(texture, + vec2(0,v))[1] + 0.05 * texture2D(texture, + vec2(u,v))[1];
return B;

void main(){

float A = texture2D(texture, )[0] ;
float B = texture2D(texture, )[1] ;

float A_1 = A + (0.9 * laplaceA(, u , v) - A * B * B + 0.0545 * (1 - A)) ;
float B_1 = B + ( 0.18 * laplaceB(, u, v) + A * B * B - (0.062 + 0.0545) * B)  ;

gl_FragColor =  vec4(A_1, B_1, 1.0, 1.0);


And here is an example:


Tip: try to change the numerical values in the definition of A_1 and B_1 in the fragment shader code.

*: A fragment shader technically deals with fragments rather than pixels.