1

以 4x4x4 网格为例,我有 64 个顶点(我称之为粒子),它们从相对于彼此的特定位置开始。这 64 个粒子将在 x、y 和 z 方向上移动,失去它们相对于彼此的初始位置。然而,每个周期,新的粒子位置和速度都需要根据粒子与其原始邻居之间的原始起始关系来计算。

我了解到我需要为此使用纹理,因此需要使用帧缓冲区,现在我可以编写两个 3DTexture 触发器来提供执行此操作的写入和读取功能。但是,在下一个循环中,当 gl_FragCoord 被传递给片段着色器时,粒子的新位置(例如可以用另一个粒子切换),我看不到任何机制可以使保存粒子的纹理的原始坐标信息将与粒子的当前信息一起写入。是否有一些我不理解的机制允许移动粒子将它们的数据存储在静态网格(3D 纹理)中,每个粒子的数据总是填充相同的坐标,所以我可以使用 texelFetch 来获取粒子的数据,以及原始邻居的数据?我可以更改 gl_FragCoord,

一旦我解决了这个问题,我希望实现一个变换反馈来执行顶点的实际移动,而不会将纹理转储到 CPU 并提取位置数据并将其重新上传到 GPU 以供下一个周期使用。

对于如何使用帧缓冲区中编写的纹理来跟踪每个粒子的原始位置、原始邻居以及相对于那些原始邻居的当前位置,是否有任何建议?

4

1 回答 1

3

我对你的困惑感到困惑

这是一个简单的纯 JavaScript 粒子系统。每个粒子从随机位置开始并沿随机方向移动

'use strict';

const ctx = document.querySelector('canvas').getContext('2d')
const {width, height} = ctx.canvas;

const numParticles = 128;
const particleParameters = [];  // info that does not change
let currentParticleState = [];  // info that does change
let nextParticleState = [];     // computed from currentState

for (let i = 0; i < numParticles; ++i) {
  particleParameters.push({
    velocity: [rand(-100, 100), rand(-100, 100)],
  });
  currentParticleState.push({
    position: [rand(0, width), rand(0, height)],
  });
  nextParticleState.push({
    position: [0, 0],
  });
}


function rand(min, max) {
  return Math.random() * (max - min) + min;
}

function euclideanModulo(n, m) {
  return (( n % m) + m) % m;
}

