Projecting textures and remapping [-1,1] to [0,1]

When doing projective textureing, we need to remap X, Y and Z form [-1,1] to [0,1]. This is clear for X and Y coordinates to me, but not enough clear for Z coordinate. It’s because in my OGL renderer I actually use D3D projection matrix:


xScale     0          0               0
0        yScale       0               0
0          0       zf/(zf-zn)         1
0          0       -zn*zf/(zf-zn)     0
where:
yScale = cot(fovY/2)

xScale = yScale / aspect ratio

And this matrix maps Z to range [0,1]. But such tex proj remapping matrix:


projTexCoordTransform = CMatrix
(
	0.5f, 0.0f, 0.0f, 0.0f,
	0.0f, 0.5f, 0.0f, 0.0f,
	0.0f, 0.0f, 1.0f, 0.0f,
	0.5f, 0.5f, 0.0f, 1.0f
);

simply doesn’t work. I need to use:


projTexCoordTransform = CMatrix
(
	0.5f, 0.0f, 0.0f, 0.0f,
	0.0f, 0.5f, 0.0f, 0.0f,
	0.0f, 0.0f, 0.5f, 0.0f,
	0.5f, 0.5f, 0.5f, 1.0f
);

which makes remapping [-1,1] -> [0,1]. The question is: why? Does OGL make some additional internal scaling and forces Z values to be in range [-1,1] or what?

with texturing you have control over the whole shebang so it doesn’t eally make sense to use a “d3d matrix”, use whatever makes sense for your application.

vertex pipeline is different since gl controls the final depth range mapping so you’ve gotta be careful there. i’d stick to the symmetric viewing cube and remap z for d3d if need be…

Honestly, I preffer Z in range [0,1] instead of [-1,1]. And I think I haven’t described the full problem. I need this projection to hardware shadow mapping cause I project the depth texture. So I render to DEPTH_COMPONENT_24 texture and if OGL rescales my Z in [0,1] (after projection) to [-1,1] I’m fine with that. I only need someone to make me sure OGL really does that and always hold Z in range [-1,1].

And I’d be pleased if someone could help me imagine how the OGL z-mapping function look like. I mean, in D3D we have a [0,1] range. And the function is logarithmic (objects closer to camera have greater precision). So how does this function look like in OGL if we have [-1,1] range?

In OpenGL visible vertices always lie in [-1,1] for x,y values and [0,1] for depth range(negative values [0,-1] lie behind the camera and are not rendered. Yep, same as direct 3D. AND we have logarithmic scale as well(which is a consequence of the projection transformation and finite precision, not the APIs)). Texture coordinates [s,t,r,q] on the contrary lie in range [0,1] for s,t (that’s why you have to do scaling), and r doesn’t really matter as long as you use the exact same transformations you used for the vertex z value(that’s the point of shadow maps anyway, compare two values that were generated in the same way). I don’t want to delve into the matrix maths of d3d(sorry, not in a math mood right now) but I’m sure the inconsistency derives from the different form of the projection matrix for the 2 APIs(well OBVIOUSLY, not much of a contribution here, see gluPerspective in http://www.opengl.org/sdk/docs/man/ for more info). For your question, if you use the OpenGL projection matrix used in the vertex shader(I mean, why not?You have access to it anyway), the first matrix you provided should work. Notice that you STILL have to scale s,t coordinates since [x,y]->[-1, 1], [s,t]->[0,1] even if you use the OpenGL projection matrix. Yes, visible vertices ALWAYS have z in range [0,1], so as I’ve said if you use the exact same transformation for texture coordinates…

If you’re talking about screen/window space that would be correct. But clip space is defined differently in OpenGL and D3D.

In OpenGL clip space Z is within [-Wclip, Wclip] while in D3D it is limited to [0, Wclip]. After the homogeneous division by W this results in normalized device coordinates (NDC) in the ranges [-1, 1] for OpenGL or [0, 1] for D3D, respecively. However, because the viewport transformation is also different, screen/window space Z ends up in the range [0, 1] in both cases. Or more precisely, [n, f] where n and f are the arguments to glDepthRange.

The vertex shader position output (gl_Position) is given in clip space. Thus you should not use the same projection matrix in OpenGL as you would in D3D (if you do, you will find the near clip plane is at half the distance compared to where you intended it to be). It also means that for a shadow texture lookup S, T, and R should be remapped just as maxest found it necessary.

Thanks for this explanation Xmas! It’s all correct what you’re saying, especially with this issue on using D3D proj. matrix in OGL. Indeed my zNear clipping plane is at half the distance. I didn’t notice that before (who would actually care about checking zNear if it’s always small anyway ;)).

