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

时间:2016-07-04 09:29:09

标签: javascript webgl fragment-shader

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

目前,我正在使用一个巨大的算法准备一个图像,我想将它保存到一些vec4,再次请求,我想得到那个vec4,应该说

gl_FragColor = vec4(previously saved variable)

这个问题与我提出的另一个question here有关,但我觉得如果这个问题有答案,那我就可以轻易破解另一个问题。

有什么建议吗?

2 个答案:

答案 0 :(得分:3)

WebGL中的片段着色器写入2件事中的1件。 (1)画布到(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>
&#13;
&#13;
&#13;

上面的示例将红色放在纹理中。然后它通过调色来渲染纹理。绿色进入红色通道,蓝色进入绿色通道,红色进入蓝色通道。

我制作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

答案 1 :(得分:1)

片段着色器按片段(像素)执行。与任何其他着色器一样,默认情况下它不能存储值,正如您在常规编程语言中所期望的那样。 有几种方法可以做你想要的: 您可以使用imageLoad/Store,它允许您从着色器读取和写入数据到图像。Image使用GL纹理作为内存存储。它的好处是可以存储和加载数字数据而不会丢失使用图像时的精度,因为在通过图像访问纹理数据时禁用纹理过滤。

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

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

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