如何通过 2D 前景穿透或切孔

How to penetrate or cut holes through a 2D foreground

我目前正在 Javascript 中制作 2D 游戏,但我希望游戏具有不同的照明级别,例如,如果我要创建昼夜循环。但是,我希望能够在 lighting/foreground 上打孔,或者做 某事 以便我可以使屏幕的某些部分点亮,例如手电筒或蜡烛。注意:我也在使用 P5.js 库。

在创建前景时想到的最明显的想法就是创建一个覆盖整个屏幕的具有一定不透明度的矩形。这很好,但我应该如何解决这个问题?显然,下面的代码不会起作用,因为我只是在另一个元素上分层,矩形仍然被遮挡并且不是很清楚。

function setup() {
  noStroke();
  createCanvas(400, 400);
}

function draw() {
  background(255); //white
  
  fill(255, 0, 0);
  rect(200, 200, 25, 25); //Example object
  
  fill(150, 150, 150, 100); //Gray with opacity
  rect(0, 0, 400, 400); //Darkness covering entire screen, foreground
  
  fill(255, 255, 255, 100)
  ellipse(mouseX, mouseY, 50, 50); //object that is supposed to penetrate foreground.
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />

  </head>
  <body>
    <script src="sketch.js"></script>
  </body>
</html>

我该怎么做?我希望它不仅适用于简单的背景和简单的形状,而且适用于图像。有没有办法在形状上切孔,或者我需要使用其他东西,比如着色器或遮罩?

谢谢。

erase() function可能就是您要找的。它比尝试明确地在您想要覆盖的区域上绘画更灵活(例如使用圆形和矩形的描边来覆盖除圆形以外的所有内容的方法)。而且它比 beginContour() 更容易使用,因为您可以将它与任何内置绘图基元(矩形、椭圆、三角形等)一起使用。

let overlay;

function setup() {
  noStroke();
  createCanvas(windowWidth, windowHeight);
  overlay = createGraphics(width, height);
  overlay.noStroke();
  overlay.fill(255);
  overlay.background(150, 150, 150, 200);
}

function mouseMoved() {
  // Only update the overlay when something changes
  // Clear the overlay so that alpha doesn't accumulate
  overlay.clear()
  //Gray with opacity, covering entire screen, foreground
  overlay.background(150, 150, 150, 200);

  // The color and alpha values for shapes drawn when erasing basically do not matter. You can effect the % of erasure with the arguments erase.
  overlay.erase(100);
  overlay.ellipse(mouseX, mouseY, 50, 50); //object that is supposed to penetrate foreground.
  overlay.noErase();
}

function draw() {
  background(255); //white

  fill(255, 0, 0);
  rect(width / 2, height / 2, 25, 25); //Example object

  image(overlay, 0, 0, width, height);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

如果您想通过更完整的 alpha 通道支持从叠加层中移除形状,您可以使用 blendMode(ERASE)

let overlay;
let overlayCtx;

let gradient;

function setup() {
  noStroke();
  createCanvas(windowWidth, windowHeight);
  overlay = createGraphics(width, height);
  overlayCtx = overlay.drawingContext;
  overlay.noStroke();
  overlay.background(150, 150, 150, 200);

  // Using the raw canvas API to make a radial gradient
  gradient = overlayCtx.createRadialGradient(0, 0, 5, 0, 0, 25);
  // The colors here don't matter, only the alpha channel
  gradient.addColorStop(0, 'rgba(0,0,0,1)');
  gradient.addColorStop(1, 'rgba(0,0,0,0)');
  overlayCtx.fillStyle = gradient;
}

function mouseMoved() {
  // Only update the overlay when something changes
  // Clear the overlay so that alpha doesn't accumulate
  overlay.clear()
  //Gray with opacity, covering entire screen, foreground
  overlay.background(150, 150, 150, 200);

  overlay.push();
  // Use blend mode REMOVE to remove the color (using only the alpha channel?)
  overlay.blendMode(REMOVE);
  // Because of the way gradients work we have to translate and draw our ellipse at the origin.
  overlay.translate(mouseX, mouseY);
  overlay.ellipse(0, 0, 50, 50); //object that is supposed to penetrate foreground.
  overlay.blendMode(BLEND);
  overlay.pop();
}

function draw() {
  background(255); //white

  fill(255, 0, 0);
  rect(width / 2, height / 2, 25, 25); //Example object

  image(overlay, 0, 0, width, height);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>