Please, help me explain one more point then. I mean this [-1,1] depth range. “Where” is it? Is it that from camera point to zNear it’s [-1,0] and from zNear to zFar it’s [0,1]? If so how do these mapping functions look like (I mean what’s their plot)?
And how does it look like in case of D3D? [0,1] is from zNear to zFar, so what’s from camera point to zNear? All zeros?

sure would be nice if gl and d3d could see eye to eye on this one…

you may find it informative to plot some values on a graph and also to visualize the viewing frustum by back projecting the unit cube into world space. seeing is believing. :wink:

Doing the math is really simple. There are only two transformations involved in going from view space to normalized device coordinates (NDC):

  1. multiply the projection matrix with the eye position to get clip coordinates
  2. perform perspective division to yield normalized device coordinates

We can go through these transformations to derive the formulas for NDC and window space Z:

  1. If you are using glFrustum, the bottom half of the projection matrix (the top half is not relevant if we’re only interested in Z) looks like this, with n and f being the distances to the near and far clip planes, respectively:
0 0 -(f+n)/(f-n) -2fn/(f-n) 
0 0      -1          0

If we multiply this with an eye space vertex (Xeye, Yeye, Zeye, 1), we get clip coordinates as follows:
Zclip = -Zeye(f+n)/(f-n) - 2fn/(f-n)
Wclip = -Zeye

  1. To get normalized device coordinates, we divide x, y, and z in clip space by w. We’re only interested in Z, so this is the important bit:

Zndc = Zclip / Wclip = (f+n + 2fn/Zeye)/(f-n)

Note that vertices get clipped to -Wclip <= Zclip <= Wclip.
Let’s plug in some simple values for near and far clip distance: n=1 and f=10

Zndc = 11/9 + 20/(9*Zeye)
As we now see, for Zeye = -1 we get -1, and for Zeye = -10 we get 1. Why negative Zeye values? Well, because glFrustum is designed to give a projection matrix looking down the negative Z axis. That’s easily changed by scaling Z with -1 if necessary.
So at the near plane you get -1, at the far plane 1, and at 2fn/(f+n) it’s 0.

Here’s a nice graph plotted by Wolfram Alpha.
And here’s how it looks like inverting the sign of the Z axis:

Anything that’s in front of the near clip plane or behind the far clip plane will be clipped so it won’t have a Z value at all. But if you were to disable clipping, Zndc would approach +Inf for points just in front of the camera, -Inf for points just behind the camera, and you’d get a division by 0 for all points on the Zeye=0 plane.

I think I got the point. I would just like to ask two more questions.

First refers to:

Well, because glFrustum is designed to give a projection matrix looking down the negative Z axis.

Is this because gluLookAt builds camera-space in such a way that Z is actually negated (http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/glu/lookat.html) and in fact makes a right-handed coordinate system? If forward vector in camera-space is not negated, then the (4,3)-entry of glFrustum doesn’t need to be negated too?

I would also be very pleased if you could explain, in mathematical terms, why D3DX’s projection matrix causes OGL to have a near clip-plane half closer? All I have come up with is that when using this DX-like projection, we end up with z-values mapped to [0,1] whereas there is free space in OGL (at [-1,0]). So OGL “fills” that space with what is in front of near clip-plane in D3D (in fact it causes a longer frustum). But why is this actually half of this space?

I’m sory if my questions seem to be dull for you, but this whole Z-play in terms of both OGL and D3D can hardly be found anywhere.

Yes, it’s about creating a right-handed coordinate system, but you don’t need to use gluLookAt. If you consider that, in window space, positive X goes to the right and positive Y is up, a right-handed coordinate system implies that positive Z has to be directed towards the viewer.

Of course coordinate system orientation is a completely arbitrary choice, it only matters that you keep everything consistent.

I would also be very pleased if you could explain, in mathematical terms, why D3DX’s projection matrix causes OGL to have a near clip-plane half closer?

This is how the lower half of a typical (transposed) D3D projection matrix looks like:

0 0 f/(f-n) -nf/(f-n)
0 0   1        0

Thus we get
Zclip = (Zeye - n)f/(f-n)
Wclip = Zeye
Zndc = (1 - n/Zeye)f/(f-n)

We can easily see that for Zeye = n this is 0 and for Zeye = f this is 1, which is the range D3D clips to. However OpenGL clips to [-1, 1] instead (or rather, [-Wclip, Wclip] before division, but that doesn’t matter here). And the solution for Zndc = -1 for the above equation is Zeye = fn/(2f-n), which approaches n/2 for f -> +Inf. So the near clip plane ends up (very close to) half the distance compared to where you wanted it to be.

More on that here.

Thank you very much Xmas for this comprehensive explanation. I played a bit with these matrices and all what you’re saying is correct (not that I doubted it!). Again, thanks a lot.

Wow! Thanks too! I was under the wrong impression that z clip space was {0,1}. -Sorry for the incorrect info-