问题
Suppose I have a 100x100 texture and I do the following:
vec4 texelQuad = textureGather(sampler, vec2(50.5)/vec2(100.0));
The coordinate I am requesting is exactly at the center of texel (50, 50). So, will I get a quad of texels bounded by (49, 49) and (50, 50) or the one bounded by (50, 50) and (51, 51). The spec is evasive on the subject. It merely states the following:
The rules for the LINEAR minification filter are applied to identify the four selected texels.
The relevant section 8.14.2 Coordinate Wrapping and Texel Selection of the spec is not terribly clear either. My best hypothesis would be the following:
ivec2 lowerBoundTexelCoord = ivec2(floor(textureCoord * textureSize - 0.5));
Does that hypothesis hold in practice? No, it doesn't. In fact no other hypothesis would hold either, since different hardware returns different results for this particular case:
textureSize: 100x100
textureCoord: vec2(50.5)/vec2(100.0)
Hyphotesis: (49, 49) to (50, 50)
GeForce 1050 Ti: (49, 49) to (50, 50)
Intel HD Graphics 630: (50, 50) to (51, 51)
another case:
textureSize: 100x100
textureCoord: vec2(49.5)/vec2(100.0)
Hyphotesis: (48, 48) to (49, 49)
GeForce 1050 Ti: (49, 49) to (50, 50)
Intel HD Graphics 630: (48, 48) to (49, 49)
Does that make textureGather() useless due to the unpredictable behavior at texel center coordinates? Not at all!. While you may not be able to predict which 4 texels it will return in some particular cases, you can still force it to return the ones you want, by giving it a coordinate between those 4 texels you want. That is, if I want texels bounded by (49, 49) and (50, 50), I would call:
textureGather(sampler, vec2(50.0, 50.0)/textureSize);
Since the coordinate I am requesting this time is the point where 4 texels meet, any implementation will surely return me those 4 texels.
Now, the question: Is my analysis correct? Does everyone who uses textureGather() force it to return a particular quad of texels rather then figuring out which ones it would return by itself? If so, it's such a shame it's not reflected in any documentation.
EDIT
It was pointed out that OpenGL doesn't guarantee the same result dividing identical floating point numbers on different hardware. Therefore, it becomes necessary to mention that in my actual code I had vec2(50.5)/vec2(textureSize(sampler, 0)) rather than vec2(50.5)/vec2(100.0). That's important, since the presence of textureSize() prevents that division from being carried out at shader compilation time.
Let me also rephrase the question:
Suppose you've got a normalized texture coordinate from a black box. That coordinate is then passed to textureGather():
vec2 textureCoord = takeFromBlackBox();
vec4 texelQuad = textureGather(sampler, textureCoord);
Can anyone produce GLSL code that would return the integer pair of coordinates of the texel returned in texelQuad[3], which is the lower-bound corner of a 2x2 box? The obvious solution below doesn't work in all cases:
vec2 textureDims = textureSize(sampler, 0);
ivec2 lowerBoundTexelCoord = ivec2(floor(textureCoord * textureDims - 0.5));
Examples of tricky cases where the above approach may fail are:
vec2 textureCoord = vec2(49.5)/vec2(textureSize(sampler, 0));
vec2 textureCoord = vec2(50.5)/vec2(textureSize(sampler, 0));
where textureSize(sampler, 0) returns ivec2(100, 100).
回答1:
Recall that the texel locations for GL_LINEAR ([OpenGL 4.6 (Core) §8.14 Texture Minification]) are selected by the following formulas:
i0 = wrap(⌊u′ - 1/2⌋)
j0 = wrap(⌊v′ - 1/2⌋)
...
The value of (u′,v′) in this case is equal to
(vec2(50.5) / vec2(100)) * vec2(100)
However, note that this is not guaranteed to be equal to vec2(50.5). See The OpenGL Shading Language 4.60 §4.71 Range and Precision:
a / b, 1.0 / b: 2.5 ULP for b in the range [2-126, 2126].
So the value of u′ might be slightly larger than 50.5, slightly smaller than 50.5, or it might be 50.5 exactly. No promises! Well, the spec promises no more than 2.5 ULP, but that's nothing to write home about. You can see that when you subtract 0.5 and take the floor, you are either going to get 49 or 50, depending on how the number was rounded.
i0 = wrap(⌊(50.5 / 100) * 100 - 1/2⌋)
i0 = wrap(⌊(.505 ± error) * 100 - 1/2⌋)
i0 = wrap(⌊50.5 ± error - 1/2⌋)
i0 = wrap(⌊50 ± error⌋)
i0 = 50 (if error >= 0) or 49 (if error < 0)
So in fact it is not textureGather() that is behaving unpredictably. The unpredictable part is the rounding error when you try to divide by 100, which is explained in the GLSL spec and in the classic article, WhatEvery Computer Scientist Should Know About Floating-Point Numbers.
Or in other words, textureGather() always gives you the same result, but 50.5/100.0 does not.
Note that you could get exact results if your texture were a power of two, since you could use 50.5 * 0.0078125 to compute 50.5 / 128, and the result would be exactly correct, since multiplication is correctly rounded and 0.0078125 is a power of two.
来源:https://stackoverflow.com/questions/46413746/texturegather-behavior-at-texel-center-coordinates