0
votes

I'm trying to understand how far should I place the camera position in the lookat function (or the object in the model matrix) to have pixel-perfect coordinates to pass in the vertex shader.

This is actually simple with orthographic projection matrices, but I fail to visualize how the math would work for perspective projection.

Here's the perspective matrix I'm using:

glm::mat4 projection = glm::perspective(45.0f, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 10000.0f);

vertex multiplication in the shader is as simple as:

gl_Position = projection * view * model * vec4(position.xy, 0.0f, 1.0);

I'm basically trying to show a quad on screen that needs to be rotated and show perspective effects (hence why I can't use orthographic projection), but I'd like to specify in pixel coordinates where and how big it should appear on screen.

4
What are you actually trying to achieve? Specifying the four corners of a trapezoid in pixel coords, and you want some perspective corrected interpolation across the whole thing? If so, focussing on the projection matrix is a bit misleading (One of course could put any arbitrary homogrophy into the projection matrix, but the classical perspective functions won't be helpful in that case).derhass
I'm trying to figure out how to express vertex coordinates in screen pixel to show a quad on screen at a given pixel position and pixel size so that once roating it, I can still achieve the effects in perspection that i'd get with a normal projection matrix So basically yes, it's not about perspection persè, nor about the distance, I just want to find a way to specify coordinates in screenpixels and still achieve that effectuser257824
So if I get you right, you just want to specify an undistorted rectangle in pixel coords and later want to rotate it (into depth)?derhass
Yes i'd like to specify the vertex attribute in the vbo using pixel coordinates but still being able to rotate / scale / translate the object while perspective effects still apply. So again, I'd like to specify in pixel coordinates it's beginning position and width/height, I don't get how I could do that once I set a viewmatrix and using it in conjunction with a projection matrixuser257824

4 Answers

2
votes

Well it can only have pixel-coordinates in one "z-plane" if you want to use a trapezoid view-frustum.

Basic Math

If you use a standard camera the basic math for a camera at (0,0,0) would be

for alpha being the vertical fov (45° in your case)

target_y = tan(alpha/2) * z-distance * ((pixel_y/height)*2-1)
target_x = tan(alpha/2) * z-distance * ((pixel_x/width)*aspect-ratio*2-1)

Reversing projection

As for the general case. You can "un-project" to find where a point in 3D before all transforms should be to end up on a specific point.

Basically you need to un-do the math.

gl_Position = projection * view * model * vec4(position.xy, 0.0f, 1.0);

So if you have your final position and want to revert it you do:

unprojection =  model^-1 * view^-1 *projection^-1 * gl_Position //not actual glsl notation, '^-1' being the inverse

This is basically what functions like gluUnProject or glm::gtc::matrix_transform::unProject do.

But you should note that the final clip-space after you apply the projection matrix is typically [-1,-1,0] to [1,1,1], so if you want to enter pixel coordinates you can apply an additional matrix to transform into that space.

Something like:

               [2/width,        0,     0    -1]
               [      0, 2/height,     0    -1]
screenToClip = [      0,        0,     1     0]
               [      0,        0,     0     1]

would transform [0,0,0,1] to [-1,-1,0,1] and [width,height,0,1] to [1,1,0,1]

Also, you're probably best off trying some z-value like 0.5 to make sure that you're well within the view frustum and not clipping near the front or back.

1
votes

You can achieve this effect with a 60 degree field of view. Basically you want to place the camera at a distance from the viewing plane such that the camera forms an equilateral triangle with center points at the top and bottom of the screen.

Here's some code to do that:

float fovy = 60.0f; // field of view - degrees
float aspect = nScreenWidth / nScreenHeight;
float zNearClip = 0.1f;
float zFarClip = nScreenHeight*2.0f;
float degToRad = MF_PI / 180.0f;
float fH = tanf(fovY * degToRad / 2.0f) * zNearClip;
float fW = fH * aspect;

glFrustum(-fW, fW, -fH, fH, zNearClip, zFarClip);

float nCameraDistance = sqrtf( nScreenHeight * nScreenHeight - 0.25f * nScreenHeight * nScreenHeight); 

glTranslatef(0, 0, -nCameraDistance);

You can also use a 90 degree fov. In that case the camera distance is 1/2 the height of the window. However, this has a lot of foreshortening.

In the 90 degree case, you could push the camera out by the full height, but then apply a 2x scaling to the x and y components (ie: glScale (2,2,1).

Here's an image of what I mean:

Field of view

0
votes

I'll extend PeterT answer and leave here the practical code I used to find the world coordinates of one of the frustum's plane through unprojection

This assumes a basic view matrix (camera pos at 0,0,0)

glm::mat4 projectionInv(0);
glm::mat4 projection = glm::perspective(45.0f, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 500.0f);
projectionInv = glm::inverse(projection);

std::vector<glm::vec4> NDCCube;
NDCCube.push_back(glm::vec4(-1.0f, -1.0f,   -1.0f,     1.0f));
NDCCube.push_back(glm::vec4(1.0f, -1.0f,    -1.0f,     1.0f));
NDCCube.push_back(glm::vec4(1.0f, -1.0f,     1.0f,     1.0f));
NDCCube.push_back(glm::vec4(-1.0f, -1.0f,    1.0f,     1.0f));
NDCCube.push_back(glm::vec4(-1.0f, 1.0f,    -1.0f,     1.0f));
NDCCube.push_back(glm::vec4(1.0f, 1.0f,     -1.0f,     1.0f));
NDCCube.push_back(glm::vec4(1.0f, 1.0f,      1.0f,     1.0f));
NDCCube.push_back(glm::vec4(-1.0f, 1.0f,     1.0f,     1.0f));

std::vector<glm::vec3> frustumVertices;

for (int i = 0; i < 8; i++)
{
    glm::vec4 tempvec;
    tempvec = projectionInv * NDCCube.at(i); //multiply by projection matrix inverse to obtain frustum vertex
    frustumVertices.push_back(glm::vec3(tempvec.x /= tempvec.w, tempvec.y /= tempvec.w, tempvec.z /= tempvec.w));
}

Keep in mind these coordinates would not end up on screen if your perspective far distance is lower than the one I set in the projection matrix

0
votes

If you happen to know the world-coordinate width of "some item" that you want to display pixel-exact, this ends up being a bit of trivial trigonometry (works for both y FOV or x FOV):

S = Width of item in world coordinates
T = "Pixel Exact" size of item (say, the width of the texture)
h = Z distance to the object
a = 2 * h * tan(Phi / 2)
b = a / cos(phi / 2)
r = Total screen resolution (width or height depending on the FOV you want)

a = 2 * h * tan(Phi / 2) = (r / T) * S
Theta = atan(2*h / a)
Phi = 180 - 2*Theta

Where b are the sides of your triangle, a is the base of your triangle, h is the height of your triangle, theta is the angles of the two equal angles of the Isosoleces triangle, and Phi is the resulting FOV

triangle

So the end code might look something like

float frustumWidth = (float(ScreenWidth) / TextureWidth) * InWorldItemWidth; float theta = glm::degrees(atan((2 * zDistance) / frustumWidth)); float PixelPerfectFOV = 180 - 2 * theta;