Sign in to follow this  
denisve

Strange artifacts on stiches, when sampling from 2 textures

Recommended Posts

Hi, all.

 

I'm working on terrain editor, and the idea so far, is to have a set of textures I could place on each square with UV coords 1x1. I wrote a simple shader, which samples from 2 different textures, depending on UV coords, but for some reason, even though the textures should connect seamlessly, I'm having a strange artifact pixels on the seams. Now the really funny part, is that it works perfectly on a crappy Intel on-board video card, and doesn't work on my GTX500 with drivers up to date (!!!)

 

For instance, on an arbitrary hexagonal surface I sample everything, except for one square from grass texture, and one square from desert texture.

 

rsFhQDF.jpg

 

Magnified (Where are those pixels sampled from exactly? o.O)

 

ssMy9Gg.png

 

Vertex shader code:

precision highp float;
precision highp int;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv

void main()
{
	vUv = uv;
	vec4 mvPosition;
	mvPosition = modelViewMatrix * vec4( position, 1.0 );
	gl_Position = projectionMatrix * mvPosition;
}

Fragment shader code:

precision highp float;
precision highp int;

uniform vec3 diffuse
uniform float opacity;
varying vec2 vUv;
uniform sampler2D map;
uniform sampler2D map1;

void main()
{
	gl_FragColor = vec4( diffuse, opacity );

	vec4 texelColor;
	if(vUv.x >= 0.0 && vUv.x < 1.0 && vUv.y >=0.0 && vUv.y < 1.0) // <- Sample from a different texture for that square
		texelColor = texture2D( map1, vUv );
	else
		texelColor = texture2D( map, vUv );

	gl_FragColor = gl_FragColor * texelColor;
}

JSFiddle link (sorry for a big chunk of code, embedded textures take some space). The code is first 100 lines, everything below is THREE.js code to render the scene (Scroll wheel: dolly in/out, left click: rotate, right click: pan) -> https://jsfiddle.net/denisve/aucxz90s/

 

What am I doing wrong? The code is pretty straight forward, just sample from a different texture depending on UV.

 

Thanks in advance,

Denis.

Edited by denisve

Share this post


Link to post
Share on other sites

could you show us what do you expect it to be, actually i don't understand your problem at all.

 

and explain it a little bit further coz:

 

 

and the idea so far, is to have a set of textures I could place on each square with UV coords 1x1.

 

doesnt say to me anything.

Share this post


Link to post
Share on other sites

I expect it to be as it is now, just without the artifacts on the seams.

 

The idea is very simple: I have a UV mapped mesh, I have a set of textures. Now depending on UV coords I sample from different textures, each textures covers UV square of size 1 x 1.

 

See the image below, hope it explains better. Yellow lines denote faces, red grid is the UV grid, each square is 1 x 1. What I want, is for each square on the grid, the shader would sample from a different texture. See fragment shader code in OP.

 

ztoAesm.jpg

Share this post


Link to post
Share on other sites

 

expect it to be as it is now, just without the artifacts on the seams.

 

 

Were are these artifacts?

 

 

now to that second image it seems that you store even 3 faces on 1x1 square

 

 

do you have artifacts on these yellow lines? i assume those black tiny squares (black grid) are  screen pixels?

 

 

its hard for me to understand what is what 1x1 square doesnt say to me anything. but i assume if you have artifaces near yellow lines then you will have to sample all textures that are near the pixel line and maybe i dont know divide that by the amount of textures sampled for pixel.

(rgb1 + rgb2 + rgb3 )/ 3

 

 

or maybe you dont use squared textures since its not es3 (opengl ES is strict for that and you always should use textures like: 2x2 4x4 8x8 16x16 32x32 64x64, not 32x16 or 16x32 <- those are not valid for es 2.0 context), maybe you set clamping or repeating and shader leprs between last and first texel of texture that results in wrong color so maybe setting texture filtering to gl_nearest could somehow fix the problem but then you will get pixeloze)

 

im waiting for more info 

 

 

 

 

each textures covers UV square of size 1 x 1.

 

 

You mean in that image you have more than 8*5 = 40 ? textures?

 

 

explain please how you think the algorithm should work.

 

 

 

Or maybe do you just put a set of faces on a grid and make their vertices to be UV'ed accordingly to grid position?

so then it may be the problem with lerping between two colors from one texture and not taking into account the second texture (you could also show textures)

Edited by WiredCat

Share this post


Link to post
Share on other sites
Do your textures have mipmaps? Try turning those off and see if it makes a difference. The mip level selected during a sample will depend on the texture coordinate gradient between adjacent pixels. you have conditional logic to sample from one texture or the other, so weird things might happen at that "boundary"

Share this post


