Line length in pixels in perspective camera Topic is solved

Problems building or running the engine, queries about how to use features etc.
User avatar
bishopnator
Gnome
Posts: 328
Joined: Thu Apr 26, 2007 11:43 am
Location: Slovakia / Switzerland
x 15

Line length in pixels in perspective camera

Post by bishopnator »

Ogre Version: ogre-next 3.0
Operating System: Windows
Render System: D3D11

Does anybody know how to calculate pixel position of the vertex outside the frustum? Let's say I have a line rendered with perspective camera and I would like to calculate its length in pixels. If both vertices are visible, then simple multiplication with projection matrix + applying the RT resolution yield the desired values (example as in HLSL):

Code: Select all

float4 p0proj = mul(p0, worldViewProjMatrix);
float4 p1proj = mul(p1, worldViewProjMatrix);
float2 p0screen = (p0proj.xy / p0proj.w + float2(1, 1)) * 0.5 * float2(screenWidth, screenHeight);
float2 p1screen = (p1proj.xy / p1proj.w + float2(1, 1)) * 0.5 * float2(screenWidth, screenHeight);
float lineLength = length(p1screen - p0screen);

But the code above gives incorrect results if one of the vertices is clipped by near plane.

As one solution, it is possible to clip the line manually and calculate only the length of visible line segment, but I would like to avoid this manual clipping and rather calculate size of the whole line and let the interpolation on GPU. Is there any trick with projection matrix?

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5477
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1359

Re: Line length in pixels in perspective camera

Post by dark_sylinc »

Perspective projection makes the object appear bigger as you it gets closer.

Mathematically this happens because of homogeneous coordinates making the W component smaller as you get close to the near plane. For the same XY orthographic point [1, 1, z] (we don't care about z in this example):

xyz = [1, 1, z] / [w = 4] = [0.25, 0.25, z]
xyz = [1, 1, z] / [w = 2] = [0.5, 0.5, z]
xyz = [1, 1, z] / [w = 1] = [1, 1, z]
xyz = [1, 1, z] / [w = 0.5] = [2, 2, z]
xyz = [1, 1, z] / [w = 0.25] = [4, 4, z]
xyz = [1, 1, z] / [w = 0.125] = [8, 8, z]
xyz = [1, 1, z] / [w = 0] = [inf, inf, z]

Note: At w = 1 it means you're exactly at the near plane.

The reason it doesn't work is because once you go behind the near plane, the value of w stops making sense.
And if you keep going backwards w becomes negative and the coordinates flip:

In this picture you can see the gradient appears "bigger" as the gap between the frustum bounds is smaller. But once it goes behind (w < 0), the gradient flips. Note that by "behind" I mean behind the origin. There's still a portion between the near plane and the origin that won't be rendered (i.e. it's behind the near plane) but w >= 0.

Perhaps you can make it work by making w = abs( w ); but still, I don't think it makes sense since the object now shrinks as it keeps getting away from behind. Perhaps by multiplying by abs( w ) when it's negative instead of dividing you get what you want? There's still the issue of dealing what to do with w while it's in the range [-1; 1]

I don't know how to solve it because it makes no sense. However I hope by explaining what's going on, perhaps you can come out with a way to achieve what you want.

User avatar
bishopnator
Gnome
Posts: 328
Joined: Thu Apr 26, 2007 11:43 am
Location: Slovakia / Switzerland
x 15

Re: Line length in pixels in perspective camera

Post by bishopnator »

Hi, I am trying to figure out the proper fix/correction for the problem already over a week, but without significant success :-( I remember that when I implemented a printing module which converted a 3D scene (wireframe) to a PDF (so 2D vector output), I had to clip all the geometry against all clipping plane of the frustum. I did that in homogeneous coordinates which is pretty straightforward and works for both type of projections (orthographic and perspective). In OpenGL the points are projected to the (-1, 1) ranges in X, Y axis and (0, 1) in Z axis (after division by w component. In D3D the Z is mapped to (-1, 1). So the clipping planes in homogeneous coordinates are in OpenGL:

Code: Select all

	left:   w + x = 0
	right:  w - x = 0
	bottom: w + y = 0
	top:    w - y = 0
	near:   z = 0
	far:    w - z = 0

and in D3D the near is changed to w + z = 0
(if each of the equation is divided by w, then it is clear or viceversa - if the points on the near-clip plane have after perspective division z = -1, then before perspective division it is z * w = -w ===> z * w + w = 0 and the z * w is the z coordinate of the point in homogeneous space).

The clipping function in HLSL can be implemented as:

Code: Select all

void clipLine(inout float4 p0, inout float4 p1, const float4 clipPlane)
{
	const float p0side = dot(p0, clipPlane);
	const float p1side = dot(p1, clipPlane);
	if (p0side * p1side < 0)	
	{
		// projected line between p0 and p1 crossed homogeneous plane
		const float4 dir = p1 - p0;
		const float t = -p0side / dot(dir, clipPlane);
		const float4 intersection = p0 + t * dir;
		if (p0side < 0)
			p0 = intersection;
		else
			p1 = intersection;
	}
}

and e.g. used in geometry shader as:

Code: Select all

	float4 p0 = input[0].position;
	float4 p1 = input[1].position;
	clipLine(p0, p1, float4( 1,  0,  0, 1));
	clipLine(p0, p1, float4(-1,  0,  0, 1));
	clipLine(p0, p1, float4( 0,  1,  0, 1));
	clipLine(p0, p1, float4( 0, -1,  0, 1));
	clipLine(p0, p1, float4( 0,  0,  1, 1)); // note that in GLSL it is necessary to use here float4(0, 0, 1, 0)
	clipLine(p0, p1, float4( 0,  0, -1, 1));

If I clip the line, compute the pixel length of the line segment, the rendering is as expected. I don't however understand, why I am getting glitches, when I leave only clipping to near-clip plane. I would like to reduce the clipping as it is a lot of operations in geometry shader.

User avatar
bishopnator
Gnome
Posts: 328
Joined: Thu Apr 26, 2007 11:43 am
Location: Slovakia / Switzerland
x 15

Re: Line length in pixels in perspective camera

Post by bishopnator »

Small correction - the OpenGL uses z-coord in range (-1, 1) and D3D in range (0, 1). The problem is that I cannot simply clip in homogeneous coordinates with plane z=0. The glitch happens when one vertex has negative w and another positive (both vertices have approx. same positive z). So I run my clipLine function with float4(0, 0, 1, 0), it just returns as the z doesn't cross the 0 value. But after dividing by w, it crosses. I cannot clip to w=0, because then there will be divisions by 0.

User avatar
bishopnator
Gnome
Posts: 328
Joined: Thu Apr 26, 2007 11:43 am
Location: Slovakia / Switzerland
x 15

Re: Line length in pixels in perspective camera

Post by bishopnator »

Finally I figured out the details - the trick was that by default, the ogre uses reversed depth - which means that the points on the near clip plane after w division have z = 1 and on the far plane z = 0. The near clip plane hence is z/w = 1 ==> z = w ==> w - z = 0 and with only this active clipping my code seems to work as expected. It is even advantage that GL3Plus and D3D11 use by default reversed depth and projection matrix is same - I don't have to update the code according to render system.