Complex gradient loop

Here is a version that computes the gradient in a shader using a Gaussian function for the color spread.

GradientShader-2023-09-27-18-28-53

Press Enter to generate a new set of random points and colors. Space toggles showing the control points and text. The + key saves an image. The right and left arrows change the number of control points. Up and down arrows change the strength of the Gaussian. Up arrow narrows the Gaussian which intensifies the individual color around each control point. Increase it enough and you get a voronoi diagram of the points. If you decrease it enough, everything blurs to the average color of all of the points. By default, the Gaussian is scaled based on the number of points – for more points, you want a tighter curve so each color shows more distinctly.

GradientShader-2023-09-27-18-29-16

I pass the control points to the shader in a Nx2 texture. The top row is the positions with x in AR and y in GB, each scaled from 0 to 65535 across the image. The bottom row is the control point color.

PImage inputs;
PGraphics grad;
PShader gradShader;

int N = 4;
float strength = 1.;
boolean bShowPoints = true;
boolean bSaveFrame = false;

void setup() {
  size( 800, 600, P3D );
  //fullScreen( P3D, SPAN );
  colorMode( HSB, 1, 1, 1, 1 );
  grad = createGraphics( width, height, P3D );
  gradShader = new PShader( this, gradVert, gradFrag );
  initControlPoints();
  gradShader.set( "strength", strength );
  noLoop();
}

void initControlPoints() {
  inputs = createImage( N, 2, ARGB );
  inputs.loadPixels();
  for( int i=0; i<N; i++ ) {
    int x = (int)random(65536);
    int y = (int)random(65536);
    color c = color( random(1), random(0.25, 1), random(0.25, 1) );
    inputs.pixels[ i ] = (x << 16) | y;
    inputs.pixels[ i+N ] = c;
  }
  inputs.updatePixels();
  gradShader.set( "inputs", inputs );
  gradShader.set( "N", N );
}

void draw() {
  gradShader.set( "bShowPoints", bShowPoints );
  grad.beginDraw();
  grad.noStroke();
  grad.shader( gradShader );
  grad.rect( 0, 0, grad.width, grad.height );
  grad.resetShader();
  grad.endDraw();
  image( grad, 0, 0 );
  if( bShowPoints ) 
    text( str(N)+"    "+str(strength), 8, 16 );
  if( bSaveFrame ) {
    String fileName = getClass().getSimpleName() + "-" +
      nf(year(),4) + "-" + nf(month(),2) + "-" + nf(day(),2) + "-" +
      nf(hour(),2) + "-" + nf(minute(),2) + "-" + nf(second(),2) + ".png";
    save( fileName );
    println( "saved " + fileName );
    bSaveFrame = false;
  }
}

void keyPressed() {
  if( keyCode == ENTER ) initControlPoints();
  else if( keyCode == DOWN ) { 
    strength /= 1.1;    
    gradShader.set( "strength", strength );
  }
  else if( keyCode == UP ) { 
    strength *= 1.1;    
    gradShader.set( "strength", strength );
  }
  else if( keyCode == LEFT ) { N = max(N-1, 1);  initControlPoints(); }
  else if( keyCode == RIGHT ) { N++;  initControlPoints(); }
  else if( key == ' ' ) bShowPoints = !bShowPoints;
  else if( key == '+' ) bSaveFrame = true;
  redraw();
}


String[] gradVert = {"""
#version 330
uniform mat4 transformMatrix;
in vec4 position;
void main() {
  gl_Position = transformMatrix * position;
}
"""};


String[] gradFrag = {"""
#version 330
precision highp float;
uniform vec2 resolution;
uniform sampler2D inputs;
uniform int N;
uniform bool bShowPoints;
uniform float strength;
out vec4 fragColor;

void main() {
  float aspect = resolution.x/resolution.y;
  vec2 uv = gl_FragCoord.xy / resolution.y;
  vec3 col = vec3(0.);
  float totw = 0.;
  for( int i=0; i<N; i++ ) {
    vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
    float x = (posData.a * 256 + posData.r)*aspect;
    float y = posData.g * 256 + posData.b;
    vec2 pos = vec2( x, y ) / 65536.0;
    vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
    float d = length( uv - pos );
    float w = exp( -d*d*N*strength );
    if( w > 0 ) {
      col += c * w;
      totw += w;
    }
  }
  if( totw > 0. ) col /= totw;

  if( bShowPoints ) {
    for( int i=0; i<N; i++ ) {
      vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
      float x = (posData.a * 256 + posData.r)*aspect;
      float y = posData.g * 256 + posData.b;
      vec2 pos = vec2( x, y ) / 65536.0;
      vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
      float d = length( uv - pos );
      if( d < 0.01 ) col = vec3(1.);
      if( d < 0.007 ) col = c;
    }
  }

  fragColor = vec4( col, 1. );
}
"""};
3 Likes