如何在片段着色器中保存一个值以便以后使用?

How to save a value inside a fragment shader to use it later?

我想将片段着色器的计算值保存在某个变量中,以便下次使用。

目前,我正在使用庞大的算法准备图像,我想将其保存到某个 vec4 中,一旦再次请求,我只想获取该 vec4 并且应该说

gl_FragColor = vec4(previously saved variable)

这个问题和另一个question here有关系,我也问过这个问题,但是我觉得如果这个问题有答案,那么我可以很容易地破解另一个

有什么建议吗?

片段着色器按片段(像素)执行。与任何其他着色器一样,默认情况下它不能存储值,正如您在常规编程语言中所期望的那样。 有几种方法可以做你想做的事: 您可以使用 imageLoad/Store ,which allows you to read and write data from shader into image.Image 使用 GL 纹理作为内存 storage.What 的好处是您可以在使用图像时存储和加载数字数据而不会丢失精度,因为在通过图像访问纹理数据时禁用纹理过滤。

在着色器中存储和读取数据的另一种方法是使用 buffers.Uniform 缓冲区,或者从 GL4.3 Shader storage buffers. 开始。 SSBO 允许读取和写入大量 data.It 确实由您决定使用哪些存储和检索数据 shaders.Some 人们说某些 [= 上的纹理内存访问速度更快25=] 根据我的经验,使用 SSBO 与图像加载存储,我没有发现 Nvidia GPU 的性能有显着差异。

在您的场景中,我可能会选择图像 Load/Store。因为您可以像对采样纹理一样对图像数据使用相同的 UV 索引。

此外,我真的不知道您使用的是什么版本的 OpenGL,但要使用这些扩展,您必须使用 GL4.2 和 GL4.3。

WebGL 中的片段着色器写入 2 个事物中的 1 个。 (1) canvas 到 (2) 帧缓冲区的附件。帧缓冲区的附件可以是纹理。纹理可以用作着色器的输入。因此,您可以写入纹理并在下一次绘制中使用该纹理。

这是一个例子

var vs = `
attribute vec4 position;
void main() {
  gl_Position = position;
}
`;
var fs = `
precision mediump float;
uniform sampler2D u_texture;
void main() {
  // just grab the middle pixel(s) from the texture
  // but swizzle the colors g->r, b->g, r->b
  gl_FragColor = texture2D(u_texture, vec2(.5)).gbra;
}`;

var canvas = document.querySelector("canvas");
var gl = canvas.getContext("webgl");
var program = twgl.createProgramFromSources(gl, [vs, fs]);

var positionLocation = gl.getAttribLocation(program, "position");
// we don't need to look up the texture's uniform location because
// we're only using 1 texture. Since the uniforms default to 0
// it will use texture 0.

// put in a clipspace quad
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  -1, -1,
   1, -1,
  -1,  1,
  -1,  1,
   1, -1,
   1,  1,
]), gl.STATIC_DRAW);
  

gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

// make 2 1x1 pixel textures and put a red pixel the first one
var tex1 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
              gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
var tex2 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex2);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
              gl.UNSIGNED_BYTE, null);

// make a framebuffer for tex1
var fb1 = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb1);
// attach tex1
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, 
                        gl.TEXTURE_2D, tex1, 0);
// check this will actually work
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !==
    gl.FRAMEBUFFER_COMPLETE) {
  alert("this combination of attachments not supported");
}

// make a framebuffer for tex2
var fb2 = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
// attach tex2
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, 
                        gl.TEXTURE_2D, tex2, 0);
// check this will actually work
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !==
    gl.FRAMEBUFFER_COMPLETE) {
  alert("this combination of attachments not supported");
}

function render() {
  gl.useProgram(program);
  // render tex1 to the tex2
  
  // input to fragment shader
  gl.bindTexture(gl.TEXTURE_2D, tex1);  
  
  // output from fragment shader
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);  
  gl.viewport(0, 0, 1, 1);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
  
  // render to canvas so we can see it
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  // input to fragment shader, the texture we just rendered to
  gl.bindTexture(gl.TEXTURE_2D, tex2);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
  
  // swap which texture we are rendering from and to
  var t = tex1;
  tex1 = tex2;
  tex2 = t;
  
  var f = fb1;
  fb1 = fb2;
  fb2 = f;
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<canvas></canvas>

上面的示例将红色放入纹理中。然后它通过混合颜色来渲染该纹理。绿色到红色通道,蓝色到绿色通道,红色到蓝色通道。

我制作了 2 个纹理并将它们附加到 2 个帧缓冲区。

第一次迭代

tex1 = red
tex2 = 0,0,0,0
render to fb2
tex2 is now blue (because red was copied to blue)
render tex2 to canvas (canvas is now green because blue is copied to green)
switch which textures we're rendering to

第二次迭代

tex1 = blue (was tex2 last time) 
tex2 = red  (was tex1 last time)
render to fb2 (was fb1 last time)
tex2 = green (because blue is copied to green)
render tex2 to canvas (canvas is now red because green is copied to red)
switch which textures we're rendering to