8
$\begingroup$

So I'm trying to correctly map my textures in my software renderer using the u, v coordinates but I can't seem to get it working. I got affine texture mapping working. This is what I can produce using it:

enter image description here

You can see from the "tilt" in the texture that it's not quite right. If I add more triangles to the mesh it becomes more correct. From my understanding these are not perspective correct textures. But this perspective divide thing is confusing me. I read that I needed to divide the u, v coordinates by the w component (perspective divide). I'm guessing that this is the w component from the vertex after multiplying with the projection matrix. But I couldn't get that too work. So I looked a little bit more into the problem. First, here's what is happening to one vertex of my triangles.

float4 pt1 = worldViewProj * mesh->vertices[mesh->indices[i]];
float2 uv1 = mesh->texCoords[mesh->indices[i]];
triangle.p1 = float3((pt1.x / pt1.w + 0.5f) * screen->GetScreenWidth(), (pt1.y / pt1.w + 0.5f) * screen->GetScreenHeight(), pt1.w);

I multiply it with the wVP and then do my perspective divide with x and y. The + 0.5f is to center it in homogeneous space and then multiply with the screen dimensions to get it in screen space. I then stuff the w component of the vertex as the z value so it will be sent to the triangle drawing function and can be used for things like the depth buffer.

To my understanding the z value from the vertex gets put into w after multiplying with the projection matrix. This makes sense since m32 = 1 in my projection matrix. So after multiplication with the projection matrix w = z. I first tried dividing the texture coordinates per vertex by w. Like so:

 float2 uv1 = mesh->texCoords[mesh->indices[i]] / w;

Then in the triangle drawing function I used the bary centric coordinates to interpolate across these the uv coordinates. This did not work. Since w = z. When the texture is far from the camera (ie z is a large value. With my world translations applied z = ~700) then the texture coordinates shrink a lot and in every render I just get the (0, 0) value of my texture.

But I also tried this instead in my triangle drawing function:

float2 texCoord = (texCoord1 * u + texCoord2 * v + texCoord3 * w) / z;

(Here u, v, and w are the bary centric coordinates. Each texCoord is the texture Coordinate at that vertex) The difference here is that the interpolated z value is being divided at every pixel instead of it being divided at the vertices. Either way I can't seem to get it too work. Again the z value is too large and I end up getting (0, 0).

So I'm investigating what I need to divide u and v by. Obviously I'm doing something wrong. Is it really the w component of the vertex after multiplying with the projection matrix? Is my w value wrong? Could the projection matrix be causing this? I've been googling around for awhile now... x / w and y / w are [-1, 1] (homogeneous) but z isn't [-1, 1]. Is it supposed to be? My perspective divide for x and y obviously works.

Here's how I built my projection matrix just in case it's wrong?:

float tanHalfFov = tanf(fov * 0.5f * 3.14f / 180.0f);
float s = 1.0f / (tanHalfFov * aspectRatio);
float zRange = farZ - nearZ;

// Set the projection matrix
proj.m00 = s;    proj.m01 = 0.0f;            proj.m02 = 0.0f;          proj.m03 = 0.0f;

proj.m10 = 0.0f; proj.m11 = aspectRatio * s; proj.m12 = 0.0f;          proj.m13 = 0.0f;

proj.m20 = 0.0f; proj.m21 = 0.0f;            proj.m22 = farZ / zRange; proj.m23 = farZ * nearZ / zRange;

proj.m30 = 0.0f; proj.m31 = 0.0f;            proj.m32 = 1.0f;          proj.m33 = 0.0f;
$\endgroup$
1
  • 2
    $\begingroup$ The projection matrix transforms points into clip space, which is a 2x2x2 cube centered at the origin, not what you're calling "screen space". Homogeneous coordinates means you have a w in addition to your spatial coordinates(x,y and possibly z). It has nothing to do with normalization. You never divide by Z - always W. The point of W is that you can perspective divide and keep depth (again, in clip space), and nicely lets you add translation into your matrix rather than having a separate step. You're mixing up a lot of different concepts. $\endgroup$
    – 3Dave
    Oct 5, 2016 at 22:23

1 Answer 1

10
$\begingroup$

You are on the right track but what you need to do is to calculate u/w and v/w, and also 1/w for each vertex, which you interpolate linearly in screen space in your rasterizer. Then for every pixel you divide the interpolated u/w and v/w coordinates with the interpolated 1/w to get perspective correct uv-coordinates for the pixel.

The same applies to all the vertex attributes, e.g. if you need to get perspective correct color or position of vertices per pixel, then you interpolate c/w and p/w and divide those values by 1/w per pixel. Even if you don't implement software rasterizer this can be quite useful for example in ray marching of screen-space reflection implementation. I have seen people doing the ray marching for SSR in world space because they don't know how to properly interpolate position in screen space.

What old software rasterizers used to do is to perform this 1/(1/w) division only every X pixels and linearly interpolate values in-between because the division was relatively expensive operation to perform every pixel.

$\endgroup$
2
  • $\begingroup$ Is 1/(1/w) a typo or something I've misunderstood? $\endgroup$ Oct 11, 2016 at 9:03
  • 2
    $\begingroup$ Not a typo. You interpolate 1/w linearly over the screen and calculate 1/(1/w) per pixel. Then multiply linearly interpolated u/w and v/w with the per-pixel 1/(1/w) value. Just a trivial optimization of turning divs to muls to avoid multiple divisions per pixel. $\endgroup$
    – JarkkoL
    Oct 11, 2016 at 12:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.