1
votes

Let's say I'm working on a problem in WebGL that requires values being retrieved from large textures (say, 2048x2048) with pixel perfect precision.

Everything's worked out great for me thus far. I can pass the large texture to a fragment shader, the fragment shader will transform it to a render target, and I can even supply that render target as input for another render pass, always retrieving the exact pixel I need.

But now let's say I want to mix things up. I want to create a render pass that returns a texture storing a pair of uv coordinates for each pixel in the large texture that I started out with. As the simplest use case, let's say:

precision highp float;
precision highp sampler2D;
varying vec2 vUv;

void main() {

    gl_FragColor = vec4(vUv, 0, 1);

}

Using the uv coordinates returned by this first render pass, I want to access a pixel from the large texture I started out with:

precision highp float;
precision highp sampler2D;
uniform sampler2D firstPass;
uniform sampler2D largeTexture;
varying vec2 vUv;

void main() {

    vec2 uv = texture2D(firstPass, vUv);
    gl_FragColor = texture2D(largeTexture, uv);

}

This however does not work with adequate precision. I will most often get a color from a neighboring pixel as opposed to the pixel I intended to address. From some tinkering around I've discovered this only works on textures with sizes up to ~512x512.

You will note I've specified the use of high precision floats and sampler2Ds in these examples. This was about the only solution that came readily to mind, but this still does not address my problem. I know I can always fall back on addressing pixels with a relative coordinate system that requires lower precision, but I'm hoping I may still be able to address with uv for the sake of simplicity.

1

1 Answers

7
votes

Ideas

  • Make your UV texture a floating point texture? Your texture is currently probably only 8bits per channel so that means it can only address 256 unique locations. A floating point texture would not have that problem

    Unfortunately rendering to floating point textures is not supported everywhere and the browsers have not uniformly implemented the required extensions to check if it will work or not. If you're on a modern desktop it likely will work though.

    To find out if it will work, try to get the floating point texture extension, if it exists make a floating point texture and attach it to a framebuffer then check if the framebuffer is complete. If it is you can render to it.

    var floatTextures = gl.getExtension("OES_texture_float");
    if (!floatTextures) {
       alert("floating point textures are not supported on your system");
       return;
    }
    
    // If you need linear filtering then...
    var floatLinearTextures = gl.getExtension("OES_texture_float_linear");
    if (!floatLinearTextures) {
       alert("linear filtering of floating point textures is not supported on your system");
    }
    
    // check if we can render to floating point textures.
    var tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);
    
    // some drivers have a bug that requires you turn off filtering before
    // rendering to a texture. 
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 
    
    // make a framebuffer
    var fb = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    
    // attach the texture
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, 
                            gl.TEXTURE_2D, tex, 0);
    
    // check if we can render
    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
    if (status != gl.FRAMEBUFFER_COMPLETE) {
        alert("can't render to floating point textures");
        return;
    }
    
    // You should be good to go.
    
  • Increase your resolution by combining data into multiple channels

    when writing the UV texture convert UV from the 0-1 range to the 0 to 65535 range then write modf(uv, 256) / 255 to one channel and floor(uv / 256) / 256 to another channel. When reading re-combine the channels with something like uv = (lowChannels * 256.0 + highChannels * 65535.0) / 65535.0

    That should work everywhere and give you enough resolution to address a 65536x65536 texture.