glslantialiasingwebgl2visual-glitch

White Pixels In What Looks Like Antialiasing Glitch


I see some white pixels around the border of the red shape in Firefox:

Here's a zoom of the area near the R.

What I expect

good

What I get

enter image description here

But the fragment shader code basically just checks some condition, writes a red pixel if true and writes information from the background image if false. There is no antialiasing implemented. So I wonder, where do those artefacts come from?

twgl.setDefaults({ attribPrefix: "a_" });
const bdrSz = 50;
const cntrl = new dat.GUI();
const glCtx = twgl.getContext(document.createElement("canvas"));
const imgLc =
  "https://upload.wikimedia.org/wikipedia/commons/8/85/BlackRock_wordmark.svg";
// const imgLc =
//   "https://upload.wikimedia.org/wikipedia/commons/8/8e/Panasonic_logo_%28Blue%29.svg";
const pmtrs = { ir: 0.3, or: 0.4, rt: 2, br: 0.1, ri: 10 };
const width = 1200;
let buffr;
let dmnsn;
let pgInf = {
  blr: twgl.createProgramInfo(glCtx, ["vs", "fs_blur"]),
  wrp: twgl.createProgramInfo(glCtx, ["vs", "fs_warp"])
};
let qBInf = twgl.primitives.createXYQuadBufferInfo(glCtx);
let imgTx;
function nrmlz(arr) {
  let sum = arr.reduce((v, w) => v + w[0] * w[1], 0);
  return arr.map((v) => v[0] / sum);
}
function rendr(fbi, pi, u) {
  twgl.bindFramebufferInfo(glCtx, fbi);
  twgl.drawObjectList(glCtx, [
    {
      programInfo: pi,
      bufferInfo: qBInf,
      uniforms: u
    }
  ]);
}
function prepr() {
  let img = new Image();
  img.crossOrigin = "Anonymous";
  img.src = imgLc;
  img.onload = function () {
    let ctx = document.createElement("canvas").getContext("2d");
    ctx.canvas.width = glCtx.canvas.width = width;
    ctx.canvas.height = glCtx.canvas.height =
      (img.height * (width - 2 * bdrSz)) / img.width + 2 * bdrSz;
    buffr = twgl.createFramebufferInfo(
      glCtx,
      [
        {
          internalFormat: glCtx.R32F
        }
      ],
      ctx.canvas.width,
      ctx.canvas.height
    );
    document.body.append(glCtx.canvas);
    ctx.drawImage(
      img,
      bdrSz,
      bdrSz,
      ctx.canvas.width - 2 * bdrSz,
      ctx.canvas.height - 2 * bdrSz
    );
    imgTx = twgl.createTexture(glCtx, { flipY: 1, src: ctx.canvas });
    compt();
  };
}
function compt() {
  let bra = Math.max(2, Math.floor(pmtrs.br * glCtx.canvas.height));
  let knl = twgl.createTexture(glCtx, {
    height: bra,
    internalFormat: glCtx.R32F,
    src: nrmlz(
      Array(bra)
        .fill()
        .map((_, i) =>
          Array(bra)
            .fill()
            .map((_, j) => [
              Math.exp(-((i / bra) ** 2 + (j / bra) ** 2)),
              i == 0 && j == 0 ? 1 : i == j || i == 0 || j == 0 ? 4 : 8
            ])
        )
        .flat()
    ),
    width: bra
  });
  rendr(buffr, pgInf.blr, {
    u_kernel: knl,
    u_radius: bra,
    u_resolution: [glCtx.canvas.width, glCtx.canvas.height],
    u_texture: imgTx
  });
  finsh();
}
function finsh() {
  rendr(null, pgInf.wrp, {
    u_blurred: buffr.attachments[0],
    u_original: imgTx,
    u_innerRadius: pmtrs.ir,
    u_outerRadius: pmtrs.or,
    u_ratio: pmtrs.rt,
    u_refIndex: pmtrs.ri,
    u_resolution: [glCtx.canvas.width, glCtx.canvas.height]
  });
}
cntrl.add(pmtrs, "ir", 0, 1).onChange(finsh);
cntrl.add(pmtrs, "or", 0, 1).onChange(finsh);
cntrl.add(pmtrs, "rt", 0, 3).onChange(finsh);
cntrl.add(pmtrs, "br", 0, 0.1).onChange(compt);
cntrl.add(pmtrs, "ri", -10, 10).onChange(finsh);
prepr();
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script id="vs" type="x-shader/x-vertex">
  #version 300 es
  in vec4 a_position;
  in vec2 a_texcoord;
    
  out vec2 v_texcoord;

  void main() {
    v_texcoord = a_texcoord;
    gl_Position = a_position;
  }
</script>
<script id="fs_blur" type="x-shader/x-fragment">
  #version 300 es
  precision highp float;

  in vec2 v_texcoord;

  uniform sampler2D u_kernel;
  uniform int u_radius;
  uniform vec2 u_resolution;
  uniform sampler2D u_texture;

  out vec4 f_color;

  void main() {
    float sum = 0.0;
    for(int i = -u_radius + 1; i < u_radius; i++) {
      for(int j = -u_radius + 1; j < u_radius; j++) {
        sum += texture(u_kernel, vec2(abs(i), abs(j)) / float(u_radius)).r *
          texture(u_texture, v_texcoord + vec2(i,j) / u_resolution).a;
      }
    }
    f_color = vec4(sum, 1.0, 1.0, 1.0);
  }
</script> 
<script id="fs_warp" type="x-shader/x-fragment">
  #version 300 es
  precision highp float;

  in vec2 v_texcoord;

  uniform sampler2D u_blurred;
  uniform sampler2D u_original;
  uniform float u_innerRadius;
  uniform float u_outerRadius;
  uniform float u_ratio;
  uniform float u_refIndex;
  uniform vec2 u_resolution;

  out vec4 f_color;

  void main() {
    vec2 ref = v_texcoord + u_refIndex * (
      texture(u_blurred, v_texcoord + vec2(0, 1) / u_resolution).r -
      texture(u_blurred, v_texcoord + vec2(0, -1) / u_resolution).r +
      texture(u_blurred, v_texcoord + vec2(1, 0) / u_resolution).r -
      texture(u_blurred, v_texcoord + vec2(-1, 0) / u_resolution).r
    );
    float len = length(2.0 * (ref - 0.5) / vec2(u_ratio, 1));
    if(u_innerRadius < len && len < u_outerRadius) {
      f_color = vec4(1.0, 0.0, 0.0, 1.0);
    } else {
      float tf = texture(u_original, v_texcoord).a;
      f_color = vec4(1.0 - tf, 1.0 - tf, 1.0 - tf, 1.0);
    }
  }
</script> 


Solution

  • So AFAIK the issue is that you can't use texture lookups in conditionals or they'll break your varyings

    From the GLSL ES 1.0 spec appendix A

    6 Texture Accesses

    Accessing mip-mapped textures within the body of a non-uniform conditional block gives an undefined value. A non-uniform conditional block is a block whose execution cannot be determined at compile time

    And the GLSL ES 3.0 spec section 8.8

    Some texture functions (non-“Lod” and non-“Grad” versions) may require implicit derivatives. Implicit derivatives are undefined within non-uniform control flow and for vertex texture fetches.

    In other words, you need to move this line in your fs_warp shader

    float tf = texture(u_original, v_texcoord).a;
    

    outside of the if statement.

    Unfortunately, whether it gets bad results is GPU dependent so if you're unlucky you won't run into the issue but some of your users will. Fortunately you ran into the issue and so you can fix it.