I'm seeing low quality bilinear texture sampling in WebGL, OpenGL and Directx, and was wondering if anyone knew how to make it higher quality? The picture below should help show what I mean.
The left uses the built in bilinear sampling, while on the right, i read in the 4 pixels of the texture and do linear interpolation myself, which results in a higher quality interpolation.
I've tried lots of things, including setting the precision to highp for everything, including the sampler, and also have tried using floating point textures and looking for any relevant extensions, but no luck.
Does anyone know if there's a way to get the smoother interpolation in WebGL?I have tried it on a couple different machines (all nvidia though) as well as the major browsers.
I know that I can make it higher quality by taking 4 samples and doing the bilinear interpolation myself, or by taking 2 samples where i left the hardware do the X axis, then i do the Y axis in my shader. Are there any other ways? Hopefully some solution that doesn't require more texture reads?
Here's a link to the actual WebGL page, where you can see the source: http://demofox.org/BilinearTest/bilineartest.html
Thanks for any help anyone can provide!!
EDIT: code details
I create a 2x2 texture with linear min and mag filters - no mips.
function createTexture(byteArrayWithRGBAData, width, height) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(byteArrayWithRGBAData));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
....
var textureData = [
128, 0, 0, 255, 50, 0, 0, 255,
50, 0, 0, 255, 200, 0, 0, 255
];
// create the texture
theTexture = createTexture(
textureData,
2, 2
);
I'm drawing a full screen quad, where the lower left has a texture coordinate of 0,0 and the upper right has one of (2,1). In my pixel shader, on the left side i sample along the diagonal of the image (again, using bilinear sampling), using the x texture coordinate to define where in the image 0.25 to 0.75 my u,v coordinates should be.
The left side uses bilinear sampling and you can see that it's very rough. This is the problem I'm trying to overcome, and have heard (but have not seen proof) that the texture is interpolated using 24.8 fixed point in the hardware, which makes those jaggies.
On the right side, i remap the 1.0-2.0 x axis texture coordinate to 0-1 so that it shows the same thing.
On the right side however, instead of doing hardware bilinear texture sampling, I'm getting the 4 pixels of the image and doing the bilinear interpolation in the fragment shader myself. The results are smooth like you'd expect, due to the calculations happening with floating point values.
To get the actual pixel color on both sides, i check the texture coordinate's y axis versus the pixel value interpolated. If the texture coordinate's y axis value is smaller, i make the pixel green, else make it black.
The vertex shader is uninteresting and just a pass through, but here's the fragment shader
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D uSampler;
varying vec2 vTextureCoord;
vec4 SamplePixel (vec2 pixel, bool bilinearSampling) {
// Bilinear sampling:
// Hardware based bilinear sampling
if (bilinearSampling)
return texture2D(uSampler, (pixel + vec2(0.5)) / 2.0);
// Nearest sampling:
// Software bilinear sampling (higher quality)
float fracu = fract(pixel.x);
float fracv = fract(pixel.y);
vec2 floorPixel = floor(pixel) + vec2(0.5,0.5);
vec4 A = texture2D(uSampler, floorPixel / 2.0);
vec4 B = texture2D(uSampler, (floorPixel+vec2(1.0,0.0)) / 2.0);
vec4 C = texture2D(uSampler, (floorPixel+vec2(0.0,1.0)) / 2.0);
vec4 D = texture2D(uSampler, (floorPixel+vec2(1.0,1.0)) / 2.0);
return mix(mix(A,B,fracu), mix(C,D,fracu), fracv);
}
void main(void) {
vec4 colorValue;
if (vTextureCoord.x < 0.995)
{
float pixel = vTextureCoord.x / 0.995;
colorValue = SamplePixel(vec2(pixel), true);
}
else if (vTextureCoord.x > 1.005)
{
float pixel = fract(vTextureCoord.x - 0.005) / 0.995;
colorValue = SamplePixel(vec2(pixel), false);
}
else
{
gl_FragColor = vec4(1.0);
return;
}
float value = step(vTextureCoord.y, colorValue.x);
gl_FragColor = vec4(0.0, value, 0.0, 1.0);
}
</script>
GL_LINEAR
sampling, but it does not specify a specific representation of the numbers involved. On my (AMD) hardware, it appears to do hardware linear filtering directly onUNSIGNED_BYTE
s forGL_RGBA8
textures (not sure about sRGB, float, or higher precision normalized-integer formats), so the output of the filtering is still an 8 bit unsigned integer for each channel, which is then converted to a floating point vector, rather than converting all the surrounding pixels to floating point and doing the filtering there \$\endgroup\$