4 samples instead of 16, Bilinear filter.

Hey!

I wonder how I can use hardware bilinear filtering to sample only 4 times instead of 16, see example code.
I don’t know how to calculate the new texture coordinates/offsets.

I want to go from this:


float offsets[4] = {-1.5, -0.5, 0.5, 1.5};

vec4 color;
for(int x = 0; x < 4; x++)
{
	for(int y = 0; y < 4; y++)
	{
		//This should be precalculated, i.e. the division, i know :)
		vec2 offset = vec2(offsets[x], offsets[y]) / res;
		vec4 currentSample = texture(colorTexture, texCoord + offset);
		color += currentSample;
	}
}

color /= 16.0;

to this:


float offsets[4] = {-1.5, -0.5, 0.5, 1.5};//How to choose the new offsets???
vec4 color;

vec2 offset = vec2(???, ???) / res;
vec4 currentSample = texture(colorTexture, texCoord + offset);
color += currentSample;

offset = vec2(???, ???) / res;
currentSample = texture(colorTexture, texCoord + offset);
color += currentSample;

offset = vec2(???, ???) / res;
currentSample = texture(colorTexture, texCoord + offset);
color += currentSample;

offset = vec2(???, ???) / res;
currentSample = texture(colorTexture, texCoord + offset);
color += currentSample;

color /= 4.0; //Division by 4 here I think?

Thanks in advance!

So you’re calculating the average of a 4x4 block of pixels?

Sample at the centre of a 2x2 block (with offsets of -1.0 or 1.0) to get the average for that block.


vec4 color = 0.25*(
    texture(colorTexture, texCoord + vec2(-1,-1)) +
    texture(colorTexture, texCoord + vec2( 1,-1)) +
    texture(colorTexture, texCoord + vec2(-1, 1)) +
    texture(colorTexture, texCoord + vec2( 1, 1)));

[QUOTE=GClements;1267724]So you’re calculating the average of a 4x4 block of pixels?

Sample at the centre of a 2x2 block (with offsets of -1.0 or 1.0) to get the average for that block.


vec4 color = 0.25*(
    texture(colorTexture, texCoord + vec2(-1,-1)) +
    texture(colorTexture, texCoord + vec2( 1,-1)) +
    texture(colorTexture, texCoord + vec2(-1, 1)) +
    texture(colorTexture, texCoord + vec2( 1, 1)));

[/QUOTE]

There are two problems with this. First, it only works if your texture is a rectangle texture. Regular texture coordinates are normalized to the texture’s resolution.

If you want to offset your texture access by pixels, use an offset texturing function. Though this requires that the offset is a constant expression.

The second problem is that you’re not accessing a 2x2 block; you’re accessing the four corners of a 3x3 block, centered on texCoord. If you want a 2x2 block, you do this:


vec4 color = 0.25*(
    textureOffset(colorTexture, texCoord, vec2(0, 0)) +
    textureOffset(colorTexture, texCoord, vec2(0, 1)) +
    textureOffset(colorTexture, texCoord, vec2(1, 0)) +
    textureOffset(colorTexture, texCoord, vec2(1, 1)));

That depends upon whether texCoord itself is a centre or a corner.

The original offsets of +/- 0.5 or 1.5 texels imply that texCoord is a texel corner (specifically, the centre of the 4x4 block), in which case an offset of +/- 1 texel (i.e. 1/textureSize in normalised coordinates) is correct.

You seem to have misread the original question.

The OP has a 4x4 block of texels for which he’s computing the average colour, and is asking how to use bilinear filtering to do this using 4 lookups instead of 16. The solution is to split the 4x4 block into a 2x2 grid of 2x2 sub-blocks, and sample each sub-block at its centre (the common corner of the 4 texels) so that each sample is the average of the four texels.

So, to update my earlier solution to allow for the normalisation which I previously overlooked:


vec2 scale = vec2(1,1)/vec2(textureSize(colorTexture, 0));
vec4 color = 0.25*(
    texture(colorTexture, texCoord + scale*vec2(-1,-1)) +
    texture(colorTexture, texCoord + scale*vec2( 1,-1)) +
    texture(colorTexture, texCoord + scale*vec2(-1, 1)) +
    texture(colorTexture, texCoord + scale*vec2( 1, 1)));

Or, using textureOffset():


vec4 color = 0.25*(
    textureOffset(colorTexture, texCoord, vec2(-1,-1)) +
    textureOffset(colorTexture, texCoord, vec2( 1,-1)) +
    textureOffset(colorTexture, texCoord, vec2(-1, 1)) +
    textureOffset(colorTexture, texCoord, vec2( 1, 1)));

Both assume that texCoord itself is at the centre of the 4x4 block. The also assume that the minification filter is GL_LINEAR (i.e. no mipmaps).

If you need to allow for mipmaps, use textureLod() or textureLodOffset() with a [var]lod[/var] of zero Although, if the texture is mipmapped, a single lookup with a [var]bias[/var] of 2 will have roughly the same effect (a box filter is “recommended” rather than required, so the values aren’t guaranteed to be averages).

Thanks for answering Alfonse Reinheart and GClements.

Yes, I want to average the color of the whole scene texture. I downsample(I create my own chain without glGenerateMipmap()) the scene texture(2560x1440 or 1920x1080) like this:

Non power of two(width/4 and height/4):
1920x1080 (480x270 -> 120x67 -> 30x16 -> 7x4 -> 1x1)

Power of two(Rectangle)
2560x1440 (1024x512 -> 256x128 -> 64x32 -> 16x8 -> 4x2 -> 1x1)

Power of two(Square)
1920x1080 (512x512 -> 128x128 -> 32x32 -> 8x8 -> 2x2 -> 1x1)

I suppose that the Power of two(Square) gives the best results. For example(Non power of two): I sample the 1920x1080 texture, calculating the average and store the results to the 480x270 texture. This process continues down to the 1x1 texture. GClements, I suppose your solution works best with Power of two(Square)? i.e.:

vec2 scale = vec2(1,1)/vec2(textureSize(colorTexture, 0));
vec4 color = 0.25*(
texture(colorTexture, texCoord + scalevec2(-1,-1)) +
texture(colorTexture, texCoord + scale
vec2( 1,-1)) +
texture(colorTexture, texCoord + scalevec2(-1, 1)) +
texture(colorTexture, texCoord + scale
vec2( 1, 1)));

But it should be sufficient also for the Non power of two chain? I prefer to use the Non power of two, because it is faster.

Check my painted image: http://oi58.tinypic.com/2kizia.jpg

Is this correct?

The code I gave was specifically for averaging a 4x4 block to 1x1, with equal weights.

[QUOTE=Darkpower;1267730]
But it should be sufficient also for the Non power of two chain? I prefer to use the Non power of two, because it is faster.[/QUOTE]
You can use the same technique, i.e. using bilinear filtering to calculate a weighted sum of 4 texels with a single lookup.

For the non-power-of-four case, I suggest rounding size/4 up rather than down, so that each output texel covers at most 4 input texels (rather than covering at least 4 input texels when rounding down). Otherwise, you either omit texels from the calculation or you will need to use a 5x4 or 4x5 or 5x5 average.

The general principle remains the same: take the rectangle corresponding to the output texel and split it into a 4x4 grid. The sample positions will be at 1/4 and 3/4 in each direction, while the input texture coordinate is at 1/2 (the centre). So the sample positions are ±1/4 of the output texel from the centre. For the 4x4 case, 1/4 of an output texel is one input texel, hence the offsets of ±1. In the general case, the offsets will be ±scale/4, where scale is the ratio of the input size (width, height) to the output size.

Thank you so much!

Really a great explanation!