Canvas Texture Seams/Artifact Lines at Repeat Border

I posted my bug report here, but it got closed down as a request for “help”.

I’m using CanvasTexture and seeing seams/artifact lines at the repeat border of the texture. See the link above for lots more information and pictures.

Any ideas?

Thanks.

I’ve read your issue at GitHub but do you think it’s possible to reproduce the issue with a live example?

https://jsfiddle.net/txzk1rLb/1/

TBH, it was correct to close the issue at GitHub since this is unlikely a bug in the engine.

2 Likes

…this is unlikely a bug in the engine.

How could you know that though? That’s such an arbitrary thing, especially considering this is browser + WebGL graphics we’re talking about here, and that the Github Issues section is full of similar problems (including specific seam/artifact lines problems).

…reproduce the issue with a live example?

Done: https://jsfiddle.net/579hwa0d/1
(Skip past the code at the top, the main code to see is the GrassMaterials array creation and the GameMap class. Uncomment line 109 to see the actual canvases.)

Just to make sure we see the same thing, mine looks like this:


Those thin white lines are the problem. The thicker ones are intentional, they will be located at where there’s a height difference between the grid cells.

Bonus points, by the way, if you can explain to me or point me in the right direction as to what the heck is going on with that faceVertexUvs block of code that I pulled from S.O…

Thanks again.

Added mouse scroll zoom at the bottom: https://jsfiddle.net/gz8bfd5c

Edit: Interesting, it’s actually drawing the textures beyond the edge of the map, it seems (you can see it slightly extending beyond the other textures when there’s a border drawn at the edge of the map, or something like that):
image

How could you know that though? That’s such an arbitrary thing, especially considering this is browser + WebGL graphics we’re talking about here, and that the Github Issues section is full of similar problems (including specific seam/artifact lines problems).

If you really think it’s a bug in the library then it’s up to you to prove it’s a bug, which usually involves a simple repro case. If you can’t reproduce it in a simple case then it’s not a problem with the library. With so many projects being built it’s much more likely that a problem stems from a mistake in how the library is being used rather than the library itself – it happens.

The artifacts you’re pointing to are commonly associated with texture wrapping, which defaults to clamp to edge but you’ve set to repeat. If you change this line:

let canvasTexture = new THREE.CanvasTexture(fixedContext.canvas, THREE.UVMapping,
        THREE.RepeatWrapping, THREE.RepeatWrapping);

to this

    let canvasTexture = new THREE.CanvasTexture(fixedContext.canvas, THREE.UVMapping);

you’ll fix your issue.

2 Likes

If you change this line:

let canvasTexture = new THREE.CanvasTexture(fixedContext.canvas, THREE.UVMapping,
        THREE.RepeatWrapping, THREE.RepeatWrapping);

to this

    let canvasTexture = new THREE.CanvasTexture(fixedContext.canvas, THREE.UVMapping);

you’ll fix your issue.

Yeah that’s what I originally had, but it yields this:


which is not what I want. It needs to repeat across the geometry, like it does in the other pictures.

				case RepeatWrapping:

					uv.x = uv.x - Math.floor( uv.x );
					break;

				case ClampToEdgeWrapping:

					uv.x = uv.x < 0 ? 0 : 1;
					break;

The artifacts you’re pointing to are commonly associated with texture wrapping…

And by the way I’m pretty sure this is aka. a bug.

The artifacts you’re pointing to are commonly associated with texture wrapping…

And by the way I’m pretty sure this is aka. a bug.

That’s definitely not what it is. I’m sorry it doesn’t work the way you want it to but this is intended behavior by the graphics API. It is useful in some cases. Either way three.js has no control over it.

Bonus points, by the way, if you can explain to me or point me in the right direction as to what the heck is going on with that faceVertexUvs block of code that I pulled from S.O…

That aside if you can’t explain why the code you’re using is doing what it is I think it’s awfully hasty to claim there’s a bug in the library you’re using.

Yeah that’s what I originally had, but it yields this:

Unfortunately you can’t have it both ways by just setting material or texture properties. It’s the job of the developer to work within the known and well documented limitations of the graphics APIs to get the behavior they want.

One option is to selectively pick the wrap mode based whether the UVs / geometry and whether it needs to wrap or carefully create the UVs to accomodate this behavior. Or you can do something like write a custom shader so it samples the way you want, though, but that’s a more complicated endeavor.

1 Like

I tried to read your example, but I have no idea what you’re trying to do there.

You have somewhat of an attitude, which doesn’t makes me want invest the time to parse your code. I’m sure others might thought.

