0

I render a grid texture. I want to manipulate uv coordinates(vQuadCoord) in fragment shader to make the black hole effect, that is make the gaps between lines go further as it approaches to the center. also with a circular effect

I think this would be possible, since if I do vQuadCoord = vQuadCoord * vQuadCoord it achieves a similar effect but in the corners.

const fShaderSource = `#version 300 es

precision mediump float;

out vec4 outColor;

uniform sampler2D u_texture;

in vec2 vQuadCoord;

void main() {
  outColor = texture(u_texture, vQuadCoord);
}

`;

const vShaderSource = `#version 300 es

precision mediump float;

in vec2 a_position;

out vec2 vQuadCoord;

void main() {
  vQuadCoord = (a_position + 1.0) / 2.0;

  gl_Position = vec4(a_position, 0, 1);
}
`;

main(document.getElementById('app'));

function main(element) {
  
  const canvas = document.createElement('canvas'),
        gl = canvas.getContext('webgl2');
  element.append(canvas);
  const displayWidth = canvas.clientWidth,
        displayHeight = canvas.clientHeight;
  canvas.width = displayWidth;
  canvas.height = displayHeight;


  let graphics = new Graphics({width: displayWidth, height: displayHeight}, gl);
  
  new Loop(() => {
     graphics.render();
  }).start();
}

function Graphics(state, gl) {

  const { width, height } = state;

  gl.clearColor(0, 0, 0, 0);
 
 
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  //gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
  gl.enable(gl.BLEND);
  gl.disable(gl.DEPTH_TEST);

  
  
  let minibatch = [];
  
  const redText = makeGlQuad(gl, fShaderSource, canvasTexture());

  this.render = () => {  

    minibatch.push(redText);
    
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT);

    minibatch.forEach(({
      program,
      resUniformLocation,
      vao,
      glTexture
    }) => {

    gl.useProgram(program);

    gl.uniform2f(resUniformLocation, gl.canvas.width, gl.canvas.height);
    
    if (glTexture) {
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, glTexture);
    }
    
    gl.bindVertexArray(vao);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  });
  minibatch = [];
  };

}

function makeGlQuad(gl, fShaderSource, texture) {

  let vShader = createShader(gl, gl.VERTEX_SHADER, vShaderSource);
  let fShader = createShader(gl, gl.FRAGMENT_SHADER, fShaderSource);

  let program = createProgram(gl, vShader, fShader);

  let posAttrLocation = gl.getAttribLocation(program, "a_position");
  let posBuffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);


  let left = -1,
      right = 1,
      down = -1,
      up = 1;

  /*
    (-1, 1).( 1, 1)
        .
    (-1,-1).( 1,-1)
   */
  let positions = [
      left, down,
      left, up,
      right, down,
      left, up,
      right, down,
      right, up

  ];

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);


  let vao = gl.createVertexArray();
  gl.bindVertexArray(vao);

  gl.enableVertexAttribArray(posAttrLocation);

  let size = 2,
      type = gl.FLOAT,
      normalize = false,
      stride = 0,
      offset = 0;

  gl.vertexAttribPointer(posAttrLocation,
                         size,
                         type,
                         normalize,
                         stride,
                         offset);

  let glTexture;
  if (texture) {
    glTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, glTexture);
  
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture);
    //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));


    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  }



  let resUniformLocation = gl.getUniformLocation(program, "u_resolution");
  let texUniformLocation = gl.getUniformLocation(program, "u_texture");
 
 
  return {
   program,
   resUniformLocation,
   vao,
   glTexture
  }
}

function canvasTexture() {
 
 return withCanvasTexture(256, 256, (w, h, canvas, ctx) => {
  const gap = w * 0.07;

  ctx.fillStyle = 'green';
  ctx.fillRect(0, 0, 10, 10);

  ctx.strokeStyle = 'red';
  ctx.lineWidth = 1;
  ctx.beginPath();

  for (let i = 0; i < w; i+= gap) {
    ctx.moveTo(i, 0);
    ctx.lineTo(i, h);
  }
  for (let i = 0; i < h; i+= gap) {
    ctx.moveTo(0, i);
    ctx.lineTo(w, i);
  }
  ctx.stroke();

  return canvas;

 });
 
function withCanvasTexture(width, height, f) {
  var canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  f(width, height, canvas, canvas.getContext('2d'));
  const texture = canvas;
  document.body.append(canvas);
  return texture;
}
}

function createShader(gl, type, source) {
  let shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);

  if (success) {
    return shader;
  }

  console.error(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
  return null;
};

