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

Posted

技术标签:

【中文标题】如何在 webgl 中使用 uv 纹理包裹空间以制作黑洞【英文标题】:How to wrap space to make a black hole using uv textures in webgl 【发布时间】:2019-08-22 18:22:01 【问题描述】:

我渲染了一个网格纹理。我想在片段着色器中操纵 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>

【问题讨论】:

还有为什么纹理会翻转? 不确定是否有帮助我前段时间创建了一个着色器,我把它留在这里shadertoy.com/view/MlByzd 【参考方案1】:

[...] 在片段着色器中制作黑洞效果,即当线条接近中心时,线条之间的间隙会变得更远。

您必须执行(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);

【讨论】:

实际上我希望线条循环。也就是说,当线条到中心的距离相等时,间隙应该相同。这样做的问题是,角落与边缘的间隙相同,但我认为角落离中心更远, 您能否分享您的信息来源,例如您如何知道导致这种情况的原因。 @eguneys 对不起,但来源是我的大脑。那只是数学。我知道这样的事情,因为坐标转换和 glsl 是我日常工作的一部分。 @eguneys 您可能对Issue getting gradient square in glsl es 2.0, Gamemaker Studio 2.0 或Coloring rectangle in function of distance to nearest edge produces weird result in diagonals 的答案感兴趣。 你将如何控制失真的影响,就像我将影响设置为零将没有效果,如果我将其设置为一,效果将是可见的,介于两者之间的一切都会被插值

以上是关于如何在 webgl 中使用 uv 纹理包裹空间以制作黑洞的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Three.js(WebGL)贴图(纹理映射)和UV映射

WebGL 纹理问题(奇怪的图形故障和映射问题)

SphereBufferGeometry 上的 THREE.js ShaderMaterial UV 包裹问题

如何在 webgl2 中使用深度纹理

WebGL 理论基础 - 图像处理 上

如何将 UV 纹理从搅拌机加载到 opengl android java