Developing a Fractal Shader

Previously I presented a Newton fractal sketch using ruby Complex numbers. Then I explored shadertoy and converted a sketch to Picrate. This gave me the confidence to develop my own variant:-


Here is the PiCrate code:-

require 'picrate'

class NewtonFractal < Processing::App
  attr_reader :last_mouse_position, :mouse_click_state, :mouse_dragged
  attr_reader :newton

  def settings
    size(600, 600, P2D)
  end

  def setup
    sketch_title 'Newton Fractal Shader'
    @mouse_dragged = false
    @mouse_click_state = 0.0
    # Load the shader file from the "data" folder
    @newton = load_shader(data_path('my_newton.glsl'))
    # Assume the dimension of the window will not change over time
    newton.set('resolution', width.to_f, height.to_f)
    @last_mouse_position = Vec2D.new(mouse_x.to_f, mouse_y.to_f)
  end

  def draw
    # mouse pixel coords. xy: current (if MLB down), zw: click
    if mouse_pressed?
      @last_mouse_position = Vec2D.new(mouse_x.to_f, mouse_y.to_f)
      @mouse_click_state = 1.0
    else
      @mouse_click_state = 0.0
    end
    newton.set('iMouse', last_mouse_position.x, last_mouse_position.y, mouse_click_state, mouse_click_state)
    # Apply the specified shader to any geometry drawn from this point
    shader(newton)
    # Draw the output of the shader onto a rectangle that covers the whole viewport.
    rect(0, 0, width, height)
  end
end

NewtonFractal.new

And my shader, note this is no longer a wrapped shadertoy shader, and is suitable for direct use in other processing variants (try it for yourself in processing.py or vanilla processing suggestions for improvement welcome):-

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

#define ITER 12
#define NEWTON 3  // 2,3,4,5

uniform vec2  resolution;  // viewport resolution (in pixels)
uniform int   iFrame;      // shader playback frame
uniform vec4  iMouse;  // mouse pixel coords. xy: current (if MLB down), zw: click

//Complex Math:
vec2 cexp(in vec2 z) { return vec2(exp(z.x)*cos(z.y),exp(z.x)*sin(z.y)); }
vec2 clog(in vec2 z) { return vec2(log(length(z)), atan(z.y, z.x)); }
vec2 cinv(in vec2 a) { return vec2(a.x, -a.y) / dot(a, a); }
vec2 cmul(in vec2 a, in vec2 b) { return vec2(a.x*b.x - a.y*b.y,   a.x*b.y + a.y*b.x); }
vec2 cdiv(in vec2 a, in vec2 b) { return cmul(a, cinv(b)); }
vec2 cpow(in vec2 a, in vec2 b){ return cexp( cmul(b,clog(a))); }

//---------------------------------------------------------
vec2 newton( in vec2 z )
{
  for (int i = 0; i < ITER; i++)
  {
   vec2 z2 = cpow(z, vec2(2, 0));
   vec2 z3 = cpow(z, vec2(3, 0));
   // vec2 z4 = cpow(z, vec2(4, 0));
   // vec2 z5 = cpow(z, vec2(5, 0));
   // vec2 z6 = cpow(z, vec2(6, 0));
   // vec2 z7 = cpow(z, vec2(7, 0));
   // vec2 z8 = cpow(z, vec2(8, 0));

//---> change z calculation by uncomment different lines
#if NEWTON==2
    z -= cdiv(z3 - 1.0, 3.0 * z2);                      // original: z^3 - 1  / ( 3*z^2)
#elif NEWTON==3
    z -= cdiv(z3 - 0.5+0.05*iMouse.y, (0.5+0.01*iMouse.x) * z2);  // z^3 - my / (mx*z^2)
#elif NEWTON==4
    z -= cdiv(z4 - 0.5+0.05*iMouse.y, (0.5+0.01*iMouse.x) * z3);  // z^4 - my / (mx*z^3)
#elif NEWTON==5
    z -= cdiv(z5 - 0.5+0.05*iMouse.y, (0.5+0.1*iMouse.x) * z4);  // z^5 - my / (mx*z^4)
#elif NEWTON==8
    z -= cdiv(z8 - 0.5+0.05*iMouse.y, (0.5+0.1*iMouse.x) * z7);  // z^5 - my / (mx*z^4)
#endif
  }
  return z;
}

vec3 hsb2rgb( in vec3 c ){
  vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );
  rgb = rgb*rgb*(3.0-2.0*rgb); // cubic smoothing
  return c.z * mix( vec3(1.0), rgb, c.y);
}

vec3 getColor(in float t){
  vec3 col = vec3(t, t, t);
  return hsb2rgb(col);
}
//---------------------------------------------------------
void main(void)
{
  vec2 uv = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
  uv.x *= resolution.x / resolution.y;
  vec2 z = newton(uv);
  vec3 col = getColor(length(z));
  gl_FragColor = vec4(col, 1.0);
}

Using HSB color makes all the difference.

1 Like