问题
When its translated in an integral value (1,2,3, etc....), there are no black lines in-between the tiles, it looks fine. But when it's translated to a non-integral (1.1, 1.5, 1.67), there are small blackish lines between each tile (I'm imagining that it's due to subpixel rendering, right?) ... and it doesn't look pretty =P
So... what should I do?
This is my image-loading code, by the way:
bool Image::load_opengl() {
this->id = 0;
glGenTextures(1, &this->id);
this->bind();
// Parameters... TODO: Should we change this?
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, this->size.x, this->size.y,
0, GL_BGRA, GL_UNSIGNED_BYTE, (void*) FreeImage_GetBits(this->data));
this->unbind();
return true;
}
I've also tried using:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
and:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
Here is my image drawing code:
void Image::draw(Pos pos, CROP crop, SCALE scale) {
if (!this->loaded || this->id == 0) {
return;
}
// Start position & size
Pos s_p;
Pos s_s;
// End size
Pos e_s;
if (crop.active) {
s_p = crop.pos / this->size;
s_s = crop.size / this->size;
//debug("%f %f", s_s.x, s_s.y);
s_s = s_s + s_p;
s_s.clamp(1);
//debug("%f %f", s_s.x, s_s.y);
} else {
s_s = 1;
}
if (scale.active) {
e_s = scale.size;
} else if (crop.active) {
e_s = crop.size;
} else {
e_s = this->size;
}
// FIXME: Is this okay?
s_p.y = 1 - s_p.y;
s_s.y = 1 - s_s.y;
// TODO: Make this use VAO/VBO's!!
glPushMatrix();
glTranslate(pos.x, pos.y, 0);
this->bind();
glBegin(GL_QUADS);
glTexCoord2(s_p.x, s_p.y);
glVertex2(0, 0);
glTexCoord2(s_s.x, s_p.y);
glVertex2(e_s.x, 0);
glTexCoord2(s_s.x, s_s.y);
glVertex2(e_s.x, e_s.y);
glTexCoord2(s_p.x, s_s.y);
glVertex2(0, e_s.y);
glEnd();
this->unbind();
glPopMatrix();
}
OpenGL Initialization code:
void game__gl_init() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, config.window.size.x, config.window.size.y, 0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
Screenshots of the issue:
回答1:
The problem with using texture atlases (sprite sheets) and adjacent texels leaking has to do with the way linear texture filtering works.
For any point in the texture that is not sampled exactly at the center of a texel, linear sampling will sample 4 adjacent texels and compute the value at the location you asked as the weighted (based on distance from the sample point) average of all 4 samples.
Here's a nice visualization of the problem:
Since you cannot use something like GL_CLAMP_TO_EDGE
in a texture atlas, you need to create border texels around the edge of each texture. These border texels will prevent neighboring samples from completely different textures in the atlas from altering the image through weighted interpolation explained above.
Note that when you use anisotropic filtering, you may need to increase the width of the border. This is because anisotropic filtering will increase the size of the sample neighborhood at extreme angles.
To illustrate what I mean by using a border around the edge of each texture, consider the various wrap modes available in OpenGL. Pay special attention to CLAMP TO EDGE
.
Despite there being a mode called "Clamp to Border", that is actually not what we are interested in. That mode lets you define a single color to use as a border around your texture for any texture coordinates that fall outside of the normalized [0.0-1.0] range.
What we want is to replicate the behavior of CLAMP_TO_EDGE
, where any texture coordinate outside the proper range for the (sub-)texture receives the value of the last texel center in the direction it was out of bounds in. Since you have almost complete control over the texture coordinates in an atlas system, the only scenario in which (effective) texture coordinates might refer to a location outside of your texture are during the weighted average step of texture filtering.
We know that GL_LINEAR
will sample the 4 nearest neighbors as seen in the diagram above, so we only need a 1-texel border. You may need a wider texel border if you use anisotropic filtering, because it increases the sample neighborhood size under certain conditions.
Here's an example of a texture that illustrates the border more clearly, though for your purposes you can make the border 1 texel or 2 texels wide.
(NOTE: The border I am referring to is not the black around all four edges of the image, but the area where the checkerboard pattern stops repeating regularly)
In case you were wondering, here is why I keep bringing up anisotropic filtering. It changes the shape of the sample neighborhood based on angle and can cause more than 4 texels to be used for filtering:
The larger the degree of anisotropy you use, the more likely you will have to deal with sample neighborhoods containing more than 4 texels. A 2 texel border should be adequate for most anisotropic filtering situations.
Last but not least, here is how a packed texture atlas would be built that would replicate GL_CLAMP_TO_EDGE
behavior in the presence of a GL_LINEAR
texture filter:
(Subtract 1 from X and Y in the black coordinates, I did not proof read the image before posting.)
Due to border storage, storing 4 256x256 textures in this atlas requires a texture with dimensions 516x516. The borders are color coded based on how you would fill them with texel data during atlas creation:
- Red = Replace with texel directly below
- Yellow = Replace with texel directly above
- Green = Replace with texel directly to the left
- Blue = Replace with texel directly to the right
Effectively in this packed example, each texture in the atlas uses a 258x258 region of the atlas, but you will generate texture coordinates that map to the visible 256x256 region. The bordering texels are only ever used when texture filtering is done at the edges of textures in the atlas, and the way they are designed mimics GL_CLAMP_TO_EDGE
behavior.
In case you were wondering, you can implement other types of wrap modes using a similar approach -- GL_REPEAT
can be implemented by exchanging the left/right and top/bottom border texels in the texture atlas and a little bit of clever texture coordinate math in a shader. That is a little more complicated, so do not worry about that for now. Since you're only dealing with sprite sheets limit yourself to GL_CLAMP_TO_EDGE
:)
回答2:
I had the same problem, as illustrated in this picture:
The idea is to shrink the images in the atlas by one pixel, and replace the pixels with the color adjacent to the 1px "border". Once this is done, adjust the UV offset to account for the 1px border. In other words, the actual texture coordinates would be (for top left corner to bottom right corner): start_x + 1
, start_y + 1
, end_x - 1
, end_y -1
Before:
After:
After applying, this is the result:
回答3:
Another problem if you have transparent pixel in your texture:
When OpenGL use linear filter to scale your texture, it blend some pixels with transparent pixel, but in most of the cases, the transparent pixel color is white so the result blended pixel haven't the expected color. To fix this, a solution is to create pre-multiplied alpha. I've created a script for achieve this on Gimp:
(define (precompute-alpha img color)
(define w (car (gimp-image-width img)))
(define h (car (gimp-image-height img)))
(define img-layer (car (gimp-image-get-active-layer img)))
(define img-mask (car (gimp-layer-create-mask img-layer ADD-ALPHA-TRANSFER-MASK)))
(gimp-layer-add-mask img-layer img-mask)
(define alpha-layer (car (gimp-layer-new img w h RGBA-IMAGE "alpha" 100 NORMAL-MODE)))
(gimp-image-insert-layer img alpha-layer 0 -1)
(gimp-edit-copy img-mask)
(define floating-sel (car (gimp-edit-paste alpha-layer TRUE)))
(gimp-floating-sel-anchor floating-sel)
(define bg-layer (car (gimp-layer-new img w h RGBA-IMAGE "bg" 100 NORMAL-MODE)))
(gimp-image-insert-layer img bg-layer 0 2)
(gimp-context-set-background color)
(gimp-drawable-fill bg-layer BACKGROUND-FILL)
(set! bg-layer (car (gimp-image-merge-down img img-layer 0)))
(define bg-mask (car (gimp-layer-create-mask bg-layer ADD-WHITE-MASK)))
(gimp-layer-add-mask bg-layer bg-mask)
(gimp-edit-copy alpha-layer)
(set! floating-sel (car (gimp-edit-paste bg-mask TRUE)))
(gimp-floating-sel-anchor floating-sel)
(gimp-image-remove-layer img alpha-layer)
)
(script-fu-register "precompute-alpha"
"Precompute Alpha"
"Automatically precompute alpha"
"Thomas Arbona"
"2017"
"2017"
"*"
SF-IMAGE "Image" 0
SF-COLOR "Alpha Color" '(0, 0, 0)
)
(script-fu-menu-register "precompute-alpha" "<Image>/Alpha")
Just open your image in Gimp, open Alpha > Precompute Alpha and pick a color to pre-compute the alpha on your image with this color.
来源:https://stackoverflow.com/questions/19611745/opengl-black-lines-in-between-tiles