For me though, I would ask if you could make a simpler example. Say with 2 or 4 textures instead of 16, and strip out all of the irrelevant stuff. If possible - don’t generate textures in code, just load them as PNG or something. Or clearly comment sections of your code so I know what to skip.

Let’s say this is a bug in three.js - if it only affects you, and nobody understand it, no one will fix it except for you. For example - It doesn’t affect me personally, so I have no stake here.

Let’s say it’s a bug in the browser - you would need to prove that, and this community is not the right place to address such a bug to.

I’m not saying that to start a fight or anything, it seems your expectations don’t really match up with how bugs and errors are actually handled in this community. I hope this helps.

Based on the information so far - I guess that it is not a bug in either the browser or the library. Instead, I believe it is an artifact of correct behaviour of APIs being used. Things like precision loss, rounding and sampling. It might still be interesting to figure out exactly why you see this type of behaviour, and I’m guessing that you would be interested to achieve some other outcome.

That being said - you path is pretty much the same, you can claim it’s a bug, or not. Point is - you see some unexpected behaviour and the results produced are not what you desire. Analyzing the problem is the right way forward. You already have put some effort into it, we just need a bit more to isolate the problem.

…I have no idea what you’re trying to do there.

I explained it in the Github issue.

Or clearly comment sections of your code so I know what to skip.

Did that.

For example - It doesn’t affect me personally, so I have no stake here.

Then why are you even here? Nice talking to you.

That’s what I mean by attitude :slight_smile:

I have read your github issue, and I have looked through your code. There’s very little in the way of comments in there. When you say

this tell me very little.

I do not have a stake in the problem you are describing. I do have a stake in the library’s quality overall, since I use it also.

I do think you would benefit from acting on those suggestions though. I’m just an idiot who can’t read JS code, and a three.js noob, but if you make your code clearer and more concise - it would make the problem easier to understand for those far above my skill level as well.

My suggestions were aimed at helping you solve the issue you are facing, since this is not working. I’m out.

Welcome to the forums and hope you’ll get your problem solved :wave:

1 Like

When you say

this tell me very little.

Skip past the code at the top, the main code to see is the GrassMaterials array creation and the GameMap class.

1 Like

Good news, I fixed it! :slight_smile:


The solution involved 2 things:

let canvasTexture = new THREE.CanvasTexture(context.canvas,
    THREE.UVMapping,
    THREE.RepeatWrapping, THREE.RepeatWrapping,
    THREE.NearestFilter, THREE.NearestMipmapNearestFilter);

AND I had to use my SMAAPass post-processing filter, e.g.:

this.effectComposer = new EffectComposer(this.renderer);
...
let renderPass = new RenderPass(this.scene, this.camera);
this.effectComposer.addPass(renderPass);
let smaaPass = new SMAAPass(window.innerWidth * this.renderer.getPixelRatio(), window.innerHeight * this.renderer.getPixelRatio());
smaaPass.renderToScreen = true;
this.effectComposer.addPass(smaaPass);

(P.S.: Antialiasing had no effect either way.)


So I did in fact want RepeatWrapping, but I had to use NearestFilter to stop it from sampling multiple pixels.

This article helped to clear up a lot. I had seen it earlier - and I had tried all these - but I wasn’t using the SMAAPass at the time. It says:

THREE.NearestFilter

same as above, choose the closest pixel in the texture

THREE.LinearFilter

same as above, choose 4 pixels from the texture and blend them

THREE.NearestMipmapNearestFilter

choose the appropriate mip then choose one pixel

THREE.NearestMipmapLinearFilter

choose 2 mips, choose one pixel from each, blend the 2 pixels

THREE.LinearMipmapNearestFilter

chose the appropriate mip then choose 4 pixels and blend them

THREE.LinearMipmapLinearFilter

choose 2 mips, choose 4 pixels from each and blend all 8 into 1 pixel

So as you can see, my choices of THREE.NearestFilter, THREE.NearestMipmapNearestFilter are the only options that would fix this, since they do not blend together any extra pixels. (FYI, I had tried messing around with the RepeatWrapping math in Three’s code in a bunch of different ways after making this post; nothing I tried had any effect.)

Also I didn’t need any of my canvas clipping/redrawing tests, the problem had nothing to do with the canvas graphics or bounds.

Thanks for your help, you at least pointed me at the right general settings to look at.


On a separate note, through more digging through the Three JS docs, the actual Three JS code, and this article (only article I know of on the entire internet that somewhat helpfully explains Three JS UV mapping…), and then comparing all that information with the faceVertexUvs code, I added some comments:

