How can I correct for Aspect Ratio when rotating a Normalised Device Coordinate based view? - opengl

I have an OpenGL 2D graphics display. The display uses Normalised Device Coordinates (i.e. -1 to +1 in the horizontal direction, -1 to +1 across the view in the vertical direction and the Origin at the center of the view). I can enable Panning and Zooming In/Out of objects displayed in the view with my mouse.
Almost all computer displays today have non-square aspect ratio (e.g. 16:9 width to height ratio). So the -1 to +1 spacing occupies a smaller physical distance in the vertical direction than in the horizontal direction. For example; a line or a line of equally spaced points will appear physically shorter when rotated from horizontal alignment to pointing in the vertical direction.
How can I rotate the view and dynamically correct for the non-square aspect ratio of the view? So that a line will maintain the same length in the display when it is rotated from horizontal alignment to vertical alignment - resulting in part of the line appearing to be 'clipped' by the view frame.

Assuming that your viewport is of size (w,h), you set up a projection matrix that would translate pixel coordinates to NDC:
[ 2/w 0 0 -1 ]
P = [ 0 2/h 0 -1 ]
[ 0 0 1 0 ]
[ 0 0 0 1 ]
(This one assigns the bottom-left corner as (0,0) and top-right as (w,h). You can use glm::ortho instead if you like.)
Pass it to the vertex shader, and apply it after the model-view matrix:
in vec4 position;
uniform mat4 P;
uniform mat4 MV;
void main() {
gl_Position = P * MV * position;
}
Now you can specify all your positions in pixels. When you apply a rotation in MV, it will rotate the pixel-position before the translation to NDC happens, thus keeping the aspect ratio of your 2D model.

Related

Negative values for gl_Position.w?

Is the w component of gl_Position required to be greater than zero? Because when I set it to a negative number nothing is drawn but positive numbers are fine.
gl_Position = vec4(vPos,0,-1);
Face culling is not enabled btw.
Has the w component of gl_Position required to be greater than zero?
No, but the result of gl_Position.xyz / gl_Position.w has to be in the range (-1,-1,-1) to (1,1,1), the normalized device space. This means each component (x, y and z) of the result, has to be >= -1.0 and <= 1.0.
But, if the w component is negative, nothing is draw anyway. Because gl_Position defines the clip space. The condition for a homogeneous coordinate to be in clip space is
-w <= x, y, z <= w.
If w = -1 this would mean:
1 <= x, y, z <= -1.
and that can never be fulfilled.
(see Why does GL divide gl_Position by W for you rather than letting you do it yourself?)
Explanation:
The coordinates which are set to gl_Position are Homogeneous coordinates. If the w component of a Homogeneous coordinate is 1, it is equal to the Cartesian coordinate built of the components xyz.
Homogeneous coordinates are used for the representation of the perspective projection.
In a rendering, each mesh of the scene usually is transformed by the model matrix, the view matrix and the projection matrix.
The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. The projection matrix transforms from view space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) in the range (-1, -1, -1) to (1, 1, 1) by dividing with the w component of the clip coordinates. Every geometry which is out of the NDC is clipped.
At Orthographic Projection the coordinates in the eye space are linearly mapped to normalized device coordinates. (TCommonly the w component is 1.0)
At Perspective Projection the projection matrix describes the mapping from 3D points in the world as they are seen from of a pinhole camera, to 2D points of the viewport. The eye space coordinates in the camera frustum (a truncated pyramid) are mapped to a cube (the normalized device coordinates).

Pixel perfect text rendering in perspective projection

I'm trying to render text as textured quads in perspective projection (do not want to use ortho projection), and I'm struggling with pixel alignment.
The setup is simple, I have text with align anchor point in 3D, I change its model transformation into billboard transformation, and calculate scale (triangle similarity) to have the text always the same size. Since geometry of text quads is constructed with world units corresponding to pixels, resulting text indeed seems to be the same size no matter camera orientation or anchor point offset.
Vector3d dist = camera.getPosition();
dist.sub(translation);
double pxFov = camera.getFOV() / camera.getScreenWidth();
double scale = Math.sin(pxFov) / Math.sin((Math.PI / 2) - pxFov)
* dist.length() * camera.getAspectRatio();
V.getRotationScale(R);
R.transpose();
M.setIdentity();
M.setRotation(R);
M.setScale(scale);
M.setTranslation(translation);
Where V is 4x4 camera view matrix, R temporary 3x3 matrix, and M is 4x4 model transformation matrix used for MVP calculation.
I found supposed solution, but it just slightly changed behaviour of rendered text, instead of fixing the problem.
When using vertex shader
void main () {
vec2 view = vec2(1280, 720);
vec4 cpos = MVP * vec4 (position, 1.0f);
vec2 p = floor(cpos.xy * view*0.5/cpos.w);
p += 0.5; // does not influence result
cpos.xy = p * (2.0/view*cpos.w);
gl_Position = cpos;
}
text does render in some places sharp, and in some places blurred
In case of simple vertex shader
void main () {
gl_Position = MVP * vec4 (position, 1.0f);
}
is the text either completely blurred or completely sharp
It seems logial getting vertex positions to viewport space, rounding it there and moving it back, but something seems to be missing.
EDIT: Explanation of how I'm calculating the scale factor.
Here you can see I'm getting right angle triangle as red, green and blue lines. Knowing lenght of red (camera-text anchor distance), angle between red and blue ((fov/2)/(screen width/2)), and angle between red and green being right angle, I can use law of sines to calculate length of the green line, which is also scale of one texel to have same size in current projection.
Scale of the text seems to be correct no matter camera/text orientation/position (desired 8 pixels). It is possible the scale is just lightly wrong and that results in the blur effect, but I fail to see how.
It seems to me that due to numerical issues you're suffering z-buffer fighting.
Perhaps you can add a little value to the text z-coordinate, separate it just a bit from its background.
Another solution is to avoid the text scale-unscale calculation.
You may calculate pixel anchorage by doing perspective maths on CPU side for every text. Then display it with orthographic projection.

