Automatically detecting the texture filter threshold for pixelated magnifications

This article is an extension to a previously discussed topic: How to perform large texture magnification for pixelated games without aliasing between texels. My first post described a method to achieve this by assuming that, during the whole time, your polygons remain approximately at the same size after they are rendered.

From left to right, the knight is rendered at: Close distance with 15k pixels²/texels; medium distance with 49 pixels²/texels; far distance with 9 pixels²/texels. In all frames, α=0.1.

Constant pixels with variable α

The ideal value for the α parameter will depend on how many pixels are filling each texel after the texture is rendered. To have a smooth transition between texels without blurring them under extreme magnifications, nor aliasing them under small magnifications, we can have the number of pixels at the edge of each texel being constant, regardless of how far the polygon is.

teste

The derivative \frac{wdu}{dx} gives how many texels there are for each screen pixel, which is smaller than 1.0 when the texture is magnified. Therefore, if you want to have k pixels at the edge of your texels, your α must be k\frac{wdu}{dx}. Since we also have the v texture coordinate, the same will apply to it, which means α will be a 2D vector:

k\langle\frac{wdu}{dx},\frac{hdv}{dy}\rangle

This should give you an antialiased magnification that works independently of the polygon position, as shows the figure below:

left to right
From left to right, the knight is rendered at: Close distance with 15k pixels²/texels; medium distance with 49 pixels²/texels; far distance with 9 pixels²/texels. In all frames, k=0.7.

WebGL implementation

The derivatives with respect to screen space can be calculated by functions dFdx and dFdy, but in OpenGL ES/WebGL they require the extension GL_OES_standard_derivatives.

Our vertex shader remains the same as in our previous post:

varying vec2 vUv;

void main()
{
    const float w=32.0, h=64.0;
    vUv = uv * vec2(w, h);
    gl_Position = projectionMatrix * viewMatrix * modelMatrix
                  * vec4(position, 1.0 );
}

The few modifications will come in the fragment shader:

#extension GL_OES_standard_derivatives : enable

precision highp float;

varying vec2 vUv;
uniform sampler2D texSampler;

void main(void) {
    const float w=32.0, h=64.0;
    // here, k=0.7
    vec2 alpha = 0.7*vec2(dFdx(vUv.x), dFdy(vUv.y));
    vec2 x = fract(vUv);
    vec2 x_ = clamp(0.5/alpha*x, 0.0, 0.5) +
              clamp(0.5/alpha*(x-1.0)+0.5, 0.0, 0.5);
    gl_FragColor = texture2D(texSampler, (floor(vUv) + x_)
                                         /vec2(w,h));
}

Final thoughts

We have presented a more general approach to the manual texture filter previously discussed. Despite its advantage of automatically detecting the best α for the polygon being rendered, it depends on an OpenGL ES extension that might not be available on certain devices. Add that to the performance impact of the dFdx/dFdy functions, the previous method will certainly be preferred should your polygon not change in size during your game.

It’s also important to notice that, under small magnifications, the texture can look a bit blurry depending on the value k that was chosen.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s