How can I glow the thing on the screen shining from the first?

There is a knack in making glow effect with the 'Processing' or the 'p5.js'.

Glow effect in 'Processing/p5.js'.

I'll describe specifically how to make a glow effect with the code. I'll write a code for explanation with the 'p5.js'. And I'll show you the advanced example animation code with the 'Processing'.

👉 この記事は日本語でも読めます。

 

 

The computer screen is shining in the first place...

Your creative coding code usually outputs the result on the computer screen. And the screen shines itself so your creative coding code's result is shining from the very first.


The color on the screen is made of the three primary colors of light (red, green, and blue).

Red, Green, and Blue = RGB
https://en.wikipedia.org/wiki/RGB_color_model

The three primary colors of light.

It's shining too but we don't feel it is shining.

 

How to make it glow.

Then, how can I glow the thing on the screen?

One of the easy ways in Processing/p5.js is using 'blendMode(ADD)'. The 'blendMode(ADD)' do addition of colors as its name suggests.

For example, this code draws circles of red, green, and blue color.


const w = 640;
const h = w;

function setup() {
    createCanvas(w, h);
    colorMode(HSB, 360, 100, 100, 100);
    noLoop();

    background(0, 0, 0, 100);
    noStroke();

    // Red
    fill(0, 90, 30, 100);
    circle(w * 0.4, h * 0.4, w * 0.5);
    // Green
    fill(120, 90, 30, 100);
    circle(w * 0.6, h * 0.4, w * 0.5);
    // Blue
    fill(240, 90, 30, 100);
    circle(w * 0.5, h * 0.6, w * 0.5);
}

Circles of red, green, and blue color.

It ordinally results.

 

I put the 'blendMode(ADD)' before drawing, then it does the addition of red, green, and blue colors. And the result is the picture of three primary colors of light I showed before.


    background(0, 0, 0, 100);
    noStroke();

    blendMode(ADD);
    // Red

The three primary colors of light.

Red + Green = Yellow, Red + Green + Blue = White

 

If you repeat drawing with low brightness like this code, it creates the effect "Oh! It's glowing!".


const w = 640;
const h = w;

function setup() {
    createCanvas(w, h);
    colorMode(HSB, 360, 100, 100, 100);
    
    noLoop();
    frameRate(15);

    background(0, 0, 0, 100);
    noStroke();

    blendMode(ADD);
    for (let r = 0.0; r < 0.5; r += 0.01) {
	// Red
	fill(0, 90, 5, 100);
	circle(w * 0.4, h * 0.4, w * r);
	// Green
	fill(120, 90, 5, 100);
	circle(w * 0.6, h * 0.4, w * r);
	// Blue
	fill(240, 90, 5, 100);
	circle(w * 0.5, h * 0.6, w * r);
    }
}

Creating the effect "Oh! It's glowing!".

It's an animation of drawing over and over.

 

More light!

When you repeat drawing, the hue, saturation, and brightness values act upon the way of glowing.

※Hue, Saturation, and Brightness
The default color mode of Processing/p5.js is 'RGB'. It's hard to control the hue, saturation, and brightness values individually with the 'RGB' color mode. So I recommend using the 'HSB' color mode in this situation.
colorMode(HSB, 360, 100, 100, 100);

For example, it looks different even you just change the saturation value.

Glowing with the saturation = 90.
Glowing with the saturation = 60.
Glowing with the saturation = 30.

This is the animation example of the blue and bluish-purple circles. It changes the saturation value of the bluish-purple circle only.

 

100 + 0 = 100

The 'blendMode(ADD)' means addition. So you can't paint with black (brightness = 0). The more precise, it paints with 'brightness = 0' but it is the same result when you add zero to some.

If you want to paint with 'background()' every frame, you should switch the blend mode to 'blendMode(BLEND)'. I often forget about it and I see the white outed screen.

I'll give an example of changing saturation value and switching the blend mode. You can see how the effect works when you comment out the 'blendMode(BLEND)'.


const w = 640;
const h = w;

function setup() {
    createCanvas(w, h);
    colorMode(HSB, 360, 100, 100, 100);
}

function draw() {

    let frmRatio = map(frameCount % 120, 0, 120, 1.0, 0.0);

    blendMode(BLEND);
    background(240, 100, 30, 100);
    noStroke();

    // sun
    blendMode(ADD);
    for (let r = 0.0; r < 1.0; r += 0.01) {
	fill(280, frmRatio * 100, (1.0 - r) * 5, 100);
	circle(w * 0.6, h * 0.5, w * r);
    }

    // planet
    blendMode(BLEND);
    fill(240, 100, frmRatio * 80, 100);
    circle(w * 0.5, h, w * 0.8);

}

 

ADD and SCREEN

There is the 'blendMode(SCREEN)' similar to the 'blendMode(ADD)'.

