cansik / processing-postfx

A shader based postFX library for processing.
148 stars 13 forks source link

Transparency or Blendmode or Alpha Issue? #38

Closed Skuzee closed 1 month ago

Skuzee commented 1 month ago

Hello! Sorry if this is something I'm doing wrong. I see this is somewhat of an older library.

I am trying to use this library to configure and run my own shaders. When I apply my shaders with the usual filter(myShader), the transparency and color blending look correct. When I use the PostFXSupervisor and use fx.pass(myShaderObject); then compose it, the blending of the edges of my smoothstep are colored black/gray. I know this might be a big ask, but any insight you can lend me would be amazing! I can't tell if I'm using the library wrong. 💛

results with library
image

expected results image

my pde sketch:

// Shader Framework
// imports ------------------------------------------------------------------------* imports 
import ch.bildspur.postfx.builder.*;
import ch.bildspur.postfx.pass.*;
import ch.bildspur.postfx.*;

// globals ----------------------------------------------------------------------* globals
final int WINDOW_W = 720;
final int WINDOW_H = 720;

PGraphics canvas;
PostFXSupervisor fx;
shCircleMask shCircleMask;
PShader myCircleMaskShader;
int method;

// settings ---------------------------------------------------------------------* settings
void settings() {
  size(WINDOW_W, WINDOW_H, P2D);
}

// setup ------------------------------------------------------------------------* setup
void setup() {
  // init --------------
  canvas = createGraphics(width, height, P2D);
  fx = new PostFXSupervisor(this);
  shCircleMask = new shCircleMask(0.0, 0.0, 0.5); // circle at 0,0 of size 0.5
  myCircleMaskShader = loadShader("shCircleMask.glsl");
  method=0;
}

// draw -------------------------------------------------------------------------* draw
void draw() {
  method=millis()/1000%2;

  // color the background blue, then color the canvas layer pink,
  // then use a smoothstep to control the pink canvas's alpha channel
  // the output *should* just be a pink circle that fades to blue...

  if (method == 0) { // this way works as expected
    myCircleMaskShader.set("circleOrigin", 0.0, 0.0);
    myCircleMaskShader.set("circleRadius", 0.5);

    background(200, 200, 255, 255); // light blue

    canvas.beginDraw();
    canvas.clear();
    canvas.background(255, 200, 200, 255); // pink
    canvas.filter(myCircleMaskShader);
    canvas.endDraw();
    image(canvas, 0, 0);
  } 

  else if (method==1) { // this way, the transition between the colors is greyscale
    background(200, 200, 255, 255); // light blue
    canvas.beginDraw();
    canvas.clear();
    canvas.background(255, 200, 200, 255); // pink
    canvas.endDraw();

    fx.render(canvas);
    fx.pass(shCircleMask);
    fx.compose(canvas);
    image(canvas, 0, 0);
  }
}

my shader class:

class shCircleMask implements Pass
{
  String ShaderName = "shCircleMask.glsl";
  PShader shader;
  float[] circleOrigin = {0.0,0.0};
  float circleRadius = 0.5;

  public shCircleMask()
  {
    println(ShaderName + " base constructor called");
    shader = loadShader(ShaderName);
  }

  public shCircleMask(float inputX, float inputY, float inputR)
  {
    println(ShaderName + " constructor called with values: Origin: " + inputX + "," + inputY +  "; Radius: " + inputR);
    shader = loadShader(ShaderName);
    circleOrigin[0] = inputX;
    circleOrigin[1] = inputY;
    circleRadius = inputR;
  }

  @Override
  public void prepare(Supervisor supervisor) {
    shader.set("resolution", supervisor.getResolution());
    shader.set("circleOrigin", circleOrigin);
    shader.set("circleRadius", circleRadius);
   }

  public void setRadius(float inputR) {
    circleRadius = inputR;
  }

  public void setOrigin(float inputX, float inputY) {
    circleOrigin[0] = inputX;
    circleOrigin[1] = inputY;
  }

  @Override
  public void apply(Supervisor supervisor) {
    PGraphics pass = supervisor.getNextPass();
    supervisor.clearPass(pass);

    pass.beginDraw();
    pass.shader(shader);
    pass.image(supervisor.getCurrentPass(), 0, 0);
    pass.endDraw();
  }
}

This is my shader. It just uses a smoothstep as an SDF to fade the original texture's aplha to 0 (transparent)

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

varying vec4 vertColor;

uniform vec2 resolution;
uniform sampler2D texture;
uniform float[2] circleOrigin;
uniform float circleRadius;

float makeCircle(vec2 inputPos, vec2 inputCenter, float inputSize) {

    inputCenter+= vec2(0.5);
    float dist = length(inputPos-inputCenter) - inputSize/2;

    return 1.0-smoothstep(0.0,0.1, dist);
}

void main() {
    vec2 position = ( gl_FragCoord.xy / resolution.xy );

    vec4 color = vec4(1.0);

    float circColor = makeCircle(position, vec2(circleOrigin[0],circleOrigin[1]), circleRadius);
    color = vec4(circColor);

    gl_FragColor = vec4(texture2D(texture,position).rgb, color.w);
}
Skuzee commented 1 month ago

I was able to get it partially working. I changed my apply definition to use filter() instead of shader() and it works correctly if I compose directly to the main sketch window. However, composing to a PGraphics canvas, then displaying that with image(), I get the same black halo.

I'm starting to think it might actually be a PGraphics issue and have something to do with PGraphics' default state being non-transparent black. (i.e. using clear())
Even with avoiding clear() and trying to force the canvas background to transparent, or to white I haven't seen any improvement. Below is my updated apply() and draw():

  @Override
  public void apply(Supervisor supervisor) {
    PGraphics pass = supervisor.getNextPass();
    supervisor.clearPass(pass);

    pass.beginDraw();
    pass.background(255);
    pass.image(supervisor.getCurrentPass(), 0, 0);
    pass.endDraw();

    pass.filter(shader);
  }
  void draw() {

    background(200, 200, 255, 255); // light blue

    canvas.beginDraw();
    canvas.background(255, 200, 200, 255); // pink
    canvas.endDraw();

    fx.render(canvas);
   // fx.compose(canvas); oops ignore this. 
    fx.pass(shCircleMask);

    fx.compose(); // compose to screen works

    fx.compose(canvas); // composing to a canvas, then displaying it does not.
    image(canvas, width/2, height/2,width/2, height/2);
}

image

Skuzee commented 1 month ago

I think I found my problem.

from http://processing.github.io/processing-javadocs/core/ under createGraphics

the main drawing surface which is completely opaque, surfaces created with createGraphics() can have transparency. This makes it possible to draw into a graphics and maintain the alpha channel. By using save() to write a PNG or TGA file, the transparency of the graphics object will be honored. Note that transparency levels are binary: pixels are either complete opaque or transparent. For the time being, this means that text characters will be opaque blocks. This will be fixed in a future release (Issue 80). ( end auto-generated )

I guess this mean I can't do gradual transparencies with PGraphics. It's strange that the main window will still respect the alpha channel of the colors when blending, but it seems like drawing to a graphic does not maintain the alpha of the pixel color data? (from what I see so far.) Perhaps I can set it directly.

I seem to have found my answer, although it's not exactly a solution! 😠😓 Have a great day!