Link to post
Share on other sites
Try restructuring your shader to look like this:
void main()
{
	gl_FragColor = vec4( diffuse, opacity );
	vec4 a = texture2D( map1, vUv );
	vec4 b = texture2D( map, vUv );

	vec4 texelColor;
	if(vUv.x >= 0.0 && vUv.x < 1.0 && vUv.y >=0.0 && vUv.y < 1.0) // <- Sample from a different texture for that square
		texelColor = a;
	else
		texelColor = b;

	gl_FragColor = gl_FragColor * texelColor;
}
Calling texture2D inside a non-uniform if block can result in undefined behaviour, so you should hoist them out of the if/else and do them all the time. Then you can select which result you want.

Share this post


Link to post
Share on other sites

 

 

Were are these artifacts?

 

 

 

See the images I linked above in my first post. The "desert" square has a border of weird pixels around it, you can also see it on the magnified image.

 

And yes, I'm planning to have 40 different textures, or even more. This was just an example, so I've passed 2 uniforms, the real implementation is going to be one big texture atlas and I'm going to sample from it directly.

Share this post


Link to post
Share on other sites

Do your textures have mipmaps? Try turning those off and see if it makes a difference. The mip level selected during a sample will depend on the texture coordinate gradient between adjacent pixels. you have conditional logic to sample from one texture or the other, so weird things might happen at that "boundary"

 

Mipmaps is an issue I'll have to solve separately, since I'm going to use atlas texture, so I'll have to implement something to avoid pixel bleeding. I'm not there yet, though, far from it. Just trying to basic code right now, to see it it will work at all.

Share this post


Link to post
Share on other sites

Try restructuring your shader to look like this:

void main()
{
	gl_FragColor = vec4( diffuse, opacity );
	vec4 a = texture2D( map1, vUv );
	vec4 b = texture2D( map, vUv );

	vec4 texelColor;
	if(vUv.x >= 0.0 && vUv.x < 1.0 && vUv.y >=0.0 && vUv.y < 1.0) // <- Sample from a different texture for that square
		texelColor = a;
	else
		texelColor = b;

	gl_FragColor = gl_FragColor * texelColor;
}
Calling texture2D inside a non-uniform if block can result in undefined behaviour, so you should hoist them out of the if/else and do them all the time. Then you can select which result you want.

 

 

That actually solved it! Lots of thanks! Could you perhaps link me to an article that explains why sampling textures shouldn't be done inside if? Also what other functionality should be avoided?

Share this post


Link to post
Share on other sites

Mipmaps is an issue I'll have to solve separately, since I'm going to use atlas texture, so I'll have to implement something to avoid pixel bleeding. I'm not there yet, though, far from it. Just trying to basic code right now, to see it it will work at all.

 

But the artifact you're seeing is exactly what you'd see where there is a discontinuity in texture sampling coordinates and it samples from the wrong mip level. The fix being what Xycaleth posted.

 

The shader needs information from adjacent pixels in order to calculate the mip level. So if one of those adjacent pixels has undefined texture coordinates (or say a sharp discontinuity if you are sampling from a different part of a texture atlas), then your mip level will be wrong.

Edited by phil_t

Share this post


Link to post
Share on other sites


That actually solved it! Lots of thanks! Could you perhaps link me to an article that explains why sampling textures shouldn't be done inside if? Also what other functionality should be avoided?

 

The condition of the if statement that has been driving those samplings is quite not full proof, since you cannot compare floating numbers for equality, maybe some drivers compiled them from this

 

if(vUv.x >= 0.0 && vUv.x < 1.0 && vUv.y >=0.0 && vUv.y < 1.0) // <- Sample from a different texture for that square
        texelColor = texture2D( map1, vUv );
    else
        texelColor = texture2D( map, vUv );

to this

 

if(vUv.x > 0.0 && vUv.x < 1.0 && vUv.y >0.0 && vUv.y < 1.0) // <- Sample from a different texture for that square
        texelColor = texture2D( map1, vUv );
    else
        texelColor = texture2D( map, vUv );

What resulted in more lineary propagated distribution of sampling, instead of falling randomly from texture A to texture B at too close floats, falling for equality treshold.

Share this post


Link to post
Share on other sites
You can also replace the expensive branch instruction with a few ALU instructions biggrin.png
#define saturate(x) clamp(x,0,1)
vec4 a = texture2D( map1, vUv );
vec4 b = texture2D( map, vUv );
float alpha = (saturate(x)*saturate(1-x)) > 0 ? 1.0f : 0.0f;
texelColor = mix( a, b, alpha );
Saturate is often a free instruction modifier (e.g. mul_sat instead of mul).
The ternary operator (a?b:c) is usually compiled into a cmov instead of a branch.

Share this post


Link to post
Share on other sites

Thanks for your inputs, guys.

 

In any case, I'm going to implement this differently, with no need for ifs.

Going to pass a map via texture, which I'm going to sample, according to UV coords, then with the index I've received, going to sample the atlas at specific location.

 

The only thing remaining is mipmaps though.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this