Skip to content

Fix artifacts on texture edges#61

Merged
misode merged 2 commits intomisode:mainfrom
jacobsjo:fix-nearest-neighbor-artefacts
Jul 6, 2025
Merged

Fix artifacts on texture edges#61
misode merged 2 commits intomisode:mainfrom
jacobsjo:fix-nearest-neighbor-artefacts

Conversation

@jacobsjo
Copy link
Contributor

@jacobsjo jacobsjo commented Jul 2, 2025

This fixes the artifacts that often happen on the edge of textures. Those are caused by the GPU interpolating in the wrong direction: At the very edge of the texture, (UV: 0), we are exactly at a texture border, i.e. equally far (0.5 pixels) from each pixel. Therefore texture2D(sampler, vTexCoord) in the fragment shader often picks the wrong direction.

To fix this, I've modified this to

texture2D(sampler, clamp(vTexCoord,
	vTexLimit.xy + vec2(0.5, 0.5) * pixelSize,
	vTexLimit.zw - vec2(0.5, 0.5) * pixelSize
))

vTexLimit is a vec4 that stores the uv of both edged of the texture on all vertices. This allows the fragment shader to determine on which edge of the texture it is.

The rest of the code changes are to give the fragment shader the necessary information: pixelSize and vTexLimit. The pixel size comes from a new method getPixelSize() in the TextureAtlasProvider. To provide backwards-compatability, that method is optional and defaults to 0 (which makes this fix not work).

fixes #57

@misode
Copy link
Owner

misode commented Jul 6, 2025

Testing this, I do indeed see a big improvement. But I can still notice some artifacts. Any idea where those are coming from?

Before After
image Screenshot 2025-07-06 191537

@jacobsjo
Copy link
Contributor Author

jacobsjo commented Jul 6, 2025

Ah I forgot the case when textures aren't a full 16x16. The trapdoor reuses the top texture for the sides, so the black lines you see in the artifact come from the holes in the top texture. This is probably an easy fix by making the texLimit match the actual size of the used texture.

@jacobsjo
Copy link
Contributor Author

jacobsjo commented Jul 6, 2025

Should be fixed now. I've also removed the uvEpsilon part. It seems to me that this was an earlier attempt to reduce the impact of this artifact and isn't needed anymore now.

@misode
Copy link
Owner

misode commented Jul 6, 2025

Okay this is amazing. This PR fixes the stitching issue that I had pretty much given up on fixing. And it does so in a pretty understandable and not super complicated way. Thank you!

Before After
image image
image image

@misode misode merged commit d8ec585 into misode:main Jul 6, 2025
1 check passed
KokeCacao pushed a commit to KokeCacao/deepslate that referenced this pull request Jul 7, 2025
* Fix artefacts on texture edges

fixes misode#57

* set textureLimit according to uv of face

and remove uvEpsilon
@KokeCacao
Copy link

KokeCacao commented Jul 7, 2025

Should be fixed now. I've also removed the uvEpsilon part. It seems to me that this was an earlier attempt to reduce the impact of this artifact and isn't needed anymore now.

Removing uvEpisilon makes some pixels transparent when looking from afar (see: #60 (comment)) @jacobsjo
Edit: However, not removing it causes the edge not to render correctly when looking closely.

Edit2: Finally figured it out. It turns out that moving the clamp coordinate calculation from the shader to TypeScript can resolve the minor texture bleeding issue from afar while keeping everything else intact.


Before: About 9 pixels (mostly dirt) are bleeding.
Image

After: None of the dirt pixels are bleeding.
Image

The fix:

	const [u0, v0, u1, v1] = atlas.getTextureUV(this.getTexture(face.texture))
	const du = (u1 - u0) / 16
	const dv = (v1 - v0) / 16
	const duu = atlas.getPixelSize() * 0.5
	const dvv = atlas.getPixelSize() * 0.5
	uv[0] = (face.uv?.[0] ?? uv[0]) * du
	uv[1] = (face.uv?.[1] ?? uv[1]) * dv
	uv[2] = (face.uv?.[2] ?? uv[2]) * du
	uv[3] = (face.uv?.[3] ?? uv[3]) * dv
	const r = faceRotations[face.rotation ?? 0]
	quad.setTexture([
		u0 + uv[r[0]], v0 + uv[r[1]],
		u0 + uv[r[2]], v0 + uv[r[3]],
		u0 + uv[r[4]], v0 + uv[r[5]],
		u0 + uv[r[6]], v0 + uv[r[7]],
	], [
		u0 + Math.min(uv[0], uv[2]) + duu,
		v0 + Math.min(uv[1], uv[3]) + dvv,
		u0 + Math.max(uv[0], uv[2]) - duu,
		v0 + Math.max(uv[1], uv[3]) - dvv,
	])
	mesh.quads.push(quad)
  void main(void) {
    vec2 clampedCoord = clamp(vTexCoord, vTexLimit.xy, vTexLimit.zw);
    vec4 texColor = texture2D(sampler, clampedCoord);
    gl_FragColor = vec4(texColor.xyz * vTintColor * vLighting, texColor.a);
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Trapdoor item models render with gaps

3 participants