Creating a light source in 2D world

Hi. I’m trying to create a light source in 2D. As an example, take a game character in 2D world that is holding a torch in a dark cave and there’s some area around him that’s fully lit up and then it gets darker (as the radius gets larger, until pitch black).

I tried using mask() but with no success. I’m also completely new to shaders and thought that I’d give them a try. I made a shader that does exactly what I described above: the dark circle you see has color alpha value of 0.0 and as the radius increases, the alpha increases as well. I thought this would work since these alphas are 0, hence it would draw over something else. But I was wrong! Once again, completely new to shaders :stuck_out_tongue:


What worked though, is executing shader() on separate PGraphics and then using get() to retrieve PImage. Then I used it as a mask on another image, which was just a simple red color image. But surely I’m not going to use separate PGraphics over and over since I notice big jumps in GPU and CPU (not that much) usage on my task manager.

So that’s my question: can I do this with shaders? In a way that my shader would just run over anything that’s on the screen but would not completely draw over it (transparent shader?). If so, I’m all ears and eyes. I was also thinking of just loading an image with transparent cut out circle and drawing extra black rectangles around it but idk… doesn’t seem ideal :smiley:

1 Like

Maybe something like this? (use image below)

PImage img; 

void setup() { 
  size(697, 462); // Note that the size is the same as image
  img = loadImage("treasure.png");
  noCursor();
  noFill();
}                                  
void draw() { 
  background(255);
  image(img, 0, 0);
  loadPixels(); 
  img.loadPixels(); 
  for (int y = 0; y < height; y++ ) { 
    for (int x = 0; x < width; x++ ) { 
      int loc = x+y*width;  
      float r = red(img.pixels [loc]); 
      float g = green(img.pixels[loc]); 
      float b = blue(img.pixels[loc]);
      float d = dist(mouseX, mouseY, x, y);
      float factor = map(d/2, 0, 200, 2, 0);      
      pixels[loc] = color(r*factor, g*factor, b*factor);
    }
  }
  updatePixels();
}

1 Like

Seems like a great solution! Haven’t thought of using pixels since I’ve never worked with them before, just read about the functions. But it does achieve what I was aiming for! Thanks a bunch! :slight_smile:

Although I marked this as solved, I didn’t do much testing first. While the solution is alright, looping through each pixel is really power consuming and makes the sketch drop frames.

Which is why I wanted to improve it and use a limited area only to handle pixels.

I ended up creating an image using PGraphics and mask() with a transparent circle in the middle (given some radius) and some extra area surrounding that circle, given the parameter again.

This is so that I can use pixels[] array and check the alpha of each pixel only for this given image. If the pixel turns out to have alpha higher than 0, I deal with it.

In the end, the final result looks like this (background source):


I’m also using 4 black rectangles to fill the rest of the background with darkness. I really like the way this turned out!

Sketch code:

int radius = 75;
int black = radius * 5;
float d_multi = 1 / (float(radius)/100);

PImage img;
PImage mask;

PImage bg;
BGRect[] rects;

void setup() { 
  size(1380, 780, P2D);
  
  bg = loadImage("bg.jpg");
  bg.resize(width, height);
  rects = new BGRect[] { 
    new BGRect(), new BGRect(), new BGRect(), new BGRect()
  };
  
  PGraphics pg = createGraphics(black, black);
  pg.beginDraw();
  pg.background(0, 0, 255); // Blue background does magic :)
  pg.strokeWeight(2);
  pg.stroke(255);
  pg.translate(pg.width/2, pg.height/2);
  pg.fill(0);
  pg.circle(0, 0, radius*2);
  pg.endDraw();
  mask = pg.get();
  
  pg.beginDraw();
  pg.background(0, 0, 255);
  pg.noStroke();
  pg.translate(pg.width/2, pg.height/2);
  pg.fill(0);
  pg.rect(-pg.width/2, -pg.height/2, pg.width, pg.height);
  pg.endDraw();
  img = pg.copy();
  
  pg.clear();
  img.mask(mask);
}                                  
void draw() { 
  background(170, 200, 100);
  
  // Side rects:
  rects[0].x = 0; rects[0].y = 0;
  rects[0].w = mouseX - black/2; rects[0].h = height;
  
  rects[1].x = mouseX + black/2; rects[1].y = 0;
  rects[1].w = width - rects[1].x; rects[1].h = height;
  
  // Top and down rects:
  rects[2].x = mouseX - black/2; rects[2].y = 0;
  rects[2].w = black; rects[2].h = mouseY - black/2;
  
  rects[3].x = mouseX - black/2; rects[3].y = mouseY + black/2;
  rects[3].w = black; rects[3].h = height - rects[3].y;
  
  image(bg, 0, 0);
  image(img, mouseX - black/2, mouseY - black/2);
  
  img.loadPixels();
  for(int x = 0; x < img.width; x++) {
    for(int y = 0; y < img.height; y++) {
      int loc = x + y * img.width;
      float alpha = alpha(img.pixels[loc]);
      float d = dist(mouseX, mouseY, mouseX - img.width/2 + x, mouseY - img.height/2 + y);      
      if(alpha > 0) {
        float r = red(img.pixels[loc]); 
        float g = green(img.pixels[loc]); 
        float b = blue(img.pixels[loc]);
        img.pixels[loc] = color(r, g, b, d * d_multi);
      }
    }
  }
  img.updatePixels();
  
  for(int i = 0; i < rects.length; i++) {
    rects[i].show();
  }
}

void mouseWheel(MouseEvent e) {
  if(e.getCount() == -1) {
    if(d_multi > 1 / (float(radius)/100)) d_multi -= 0.05;
  } else {
    d_multi += 0.05;
  }
}

…and BGRect class:

class BGRect {
  
  float x, y, w, h;
  
  void show() {
    pushMatrix();
    noStroke();
    fill(0);
    rect(x, y, w, h);
    popMatrix();
  }
}
1 Like