【问题标题】:How to save a value inside a fragment shader to use it later?如何在片段着色器中保存一个值以供以后使用?
【发布时间】:2016-11-05 23:31:26
【问题描述】:

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

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

gl_FragColor = vec4(previously saved variable)

这个问题与我也问过的另一个question here有关,但我觉得如果这个问题有答案,那么我可以轻松破解另一个问题。

有什么建议吗?

【问题讨论】:

    标签: javascript webgl fragment-shader


    【解决方案1】:

    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>

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

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

    【讨论】:

    • 我已经通过上面的示例了解了帧缓冲区。非常感谢。但是,第二次迭代中的这一行 tex2 = green (because blue is copied to green) 令人困惑,因为如果我们正在交换帧缓冲区,我们怎么能期望片段着色器的输出,即 gl.bindFramebuffer(gl.FRAMEBUFFER, fb2); 直接分配给 tex2?
    • 另外,有没有办法通过使用 WebGLTexture 接口知道每个纹理中的颜色值(RGBA),这将帮助我更好地理解和调试事物,现在我只是猜测:(
    • fb2 使用gl.framebufferTexture2D 附加了tex2texture2D(u_texture, ...) 正在从 tex1 读取,gl_FragColor = .. 正在写入 tex2,因为它是 COLOR_ATTACHMENT0 到当前绑定的帧缓冲区 fb2 至于你的第二个问题,如果你绑定 fb2 然后 gl.readPixels 将返回像素tex2。如果绑定fb1,那么gl.readPixels 将返回来自tex1 的像素。如果您将null 绑定为当前帧缓冲区,那么gl.readPixels 将从画布返回像素(WebGL 根据设置清除画布的问题除外)。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 我认为我使用的是遵循 OpenGL ES 2.0 的 WebGL 1.0。我拥有 GL4.2 和 GL4.3 的所有这些奢侈吗?
    • 不幸的是,你没有。
    • 好的,那么我怎样才能持久化一个 vec4 以便我以后可以访问它,知道吗?
    • @SubhasishDash:将其发射到 gl_FragColor 并渲染到附加到 FBO 的纹理。然后像往常一样在该纹理中传递下一步;像素中的纹理包含您在上一步中存储的值。如果您想发出多个值,可以使用 WEBGL_draw_buffers 扩展,可以在 dartgamedevs.org/2013/07/… 找到有关如何使用它的很好的概述
    • 这个问题被标记为WebGL 使得这个答案可以说是题外话
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-21
    • 2015-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-16
    相关资源
    最近更新 更多