从片段着色器恢复像素数据会导致意外行为

Recuperating pixel data from fragment shader results in unexpected behavior

我目前正在尝试使用片段着色器来传输一些粒子位置数据(并在将来修改它)。我使用 sampler2D 纹理将数据发送到着色器没有问题,但是当我尝试恢复数据时,我的 20 个粒子突然出现了错误的位置。我已经打印了尽可能多的输出,并尽可能地减少了代码,但仍然无法理解我哪里错了。

p5js 网站上提供了可重现的最小化版本here

这是我的 sketch.js :

let theShader;
let shaderTexture;

let NUM_PARTICLES = 20;
let particleTexture;
let particles = [];

function preload(){
    theShader = loadShader('basic.vert', 'basic.frag');
}

function setup() {
  createCanvas(400, 400, WEBGL);
  
  // Initiate Particles
  for(let i = 0; i < NUM_PARTICLES;i++){
    particles.push(createVector(i*20,i*20,0));
  }
  
  // Initialize Shader
  shaderTexture = createGraphics(NUM_PARTICLES, 1, WEBGL);
  shaderTexture.noStroke();
  
  // Create Particle Texture
  particleTexture = createImage(NUM_PARTICLES, 1);
  
  // Fill Particle Texture
  particleTexture.loadPixels();
  for (let i = 0; i < NUM_PARTICLES; i++) {   
    particleTexture.pixels[i*4+0] = map(particles[i].x,0,width,0,255); // R
    particleTexture.pixels[i*4+1] = map(particles[i].y,0,height,0,255); // G
    particleTexture.pixels[i*4+2] = 0; // B
    particleTexture.pixels[i*4+3] = 255; // A
  }
  particleTexture.updatePixels();
}

function draw() {  
  translate(-width/2, -height/2);
  background(255);
  
  // Display Particles Before Modification
  for(let i = 0; i < NUM_PARTICLES;i++){
    circle(particles[i].x,particles[i].y,10); // draw circle at particle location
  }

  // Apply Texture
  shaderTexture.shader(theShader); // set shader
  theShader.setUniform('text', particleTexture); // send particleTexture to shader
  shaderTexture.rect(0,0,NUM_PARTICLES,1); // set rect to recieve shader out
  
  // Print Shader Output
  for(let i = 0; i < NUM_PARTICLES;i++){
    let newPos = shaderTexture.get(i, 0);
    print(newPos);
  }
  
  // Update and Display Particles
  for(let i = 0; i < NUM_PARTICLES;i++){
    let newPos = shaderTexture.get(i, 0);
    particles[i].x = map(newPos[0],0,255,0,width);
    particles[i].y = map(newPos[1],0,255,0,height);
    fill(255,0,0);
    circle(particles[i].x,particles[i].y,10);
  }

  noLoop();
}

这是我的片段着色器,它不应该修改任何东西:

#ifdef GL_ES
precision highp float;
#endif

uniform sampler2D text;

void main() {
  vec2 particle = texture2D(text, vec2(gl_FragCoord.x, gl_FragCoord.y)).xy;

  gl_FragColor = vec4(particle.x,particle.y,0.0,1.0); // R,G,B,A
}

还有我的默认顶点着色器:

#ifdef GL_ES
precision highp float;
#endif

attribute vec3 aPosition;

void main() {
  vec4 positionVec4 = vec4(aPosition, 1.0);

  positionVec4.xy = positionVec4.xy * 2.0 - 1.0; 

  gl_Position = positionVec4;
}

当查找具有 texture2D the texture coordinates must be specified in range [0.0, 1.0]. (0, 0) is the bottom left and (1, 1) is the top right. However gl_FragCoord.xy 的纹理时包含 window 坐标,左上角 (0.5, 0.5) 和右上角 (width-0.5, height-0.5)。
因此,您需要将 gl_FragCoord 除以视口的大小。由于您绘制的矩形大小为 NUM_PARTICLESx1(宽度 == NUM_PARTICLES,高度 = 1),因此必须将 gl_FragCoord.x 除以 NUM_PARTICLES:

vec2 particle = texture2D(text, vec2(gl_FragCoord.x, gl_FragCoord.y)).xy;

vec2 particle = texture2D(text, vec2(gl_FragCoord.x/20.0, 0.0)).xy;

或者,您可以向片段着色器添加一个 size uniform,然后将 gl_FragCoord.xy 除以 size:

#ifdef GL_ES
precision highp float;
#endif

uniform sampler2D text;
uniform vec2 size;

void main() {
  
  vec2 particle = texture2D(text, gl_FragCoord.xy / size).xy;

  gl_FragColor = vec4(particle.x,particle.y,0.0,1.0); // R,G,B,A
}

通过[NUM_PARTICLES, 1]设置size统一的值:

// Apply Texture
shaderTexture.shader(theShader); // set shader
theShader.setUniform('text', particleTexture); // send particleTexture to shader
theShader.setUniform('size', [NUM_PARTICLES, 1]);
shaderTexture.rect(0,0,NUM_PARTICLES,1); // set rect to recieve shader out


如果您正在使用 OpenGL ES Shading Language 3.00 you have the option to use texelFetch to lookup the texture with integral pixel coordinates or get the size of the texture using textureSize.
不幸的是 p5.jscreateCanvas()) doesn't seem to provide a WebGL 2.0 上下文,所以这不是一个选项。