Convert gl_FragCoord coordinate to screen positions

I'm strictly talking about a 2d environment (in fact this is for a 2d game).
In a fragment shader, how can I convert gl_FragCoord.x and gl_FragCoord.y to screen coordinates so that the top-left pixel is 0, 0 and the bottom right is the screen resolution (for example 800, 600)? Thanks
By default the origin is at the bottom left and pixels are centered at half integer coordinates (the bottom left pixel is at (0.5, 0.5)). One way to achieve what you want is to redeclare gl_FragCoord with a layout qualifier:
layout(origin_upper_left) in vec4 gl_FragCoord;
or
layout(origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord;
if you want the pixels to be centered on integer coordinates.
Another way is to pass in the screen resolution in a uniform variable and do a bit of math:
vec2 pos = vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y);
or to get integer values:
vec2 pos = vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y) - 0.5;
Note that if you want the pixels centered on integer coordinates then the pixel at the corner opposite the origin will be resolution - 1.0 not resolution.

GLSL compute world coordinate from eye depth and screen position

I'm trying to recover WORLD position of a point knowing it's depth in EYE space, computed as follow (in a vertex shader) :
float depth = - uModelView * vec4( inPos , 1.0 ) ;
where inPos is a point in world space (Obviously, I don't want to recover this particular point, but a point where depth is expressed in that format).
And it's normalized screen position (between 0 and 1), computed as follow (in a fragment shader ) :
vec2 screen_pos = ( vec2( gl_FragCoord.xy ) - vec2( 0.5 ) ) / uScreenSize.xy ;
I can access to the following info :
uScreenSize : as it's name suggest, it's screen width and height
uCameraPos : camera position in WORLD space
and standard matrices :
uModelView : model view camera matrix
uModelViewProj : model view projection matrix
uProjMatrix : projection matrix
How can I compute position (X,Y,Z) of a point in WORLD space ? (not in EYE space)
I can't have access to other (I can't use near, far, left, right, ...) because projection matrix is not restricted to perspective or orthogonal.
Thanks in advance.
I get your question right, you have x and y as window space (and already converted to normalized device space [-1,1]), but z in eye space, and want to recosntruct the world space position.
I can't have access to other (I can't use near, far, left, right, ...)
because projection matrix is not restricted to perspective or
orthogonal.
Well, actually, there is not much besides an orthogonal or projective mapping which can be achieved by matrix multiplication in homogenous space. However, the projection matrix is sufficient, as long as it is invertible (In theory, a projection matrix could transform all points to a plane, line or a single point. In that case, some information is lost and it will never be able to reconstruct the original data. But that would be a very untypical case).
So what you can get from the projection matrix and your 2D position is actually a ray in eye space. And you can intersect this with the z=depth plane to get the point back.
So what you have to do is calculate the two points
vec4 p = inverse(uProjMatrix) * vec4 (ndc_x, ndc_y, -1, 1);
vec4 q = inverse(uProjMatrix) * vec4 (ndc_x, ndc_y, 1, 1);
which will mark two points on the ray in eye space. Do not forget to divide p and q by the respective w component to get the 3D coordinates. Now, you simply need to intersect this with your z=depth plane and get the eye space x and y. Finally, you can use the inverse of the uModelView matrix to project that point back to object space.
However, you said that you want world space. But that is impossible. You would need the view matrix to do that, but you have not listed that as a given. All you have is the compisition of the model and view matrix, and you need to know at least one of these to reconstruct the world space position. The cameraPosition is not enoguh. You also need the orientation.

C++/OpenGL convert world coords to screen(2D) coords

I am making a game in OpenGL where I have a few objects within the world space. I want to make a function where I can take in an object's location (3D) and transform it to the screen's location (2D) and return it.
I know the the 3D location of the object, projection matrix and view matrix in the following varibles:
Matrix projectionMatrix;
Matrix viewMatrix;
Vector3 point3D;
To do this transform, you must first take your model-space positions and transform them to clip-space. This is done with matrix multiplies. I will use GLSL-style code to make it obvious what I'm doing:
vec4 clipSpacePos = projectionMatrix * (viewMatrix * vec4(point3D, 1.0));
Notice how I convert your 3D vector into a 4D vector before the multiplication. This is necessary because the matrices are 4x4, and you cannot multiply a 4x4 matrix with a 3D vector. You need a fourth component.
The next step is to transform this position from clip-space to normalized device coordinate space (NDC space). NDC space is on the range [-1, 1] in all three axes. This is done by dividing the first three coordinates by the fourth:
vec3 ndcSpacePos = clipSpacePos.xyz / clipSpacePos.w;
Obviously, if clipSpacePos.w is zero, you have a problem, so you should check that beforehand. If it is zero, then that means that the object is in the plane of projection; it's view-space depth is zero. And such vertices are automatically clipped by OpenGL.
The next step is to transform from this [-1, 1] space to window-relative coordinates. This requires the use of the values you passed to glViewport. The first two parameters are the offset from the bottom-left of the window (vec2 viewOffset), and the second two parameters are the width/height of the viewport area (vec2 viewSize). Given these, the window-space position is:
vec2 windowSpacePos = ((ndcSpacePos.xy + 1.0) / 2.0) * viewSize + viewOffset;
And that's as far as you go. Remember: OpenGL's window-space is relative to the bottom-left of the window, not the top-left.