let then = 0;
function render(now) {
  now *= 0.001;  // convert to seconds
  const deltaTime = now - then;
  then = now;

  for (let i = 0; i < numParticles; ++i) {
    const curPos = currentParticleState[i].position;
    const nxtPos = nextParticleState[i].position;
    const data = particleParameters[i];
    
    nxtPos[0] = euclideanModulo(curPos[0] + data.velocity[0] * deltaTime, width);
    nxtPos[1] = euclideanModulo(curPos[1] + data.velocity[1] * deltaTime, height);    
  }
  
  const t = nextParticleState;
  nextParticleState = currentParticleState;
  currentParticleState = t;

  ctx.clearRect(0, 0, width, height);
  for (let i = 0; i < numParticles; ++i) {
    const [x, y] = currentParticleState[i].position;
    ctx.fillRect(x - 1, y - 1, 3, 3);
  }
  

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<canvas></canvas>

这是仍然在 JavaScript 中的相同粒子系统,但运行起来更像 WebGL 运行。我不知道这是否会或多或少令人困惑。重要的一点是更新粒子位置的代码fragmentShader不能选择它正在更新的内容。它只是更新gl.outColorgl.fragCoord除了和之外,它也没有输入gl.currentProgram.uniforms。currentParticleState 是一个由 4 个值数组组成的数组,与以前一样,它是一个具有位置属性的对象数组。particleParameters 也只是一个由 4 个值数组组成的数组,而不是一个具有速度值的对象数组。这是为了模拟这样一个事实,即这些将是真实 WebGL 中的纹理,因此任何意义都像positionvelocity丢失了。

实际绘制粒子的代码是无关紧要的。

'use strict';

const ctx = document.querySelector('canvas').getContext('2d')
const {width, height} = ctx.canvas;

const numParticles = 128;
const particleParameters = [];  // info that does not change
let currentParticleState = [];  // info that does change
let nextParticleState = [];     // computed from currentState

for (let i = 0; i < numParticles; ++i) {
  particleParameters.push(
    [rand(-100, 100), rand(-100, 100)],
  );
  currentParticleState.push(
    [rand(0, width), rand(0, height)],
  );
  nextParticleState.push(
    [0, 0],
  );
}


function rand(min, max) {
  return Math.random() * (max - min) + min;
}

function euclideanModulo(n, m) {
  return (( n % m) + m) % m;
}


const gl = {
  fragCoord: [0, 0, 0, 0],
  outColor: [0, 0, 0, 0],
  currentProgram: null,
  currentFramebuffer: null,
  
  bindFramebuffer(fb) {
    this.currentFramebuffer = fb;
  },
  
  createProgram(vs, fs) {
    return {
      vertexShader: vs,  // not using
      fragmentShader: fs,
      uniforms: {
      },
    }
  },
  
  useProgram(p) {
    this.currentProgram = p;
  },
  
  uniform(name, value) {
    this.currentProgram.uniforms[name] = value;
  },
  
  draw(count) {
    for (let i = 0; i < count; ++i) {
      this.fragCoord[0] = i + .5;
      this.currentProgram.fragmentShader();
      this.currentFramebuffer[i][0] = this.outColor[0];
      this.currentFramebuffer[i][1] = this.outColor[1];
      this.currentFramebuffer[i][2] = this.outColor[2];
      this.currentFramebuffer[i][3] = this.outColor[3];
    }
  },
};


// just to make it look more like GLSL
function texelFetch(sampler, index) {
  return sampler[index];
}

// notice this function has no inputs except
// `gl.fragCoord` and `gl.currentProgram.uniforms`
// and it just writes to `gl.outColor`. It doesn't
// get to choose where to write. That is handled
// by `gl.draw`
function fragmentShader() {
  // to make the code below more readable
  const {
    resolution, 
    deltaTime,
    currentState,
    particleParams,
  } = gl.currentProgram.uniforms;
  
  const i = Math.floor(gl.fragCoord[0]);
  const curPos = texelFetch(currentState, i);
  const data = texelFetch(particleParameters, i);
    
  gl.outColor[0] = euclideanModulo(curPos[0] + data[0] * deltaTime, resolution[0]);
  gl.outColor[1] = euclideanModulo(curPos[1] + data[1] * deltaTime, resolution[1]);
}


const prg = gl.createProgram(null, fragmentShader);

let then = 0;
function render(now) {
  now *= 0.001;  // convert to seconds
  const deltaTime = now - then;
  then = now;

  gl.bindFramebuffer(nextParticleState);
  gl.useProgram(prg);
  gl.uniform('deltaTime', deltaTime);
  gl.uniform('currentState', currentParticleState);
  gl.uniform('particleParameters', particleParameters);
  gl.uniform('resolution', [width, height]);
  gl.draw(numParticles);
  
  const t = nextParticleState;
  nextParticleState = currentParticleState;
  currentParticleState = t;

  // not relavant!!!
  ctx.clearRect(0, 0, width, height);
  for (let i = 0; i < numParticles; ++i) {
    const [x, y] = currentParticleState[i];
    ctx.fillRect(x - 1, y - 1, 3, 3);
  }
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<canvas></canvas>

这是实际 WebGL 中的相同代码

'use strict';

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2')
  if (!gl) {
    return alert('sorry, need webgl2');
  }
  const ext = gl.getExtension('EXT_color_buffer_float');
  if (!ext) {
    return alert('sorry, need EXT_color_buffer_float');
  }
  
  const {width, height} = gl.canvas;

  const numParticles = 128;
  const particleParameters = [];  // info that does not change
  let currentParticleState = [];  // info that does change
  let nextParticleState = [];     // computed from currentState

  for (let i = 0; i < numParticles; ++i) {
    particleParameters.push(rand(-100, 100), rand(-100, 100), 0, 0);
    currentParticleState.push(rand(0, width), rand(0, height), 0, 0);
  }


  function rand(min, max) {
    return Math.random() * (max - min) + min;
  }

  const particleParamsTex = twgl.createTexture(gl, {
    src: new Float32Array(particleParameters),
    internalFormat: gl.RGBA32F,
    width: numParticles,
    height: 1,
    minMax: gl.NEAREST,
  });
  const currentStateTex = twgl.createTexture(gl, {
    src: new Float32Array(currentParticleState),
    internalFormat: gl.RGBA32F,
    width: numParticles,
    height: 1,
    minMax: gl.NEAREST,
  });
  const nextStateTex = twgl.createTexture(gl, {
    internalFormat: gl.RGBA32F,
    width: numParticles,
    height: 1,
    minMax: gl.NEAREST,
  });

  // create a framebuffer with 1 attachment (currentStateTex)
  // and record that it's numParticles wide and 1 pixel tall
  let currentStateFBI = twgl.createFramebufferInfo(gl, [
    { attachment: currentStateTex, },
  ], numParticles, 1);

  // create a framebuffer with 1 attachment (nextStateTex)
  // and record that it's numParticles wide and 1 pixel tall
  let nextStateFBI = twgl.createFramebufferInfo(gl, [
    { attachment: nextStateTex, },
  ], numParticles, 1);

  const particleVS = `
  #version 300 es
  in vec4 position;
  void main() {
    gl_Position = position;
  }
  `;

  const particleFS = `
  #version 300 es
  precision highp float;

  uniform vec2 resolution;
  uniform float deltaTime;
  uniform sampler2D particleParamsTex;
  uniform sampler2D currentStateTex;

  out vec4 outColor;

  vec4 euclideanModulo(vec4 n, vec4 m) {
    return mod(mod(n, m) + m, m);
  }

  void main() {
    int i = int(gl_FragCoord.x);
    vec4 curPos = texelFetch(currentStateTex, ivec2(i, 0), 0);
    vec4 velocity = texelFetch(particleParamsTex, ivec2(i, 0), 0);

    outColor = euclideanModulo(curPos + velocity * deltaTime, vec4(resolution, 1, 1));
  }

  `;

  const drawVS = `
  #version 300 es
  uniform sampler2D currentStateTex;
  uniform vec2 resolution;
  void main() {
    gl_PointSize = 3.0;
    // we calculated pos in pixel coords 
    vec4 pos = texelFetch(currentStateTex, ivec2(gl_VertexID, 0), 0);
    gl_Position = vec4(
       pos.xy / resolution * 2. - 1.,  // convert to clip space
       0,
       1);
  }
  `;

  const drawFS = `
  #version 300 es
  precision mediump float;
  out vec4 outColor;
  void main() {
    outColor = vec4(0, 0, 0, 1);
  }
  `;

  // compile shaders, link program, look up locations.
  const particleProgramInfo = twgl.createProgramInfo(gl, [particleVS, particleFS]);
  const drawProgramInfo = twgl.createProgramInfo(gl, [drawVS, drawFS]);

  // create a -1 to +1 quad vertices and put in a buffer.
  const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 2);

  let then = 0;
  function render(now) {
    now *= 0.001;  // convert to seconds
    const deltaTime = now - then;
    then = now;

    // bind the framebuffer and set the viewport to match
    twgl.bindFramebufferInfo(gl, nextStateFBI);
    gl.useProgram(particleProgramInfo.program);
    twgl.setBuffersAndAttributes(gl, particleProgramInfo, quadBufferInfo);
    twgl.setUniformsAndBindTextures(particleProgramInfo, {
      resolution: [width, height],
      deltaTime: deltaTime,
      currentStateTex: currentStateFBI.attachments[0],
      particleParamsTex,
    });
    // call drawArrays or drawBuffers
    twgl.drawBufferInfo(gl, quadBufferInfo);

    const t = nextStateFBI;
    nextStateFBI = currentStateFBI;
    currentStateFBI = t;  

    // bind the canvas and set the viewport to match
    twgl.bindFramebufferInfo(gl, null);
    gl.useProgram(drawProgramInfo.program);
    twgl.setUniforms(drawProgramInfo, {
      resolution: [width, height],
      currentStateTex: currentStateFBI.attachments[0],
    });
    gl.drawArrays(gl.POINTS, 0, numParticles);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

于 2019-06-27T03:40:50.663 回答