Как сохранить координацию между частицами и тем, какой пиксель текстуры содержит информацию каждого из них? - PullRequest
0 голосов
/ 26 июня 2019

На примере сетки 4x4x4 у меня есть 64 вершины (которые я назову частицами), которые начинаются с определенных позиций относительно друг друга. Эти 64 частицы будут двигаться в направлениях x, y и z, теряя свои начальные положения относительно друг друга. Однако каждый цикл, новые положения и скорости частиц должны быть рассчитаны на основе исходных начальных отношений между частицей и ее исходными соседями.

Я узнал, что для этого мне нужно использовать текстуры и, следовательно, кадровые буферы, и теперь я могу написать две 3D-текстуры, которые используются для выполнения операций записи и чтения. Однако в следующем цикле, когда gl_FragCoord передается фрагментному шейдеру с новой позицией частицы (например, может переключаться с другой частицей), я не вижу никакого механизма, с помощью которого исходная координата текстуры, которая удерживала частицу информация будет записана с текущей информацией частицы. Есть ли какой-то механизм, который я не понимаю, который позволяет движущимся частицам хранить свои данные в статической сетке (трехмерной текстуре), где данные каждой частицы всегда заполняют одну и ту же координату, поэтому я могу использовать texelFetch, чтобы получить данные частицы, а также данные оригинальных соседей? Могу ли я изменить gl_FragCoord и получить пиксельный вывод, где я хочу, или это неизменяемая входная переменная?

Как только я решу эту проблему, я надеюсь затем реализовать обратную связь преобразования, чтобы выполнить фактическое перемещение вершин, не выгружая текстуру в ЦП и не извлекая данные о положении и повторно загружая их в графический процессор для следующего цикла.

Есть ли какие-либо предложения о том, как отслеживать исходную позицию каждой частицы, исходных соседей и текущую позицию относительно этих исходных соседей, используя текстуры, записанные в Framebuffers?

1 Ответ

0 голосов
/ 27 июня 2019

Я запутался из-за вашей путаницы 100

Вот простая система частиц 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.outColor. Он также не имеет входов, кроме gl.fragCoord и gl.currentProgram.uniforms. currentParticleState - это массив из 4 массивов значений, где, как и прежде, это был массив объектов со свойством position. частицыПараметры - это также просто массив из 4 массивов значений вместо массива объектов со значением скорости. Это делается для того, чтобы имитировать тот факт, что в реальном WebGL это были бы текстуры, поэтому любое значение, например position или velocity, потеряно.

Код, который на самом деле рисует частицы, не имеет значения.

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

  let currentStateFBI = twgl.createFramebufferInfo(gl, [
    { attachment: currentStateTex, },
  ], numParticles, 1);
  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);
  }
  `;

  const particleProgramInfo = twgl.createProgramInfo(gl, [particleVS, particleFS]);
  const drawProgramInfo = twgl.createProgramInfo(gl, [drawVS, drawFS]);

  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;

    twgl.bindFramebufferInfo(gl, nextStateFBI);
    gl.useProgram(particleProgramInfo.program);
    twgl.setBuffersAndAttributes(gl, particleProgramInfo, quadBufferInfo);
    twgl.setUniforms(particleProgramInfo, {
      resolution: [width, height],
      deltaTime: deltaTime,
      currentStateTex: currentStateFBI.attachments[0],
      particleParamsTex,
    });
    twgl.drawBufferInfo(gl, quadBufferInfo);

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

    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>
...