'SCREEN' brings mild effect than 'ADD'.

  • ADD : glistening, blaze
  • SCREEN : soft glow
  • Glowing with the blendMode(ADD).
    Glowing with the blendMode(SCREEN).

     

    Recap of the points on how to make it glow.

    Points of the glow effect.

  • Use 'blendMode(ADD)'.
  • Repeat drawing with low brightness.
  • Don't forget to switch to 'blendMode(BLEND)' when you paint it black.

  • Points of devising.

  • Repeat drawing with changing the colors.
  • Switch to the 'blendMode(SCREEN)'.
  • Tune the hue, saturation, and brightness values.
  •  







    The example code of 'p5.js'.

    I wrote an example code of a glowing animation using 'blendMode(SCREEN)' in p5.js (JavaScript). The title is 'City lights'. Please run it and enjoy how it shows.

    Please feel free to use this example code under the terms of the GPL.

    
    // City Lights
    
    const w = 720;
    const h = 480;
    const blobNum = 20;
    
    function setup() {
        createCanvas(w, h);
        colorMode(HSB, 360, 100, 100, 100);
        frameRate(15);
    }
    
    function draw() {
    
        let frmRatio = map(frameCount % 120, 0, 120, 0, 1);
    
        blendMode(BLEND);
        background(240, 100, 20, 100);
    
        for (let i = 0; i < blobNum; i++) {
    	let bTime = sin(PI * ((frmRatio + noise(10, i)) % 1));
    	let bHue  = (360 * frmRatio + noise(20, i) * 240) % 360;
    	blob(i / blobNum, noise(30, i), bTime, bHue);
        }
    
    }
    
    function blob(_x, _y, _t, _hue) {
    
        blendMode(SCREEN);
        noStroke();
        for (let r = 0.0; r < 0.2; r += 0.002) {
    	fill(_hue, 100, r * 3, 100);
    	circle(_x * w, _y * h, w * r * 0.5);
    	fill(_hue, _t * 100, (1.0 - r) * 3, 100);
    	circle(_x * w, _y * h, w * r);
        }
    
    }
    
    /*
    Copyright (C) 2022- deconbatch
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
    */
    
    
    

     

    The example code of 'Processing'.


    This example code shows sparkling lights rather than glowing. Please feel free to use this example code under the terms of the GPL.

    
    /**
     * Light Years.
     * simple animation using Node-Garden technique.
     *
     * @author @deconbatch
     * @version 0.1
     * @license GPL Version 3 http://www.gnu.org/licenses/
     * Processing 3.5.3
     * 2022.01.15
     */
    
    void setup() {
      size(720, 480, P2D);
      colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
      smooth();
      noLoop();
    }
    
    void draw() {
    
      int frmRate  = 30;
      int cycleSec = 8;
      int cycleMax = 3;
      int nodeNum  = 15;
      float orbitBase = min(width, height) * 0.25;
      float rangeS    = orbitBase * 0.2;
      float rangeL    = orbitBase * 0.4;
      float hueBase   = random(360.0);
    
      // nodes clusters
      ArrayList<Cluster> clusters = getClusters(nodeNum, orbitBase);
      int clusterMax = clusters.size();
    
      // nodes
      ArrayList<Node> nodes = getNodes(clusterMax * nodeNum, orbitBase);
      int nodeMax = nodes.size();
    
      // easing functions
      ArrayList<Ease> easing = new ArrayList<Ease>();
      easing.add(new Four());
      easing.add(new Quadratic());
      easing.add(new Cos());
      easing.add(new Pow());
      easing.add(new Cubic());
      int easeMax = easing.size();
    
      int frmMax = frmRate * cycleSec * cycleMax;
      int frmCycleMax = frmRate * cycleSec;
    
      for (int frmCnt = 0; frmCnt < frmMax; frmCnt++) {
    
        int   cycleCnt = frmCnt / frmCycleMax;
        float frmRatio = map(frmCnt % frmCycleMax, 0, frmCycleMax, 0.0, 1.0);
          
        // animate calculation
        for (int clusterCnt = 0; clusterCnt < clusterMax; clusterCnt++) {
          float waveRatio = easing.get((cycleCnt + clusterCnt) % easeMax).ease(frmRatio);
          Cluster cluster = clusters.get(clusterCnt);
          for (int idx = cluster.nodeFrom; idx < cluster.nodeTo; idx++) {
            nodes.get(idx).animate(waveRatio, frmRatio, cluster.x, cluster.y);
          }
        }
      
        blendMode(BLEND);
        background(0.0, 0.0, 0.0, 100.0);
          
        // draw
        blendMode(ADD);
        for (int clusterCnt = 0; clusterCnt < clusterMax; clusterCnt++) {
    
          float clusterRatio = map(clusterCnt, 0, clusterMax, 0.0, 1.0);
          float rangeWave = abs(sin(PI * (frmRatio + clusterRatio)));
    
          // lines between clusters
          Cluster cluster = clusters.get(clusterCnt);
          stroke(cluster.hueVal % 360.0, 90.0, 30.0, 100.0 * rangeWave);
          for (Cluster c : clusters) {
            float d = dist(cluster.x, cluster.y, c.x, c.y);
            strokeWeight(d / orbitBase);
            if (d < orbitBase * 1.5) {
              line(cluster.x, cluster.y, c.x, c.y);
            }
          }
    
          for (int i = cluster.nodeFrom; i < cluster.nodeTo; i++) {
            Node n = nodes.get(i);
    
            // nodes
            noStroke();
            fill(cluster.hueVal % 360.0, 80.0, 70.0, 100.0);
            ellipse(n.x, n.y, 3.0, 3.0);
    
            // lines between nodes
            for (int j = i + 1; j < nodeMax; j++) {
              Node m = nodes.get(j);
              float d = dist(n.x, n.y, m.x, m.y);
              if (d < rangeL * rangeWave && d > rangeS * rangeWave) {
                stroke(cluster.hueVal % 360.0, 80.0, 40.0, 100.0);
                strokeWeight(2);
                line(n.x, n.y, m.x, m.y);
    
                noStroke();
                fill((cluster.hueVal + 300.0) % 360.0, 40.0, 10.0, 100.0);
                ellipse(n.x, n.y, 8.0, 8.0);
                fill((cluster.hueVal + 30.0 + 60.0 * (d - rangeS) / (rangeL - rangeS)) % 360.0, 40.0, 3.0, 100.0);
                ellipse(m.x, m.y, 15.0, 15.0);
              }
            }
          }
        }
        saveFrame("frames/" + String.format("%04d", frmCnt) + ".png");
      }
      exit();
    }
    
    
    /**
     * getClusters : returns whole clusters.
     */
    ArrayList<Cluster> getClusters(int _nodeNum, float _radius) {
      int   tryMax  = 100;
      float spacing = _radius * 0.75;
      float hueBase = random(360.0);
      ArrayList<Cluster> clusters = new ArrayList<Cluster>();
    
      // circle packing
      int cnt   = 0;
      for (int i = 0; i < tryMax; i++) {
        int x = floor(random(spacing, width) - spacing * 0.5);
        int y = floor(random(spacing, height) - spacing * 0.5);
    
        boolean hit = false;
        for (Cluster c : clusters) {
          float d = dist(x, y, c.x, c.y);
          if (d < spacing) {
            hit = true;
            break;
          }
        }
    
        if (!hit) {
          clusters.add(new Cluster(cnt++, _nodeNum, x, y, hueBase + cnt * 90.0));
        }
      }
      
      return clusters;
    }
    
    
    /**
     * getNodes : returns whole nodes.
     */
    ArrayList<Node> getNodes(int _cnt, float _radius) {
      ArrayList<Node> nodes = new ArrayList<Node>();
      for (int i = 0; i < _cnt; i++) {
        float r = random(_radius);
        float t = random(TWO_PI);
    		nodes.add(new Node(
                           r,
                           t
                           ));    
      }
      return nodes;
    }
    
    
    /**
     * Node : hold node.
     */
    public class Node {
    
      public  float x, y;   // coordinate of node
      private float r, t;   // radius and theta to calculate the x, y
      private float oR, oT; // original radius and theta
      private float tPhase; // random phase of theta
    
      Node(float _oR, float _oT) {
        oR = _oR;
        oT = _oT;
        tPhase = random(PI);
      }
    
      public void animate(float _rRatio, float _tRatio, float _oX, float _oY) {
        r = oR * abs(sin(TWO_PI * _rRatio + tPhase));
        t = oT + TWO_PI * ((_tRatio + sin(tPhase)) % 1.0);
        x = _oX + r * cos(t);
        y = _oY + r * sin(t);
      }
    
    }
    
    /**
     * Cluster : hold cluster.
     */
    public class Cluster {
    
      public int   nodeFrom, nodeTo; // number of nodes belong to the cluster
      public float x, y;   // coordinate of node
      public float hueVal; // hue value of node
    
      Cluster(int _no, int _nodeNum, float _x, float _y, float _hue) {
        nodeFrom = _no * _nodeNum;
        nodeTo = nodeFrom + _nodeNum - 1;
        x = _x;
        y = _y;
        hueVal = _hue;
      }
    }
    
    /**
     * Ease : hold easing functions.
     */
    public interface Ease {
      public float ease(float _t);
    }
    
    public class Cos implements Ease {
      public float ease(float _t) {
        return 1.0 - cos(HALF_PI * _t);
      }
    }
    
    public class Quadratic implements Ease {
      public float ease(float _t) {
        _t *= 2.0;
        if (_t < 1.0) {
          return pow(_t, 2) / 2.0;
        }
        _t -= 1.0;
        return -(_t * (_t - 2) - 1.0) / 2.0;
      }
    }
    
    public class Cubic implements Ease {
      public float ease(float _t) {
        _t *= 2.0;
        if (_t < 1.0) {
          return pow(_t, 3) / 2.0;
        }
        _t -= 2.0;
        return (pow(_t, 3) + 2.0) / 2.0;
      }
    }
    
    public class Four implements Ease {
      public float ease(float _t) {
        return 1.0 - pow(1.0 - _t, 4);
      }
    }
    
    public class Pow implements Ease {
      public float ease(float _t) {
        return pow(_t, 2);
      }
    }
    
    /*
    Copyright (C) 2022- deconbatch
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
    */
    
    
    

     

    Next Post Previous Post
    No Comment
    Add Comment
    comment url