如何在webgl中使用uv纹理包装空间以制作黑洞

时间:2019-08-22 18:22:01

标签: javascript css canvas glsl webgl

我渲染网格纹理。我想在片段着色器中操纵uv坐标(vQuadCoord)来产生黑洞效果,即使线之间的间隙在接近中心时更远。也有圆形效果

我认为这是有可能的,因为如果我vQuadCoord = vQuadCoord * vQuadCoord这样做会达到类似的效果,但是在角落。

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>

1 个答案:

答案 0 :(得分:0)

  

[...]在片段着色器中产生黑洞效果,即使线之间的间隙在靠近中心时更远。

您必须执行类似(1.0 - (1.0 - abs(x)) * (1.0 - abs(x)))的操作。 x是一个坐标,其中(0,0)在纹理的中心。

将纹理坐标从[0,1]范围转换为[-1,1]范围:

vec2 p  = vQuadCoord * 2.0 - 1.0;

计算“黑洞效应”坐标:

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

从[-1,1]范围转换回[0,1]:

vec2 uv = p * 0.5 + 0.5;

对于圆形效果,您必须将归一化方向向量乘以一个系数,该系数取决于到中心的平方距离或到边界的距离:

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

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

片段着色器:

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);
}

要y翻转纹理,可以设置UNPACK_FLIP_Y_WEBGL标志。参见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);