function createProgram(gl, vShader, fShader) {
  let program = gl.createProgram();
  gl.attachShader(program, vShader);
  gl.attachShader(program, fShader);
  gl.linkProgram(program);
  let success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.error(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
  return null;
}


// Loop Library
function Loop(fn) {

const perf = window.performance !== undefined ? window.performance : Date;

const now = () => perf.now();

const raf = window.requestAnimationFrame;

  let running = false,
      lastUpdate = now(),
      frame = 0;

  this.start = () => {
    if (running) {
      return this;
    }

    running = true;
    lastUpdate = now();
    frame = raf(tick);
    return this;
  };

  this.stop = () => {
    running = false;

    if (frame != 0) {
      raf.cancel(frame);
    }

    frame = 0;
    return this;
  };

  const tick = () => {
    frame = raf(tick);
    const time = now();
    const dt = time - lastUpdate;
    fn(dt);
    lastUpdate = time;
  };
}
#app canvas {
  background: #ccc;
  position: fixed;
  top: 50%;
  bottom: 0;
  left: 50%;
  right: 0;

  width: 100vmin;
  height: 70vmin;

  transform: translate(-50%, -25%);

  image-rendering: optimizeSpeed;
  cursor: none;
  margin: auto;
}
<div id="app">
</div>
eguneys
  • 6,028
  • 7
  • 31
  • 63

1 Answers1

0

[...] in fragment shader to make the black hole effect, that is make the gaps between lines go further as it approaches to the center.

You've to do something like (1.0 - (1.0 - abs(x)) * (1.0 - abs(x))). x is a coordinate, where (0,0) is in the center of the texture.

Convert the texture coordinates from the range [0, 1] to the range [-1, 1]:

vec2 p  = vQuadCoord * 2.0 - 1.0;

Calculate the "black hole effect" coordinate:

p = sign(p) * (1.0 - (1.0 - abs(p)) * (1.0 - abs(p)));

Convert back from the range [-1, 1] to [0, 1]:

vec2 uv = p * 0.5 + 0.5;

For a circular effect you've to multiply the normalized direction vector by a factor which depends on the square distance to the center or distance to the border:

p =  normalize(p) * length(p) * length(p);

or

p = normalize(p) * (1.0 - (1.0 - length(p)) * (1.0 - length(p))) 

Fragment shader:

precision mediump float;

out vec4 outColor;

uniform sampler2D u_texture;

in vec2 vQuadCoord;

void main() {
    vec2 p  = vQuadCoord * 2.0 - 1.0;

    //p = sign(p) * (1.0 - (1.0 - abs(p)) * (1.0 - abs(p)));
    //p = normalize(p) * (1.0 - (1.0 - length(p)) * (1.0 - length(p)));
    p =  normalize(p) * length(p) * length(p);

    vec2 uv = p * 0.5 + 0.5;
    outColor = texture(u_texture, uv);
}

To y-flip the texture you can set the UNPACK_FLIP_Y_WEBGL flag. See WebGL 2.0, 5.14.8 Texture objects:

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture);
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • actually I wanted lines to go circular. that is the gaps should be the same where the lines distance to the center is equal. the problem with this is, the corners are the same gap as the edges, but corners are further away from the center I think, – eguneys Aug 22 '19 at 18:59
  • could you share the source of your information like how do you know that causes that. – eguneys Aug 22 '19 at 19:16
  • @eguneys Sorry, but the source is my brain. That's just math. I know things like this because coordinate transformations and glsl is part of my every day work. – Rabbid76 Aug 22 '19 at 19:40
  • @eguneys Possibly you're interested in the answers to [Issue getting gradient square in glsl es 2.0, Gamemaker Studio 2.0](https://stackoverflow.com/questions/47417916/issue-getting-gradient-square-in-glsl-es-2-0-gamemaker-studio-2-0/47418190#47418190) or [Coloring rectangle in function of distance to nearest edge produces weird result in diagonals](https://stackoverflow.com/questions/48792209/coloring-rectangle-in-function-of-distance-to-nearest-edge-produces-weird-result/48793357#48793357). – Rabbid76 Aug 22 '19 at 19:40
  • how would you control the impact of the distortion, like if I set the impact to zero there will be no effect, and if i set it to one, the effect will be visible, everything in between will be interpolated – eguneys Aug 23 '19 at 06:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/198351/discussion-between-eguneys-and-rabbid76). – eguneys Aug 23 '19 at 06:54