PDA

View Full Version : 4 samples instead of 16, Bilinear filter.

Darkpower
06-14-2015, 03:28 AM
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?

GClements
06-14-2015, 07:20 AM
I wonder how I can use hardware bilinear filtering to sample only 4 times instead of 16, see example code.
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)));

Alfonse Reinheart
06-14-2015, 07:52 AM
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)));

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 (https://www.opengl.org/wiki/Sampler_%28GLSL%29#Offset_texture_access). 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)));

GClements
06-14-2015, 10:59 AM
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.
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.

If you want a 2x2 block, you do this:
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 lod of zero Although, if the texture is mipmapped, a single lookup with a bias 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).

Darkpower
06-14-2015, 04:33 PM
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 + 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)));

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.

Darkpower
06-14-2015, 05:36 PM
Check my painted image: http://oi58.tinypic.com/2kizia.jpg

Is this correct?

GClements
06-14-2015, 06:17 PM
GClements, I suppose your solution works best with Power of two(Square)?
The code I gave was specifically for averaging a 4x4 block to 1x1, with equal weights.

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.
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.

Darkpower
06-16-2015, 01:55 AM
Thank you so much!

Really a great explanation!