# UV texture mapping with perspective correct interpolation

I am working on a software rasterizer for educational purposes and I am having issues with the texturing.

The problem is, only one face of the cube gets correctly textured. The rest are stretched edges:

You can see the running program online here.

I have used cartesian coordinates, and all I do is interpolate the uv values along the scanlines. The general formula I use for interpolating the uv coordinates is pretty much the one I use for the z-buffering interpolation and looks like this (in this case for horizontal scanlines):

    u_Slope = (right.u - left.u) / (triangleRight_x - triangleLeft_x);
v_Slope = (right.v - left.v) / (triangleRight_x - triangleLeft_x);
//[...]
new_u = left.u + ((currentX_onScanLine - triangleLeft_x) * u_Slope);
new_v = left.v + ((currentX_onScanLine - triangleLeft_x) * v_Slope);


Then, when I add each point to the pixel buffer, I restore z and uv:

                z =  (1/z);
uv.u = Math.round(uv.u * z *100);//*100 because my texture is 100x100px
uv.v = Math.round(uv.v * z *100);


Then I turn the u v indexes into one index in order to fetch the correct pixel from the image data (which is a 1 dimensional px array):

        var index = texture.width * uv.u + uv.v;
//and the rest is unimportant imagedata[index].RGBA bla bla


The interpolation formula is correct considering the consistency of the texture (including the straight stripes).

I must get some sleep now, but before I get into further dissecting of every single value to see what goes wrong, Can someone more experienced guess why might this be happening, just by looking at the cube?

"I have no idea what I'm doing" (it's my first time implementing a rasterizer). Did I miss an important stage?

Thanks for any insight.

PS: My UV values are as follows:

    { u:0,   v:0 },
{ u:0,   v:0.5 },
{ u:0.5, v:0.5 },
{ u:0.5, v:0 },
{ u:0,   v:0 },
{ u:0,   v:0.5 },
{ u:0.5, v:0.5 },
{ u:0.5, v:0 }


## [EDIT]

The uv values were to blame, and pixel 50 being in the second zone, as Victor pointed out.

My new uv values:

                    {u:0, v:0},
{u:0, v:1},
{u:1, v:1},
{u:1, v:0},
{u:1, v:0},
{u:1, v:1},
{u:0, v:1},
{u:0, v:0}


To some extent this was "a bug in my code", but also I had overlooked an important part of the process: the uv values should not just be assigned to the 8 corners of the cube.

• What does this have to do with game development in particular? – Nicol Bolas Nov 15 '12 at 1:02
• Texturing is a common component of a game engine, perhaps he thinks this question would get better answers here. – Chewy Gumball Nov 15 '12 at 4:17
• Looks like a bug in OP code, somehow even/odd triangles get rasterized differently, despite the fact that the middle line is common and should be rasterized exactly the same. I don't see how bugfixing this gonna help anyone. Downvoted. – Kromster Nov 15 '12 at 5:42
• @Nicol the FAQ states something else, at least I understand that sentence differently: "Would a professional game developer give me a better/different/more specific answer to this question than other programmers?" This does not exclude general questions as long as a game-developer can answer it better. Almost every shader question would be excluded following the logic you presented. – Maik Semder Nov 15 '12 at 17:05
• @MaikSemder I agree. There are LOTS of graphics/shader questions on this site. I know because those are the kind I try to answer! :) Nicol's statement "this is not a website for asking general purpose questions about rendering" makes no sense to me. – Nathan Reed Nov 15 '12 at 18:08

I took a quick look at your example and your code. You're extremely close to solving this, so I don't mind helping with a question that looks suspiciously like homework ;).

In data.js, you are assigning the same UV coordinates to the front and rear faces of the cube. The coordinates seem to be ([0,0], [0,0.5], [0.5,0.5], [0.5,0]). This means that you can only get texels from inside that part of your texture, regardless of which side of the cube you draw.

Because the UV coordinates go all the way up to 0.5, you include a single-pixel edge of the "2" and "3" areas when you draw the "1" face of the cube. For instance, when the u coordinate is 0.5, you will multiply that by 100 and get 50, which is the first pixel of the "2" area.

Finally, this bit of code, from your post, looks slightly wrong:

var index = texture.width * uv.u + uv.v;


The 'u' coordinate is like the 'x' coordinate in your texture, so you don't want to jump by texture.width when you add 1 to u. If the texture image is stored in the normal way (with each row of the texture appearing in order), you want to change this math to

var index = uv.u + texture.width * uv.v;

• Why yes, it is homework! Well, sort of, I'm not really required to implement all this. There's no course info to follow, so I'm pretty much sailing the internets. Can't believe how blind I was last night, got confused by those bleeding edges. You are completely right. – Spectraljump Nov 15 '12 at 19:09
• So it seems that it is impossible to wrap a texture around a cube, unless you store UV information on a per-triangle basis. 8 points of uv data aren't enough: itu.dk/people/tdbe/webStorm/rasterizer_sandbox/index.html – Spectraljump Nov 15 '12 at 19:10

Golden rule when you are using floating point: count on every calculation to have an error in the result.

It looks like your texture mapping is mapping a bit of memory past the edge of the textures. This may be because of floating point error.

This part catches my attention:

z =  (1/z);
uv.u = Math.round(uv.u * z *100);//*100 because my texture is 100x100px
uv.v = Math.round(uv.v * z *100);


Each time this code runs, the variables will deviate from their original values. You should store and restore the original values of these variables, rather than recalculate them. Or create separate variables for the "temporary" values.

It's hard to tell from what you have cut'n'pasted but I suspect you are also "stacking" calculations new_u and new_v in a similar manner. Rewriting your formulae to not do this could help.

That link I gave? Don't read it, it's far too long :)

• See, checking for floating point errors would have been one of the last things I would have thought of. (And as it turns out, it's not the cause of my problem, but) It's very useful that you've pointed this out. I'll be more careful with my long and "weird" floats. – Spectraljump Nov 15 '12 at 19:18