//All of this code only needs to be evaluated once because the X and Z coordinates don't change.
//Need normals to be calculated before calculating faceVertexUvs.
this.topGeometry.computeFaceNormals();
this.topGeometry.faces.forEach(function(face) {
    //Sort the normals, the direction of the face XYZ components.
    //Only the highest 2 will be utilized; Y has been ommitted for optimization.
    let components = ['x', 'z'].sort(function(a, b) {
        return Math.abs(face.normal[a]) > Math.abs(face.normal[b]);
    });

    //Get the vertices for each point of the face triangle.
    let v1 = this.topGeometry.vertices[face.a];
    let v2 = this.topGeometry.vertices[face.b];
    let v3 = this.topGeometry.vertices[face.c];

    //Specify the U & V texture mapping coordinate for each vertex of the face triangle.
    //U & V values range from 0.0-1.0 as a percentage of the texture width and height, but
    //RepeatWrapping will take care of this math just fine, since this is a "regular grid".
    this.topGeometry.faceVertexUvs[0].push([
        new THREE.Vector2(v1[components[0]], v1[components[1]]),
        new THREE.Vector2(v2[components[0]], v2[components[1]]),
        new THREE.Vector2(v3[components[0]], v3[components[1]])
    ]);
}.bind(this));

So I did in fact want RepeatWrapping , but I had to use NearestFilter to stop it from sampling multiple pixels.

AND I had to use my SMAAPass post-processing filter, e.g.:

So as you can see, my choices of THREE.NearestFilter, THREE.NearestMipmapNearestFilter are the only options that would fix this

Just to be clear this will also introduce other side effects which you may or may not be okay with. The solutions I mentioned would not have these side effects. A couple things to note:

  • using a variant of NearestFilter for minFilter or magFilter will make the textures look pixelated when zooming in close or cause sharp transitions in mipmaps when looking at the texture from an oblique angle.
  • SMAA should not be needed to hide these artifacts when using NearestFilter and will introduce a few extra full screen quad draws which might not be desired on some lower end platforms.
1 Like

Even better news! Given gkjohnson’s ^ feedback regarding NearestFilter (and I agree with you about SMAA, that should not be needed for the solution), and given my improved understanding of UV coordinates, I got it working without either of those!


Before I delve into it, I will note that the UV coordinates described in this article (about Three JS!) - and also in some StackExchange posts - detailed the UV coordinates being oriented like this:

//Corners in UV coordinates:
//   (0, 1)   Back  (1, 1)
//          0-----1
//    Left / Top / Right
//        3-----2
//(0, 0)  Front  (1, 0)
//Note: ^ This ^ appears to be incorrect for Three JS, see below.

(bottom left being (0, 0) and top right being (1, 1)), where the coordinate system is of course like this:

//Directions:
//  -Z    /\+Y
//-X  +X  ||
//  +Z    \/-Y

But in order to get it working, I had to use this:

//Corners in UV coordinates:
//   (0, 0)   Back  (1, 0)
//          0-----1
//    Left / Top / Right
//        3-----2
//(0, 1)  Front  (1, 1)

(top left being (0, 0) and bottom right being (1, 1)). The only way I figured this out was by alert()ing what the working code was doing.

Once I had this diagram in my comments, it was easy to figure out how to manually specify the UV coordinates (note that I don’t have to do the XYZ component sorting here because the normals would all be like (X: 0, Y: 1, Z: 0), Y would get sorted to the end, and X and Z would be utilized):

//First face is corners (0, 3, 1).
this.topGeometry.faceVertexUvs[0].push([
    new THREE.Vector2(0.0, 0.0),
    new THREE.Vector2(0.0, 1.0),
    new THREE.Vector2(1.0, 0.0)
]);
//Second face is corners (2, 1, 3).
this.topGeometry.faceVertexUvs[0].push([
    new THREE.Vector2(1.0, 1.0),
    new THREE.Vector2(1.0, 0.0),
    new THREE.Vector2(0.0, 1.0)
]);

(I do this for each pair of Face3 triangles, with the corners indicated in the comments above.)

This works beautifully! Even without SMAA or any customization settings for the CanvasTexture!


(Defaults: mapping: THREE.UVMapping (of course), wrapS and wrapT: THREE.ClampToEdgeWrapping, magFilter: THREE.LinearFilter, minFilter: THREE.LinearMipmapLinearFilter)

So you are correct, gkjohnson. Thanks again.


Since it is now possible, I’ll update the JSFiddle to show the working code now: https://jsfiddle.net/7052h469
(Skip past the code at the top, the main code to see is the GrassMaterials array creation and the GameMap class. Uncomment line 94 to see the actual canvases.)

It looks like this: