1

Let's say that my screen is (800 * 600) and I have a Quad (2D) drawn with the following vertices positions using Triangle_Strip (in NDC) :

float[] vertices = {-0.2f,0.2f,-0.2f,-0.2f,0.2f,0.2f,0.2f,-0.2f};

And I set up my Transformation Matrix in this way :

Vector2f position = new Vector2f(0,0);
Vector2f size = new Vector2f(1.0f,1.0f);

Matrix4f tranMatrix = new Matrix4f();
tranMatrix.setIdentity();
Matrix4f.translate(position, tranMatrix, tranMatrix);
Matrix4f.scale(new Vector3f(size.x, size.y, 1f), tranMatrix, tranMatrix);

And my vertex Shader :

#version 150 core

in vec2 in_Position;

uniform mat4 transMatrix;

void main(void) {

gl_Position = transMatrix * vec4(in_Position,0,1.0);

}

My question is, which formula should I use to modify the transformations of my quad with coordinates (in Pixels) ?

For example :

  • set Scale : (50px, 50px) => Vector2f(width,height)

  • set Position : (100px, 100px) => Vector2f(x,y)

To better understand, I would create a function to convert my Pixels data to NDCs to send them next to the vertex shader. I was advised to use an Orthographic projection but I don't know how to create it correctly and as you can see in my vertex shader I don't use any projection matrix.

Here is a topic similar to mine but not very clear - Transform to NDC, calculate and transform back to worldspace


EDIT:

I created my orthographic projection matrix by following the formula but nothing seems to appear, here is how I proceeded :

public static Matrix4f glOrtho(float left, float right, float bottom, float top, float near, float far){
    
    final Matrix4f matrix = new Matrix4f();
    matrix.setIdentity();

    matrix.m00 = 2.0f / (right - left);
    matrix.m01 = 0;
    matrix.m02 = 0;
    matrix.m03 = 0;
    
    matrix.m10 = 0;
    matrix.m11 = 2.0f / (top - bottom);
    matrix.m12 = 0;
    matrix.m13 = 0;
    
    matrix.m20 = 0;
    matrix.m21 = 0;
    matrix.m22 = -2.0f / (far - near);
    matrix.m23 = 0;
    
    matrix.m30 = -(right+left)/(right-left);
    matrix.m31 = -(top+bottom)/(top-bottom);
    matrix.m32 = -(far+near)/(far-near);
    matrix.m33 = 1;

    return matrix;
}

I then included my matrix in the vertex shader

#version 140

in vec2 position;

uniform mat4 projMatrix;

void main(void){

gl_Position = projMatrix * vec4(position,0.0,1.0);

}

What did I miss ?

Bo Halim
  • 1,716
  • 2
  • 17
  • 23
  • 1
    Just want to clarify, you want to convert from Screen Space to NDC Space correct? – ssell Mar 12 '17 at 18:42
  • Yes, screen space (top left origin), sorry i have a little difficulty to assimilate the notions of OpenGL ! – Bo Halim Mar 12 '17 at 18:45

3 Answers3

10

New Answer

After clarifications in the comments, the question being asked can be summed up as:

How do I effectively transform a quad in terms of pixels for use in a GUI?

As mentioned in the original question, the simplest approach to this will be using an Orthographic Projection. What is an Orthographic Projection?

a method of projection in which an object is depicted or a surface mapped using parallel lines to project its shape onto a plane.

In practice, you may think of this as a 2D projection. Distance plays no role, and the OpenGL coordinates map to pixel coordinates. See this answer for a bit more information.

By using an Orthographic Projection instead of a Perspective Projection you can start thinking of all of your transformations in terms of pixels.

Instead of defining a quad as (25 x 25) world units in dimension, it is (25 x 25) pixels in dimension.

Or instead of translating by 50 world units along the world x-axis, you translate by 50 pixels along the screen x-axis (to the right).

So how do you create an Orthographic Projection?

First, they are usually defined using the following parameters:

  • left - X coordinate of the left vertical clipping plane
  • right - X coordinate of the right vertical clipping plane
  • bottom - Y coordinate of the bottom horizontal clipping plane
  • top - Y Coordinate of the top horizontal clipping plane
  • near - Near depth clipping plane
  • far - Far depth clipping plane

Remember, all units are in pixels. A typical Orthographic Projection would be defined as:

glOrtho(0.0, windowWidth, windowHeight, 0.0f, 0.0f, 1.0f);

Assuming you do not (or can not) make use of glOrtho (you have your own Matrix class or another reason), then you must calculate the Orthographic Projection matrix yourself.

The Orthographic Matrix is defined as:

2/(r-l)       0           0       -(r+l)/(r-l)
   0       2/(t-b)        0       -(t+b)/(t-b)
   0          0       -2/(f-n)    -(f+n)/(f-n)
   0          0           0            1

Source A, Source B

At this point I recommend using a pre-made mathematics library unless you are determined to use your own. One of the most common bug sources I see in practice are matrix-related and the less time you spend debugging matrices, the more time you have to focus on other more fun endeavors.

GLM is a widely-used and respected library that is built to model GLSL functionality. The GLM implementation of glOrtho can be seen here at line 100.

How to use an Orthographic Projection?

Orthographic projections are commonly used to render a GUI on top of your 3D scene. This can be done easily enough by using the following pattern:

  1. Clear Buffers
  2. Apply your Perspective Projection Matrix
  3. Render your 3D objects
  4. Apply your Orthographic Projection Matrix
  5. Render your 2D/GUI objects
  6. Swap Buffers

Old Answer

Note that this answered the wrong question. It assumed the question boiled down to "How do I convert from Screen Space to NDC Space?". It is left in case someone searching comes upon this question looking for that answer.

The goal is convert from Screen Space to NDC Space. So let's first define what those spaces are, and then we can create a conversion.

Normalized Device Coordinates

NDC space is simply the result of performing perspective division on our vertices in clip space.

clip.xyz /= clip.w

Where clip is the coordinate in clip space.

What this does is place all of our un-clipped vertices into a unit cube (on the range of [-1, 1] on all axis), with the screen center at (0, 0, 0). Any vertices that are clipped (lie outside the view frustum) are not within this unit cube and are tossed away by the GPU.

In OpenGL this step is done automatically as part of Primitive Assembly (D3D11 does this in the Rasterizer Stage).

Screen Coordinates

Screen coordinates are simply calculated by expanding the normalized coordinates to the confines of your viewport.

screen.x = ((view.w * 0.5) * ndc.x) + ((w * 0.5) + view.x)
screen.y = ((view.h * 0.5) * ndc.y) + ((h * 0.5) + view.y)
screen.z = (((view.f - view.n) * 0.5) * ndc.z) + ((view.f + view.n) * 0.5)

Where,

  • screen is the coordinate in screen-space
  • ndc is the coordinate in normalized-space
  • view.x is the viewport x origin
  • view.y is the viewport y origin
  • view.w is the viewport width
  • view.h is the viewport height
  • view.f is the viewport far
  • view.n is the viewport near

Converting from Screen to NDC

As we have the conversion from NDC to Screen above, it is easy to calculate the reverse.

ndc.x = ((2.0 * screen.x) - (2.0 * x)) / w) - 1.0
ndc.y = ((2.0 * screen.y) - (2.0 * y)) / h) - 1.0
ndc.z = ((2.0 * screen.z) - f - n) / (f - n)) - 1.0

Example:

viewport (w, h, n, f) = (800, 600, 1, 1000)

screen.xyz = (400, 300, 200)
ndc.xyz = (0.0, 0.0, -0.599)

screen.xyz = (575, 100, 1)
ndc.xyz = (0.4375, -0.666, -0.998)

Further Reading

For more information on all of the transform spaces, read OpenGL Transformation.

Edit for Comment

In the comment on the original question, Bo specifies screen-space origin as top-left.

For OpenGL, the viewport origin (and thus screen-space origin) lies at the bottom-left. See glViewport.

If your pixel coordinates are truly top-left origin then that needs to be taken into account when transforming screen.y to ndc.y.

ndc.y = 1.0 - ((2.0 * screen.y) - (2.0 * y)) / h)

This is needed if you are transforming, say, a coordinate of a mouse-click on screen/gui into NDC space (as part of a full transform to world space).

Community
  • 1
  • 1
ssell
  • 6,429
  • 2
  • 34
  • 49
  • 1
    @BoHalim No worries. Just want to make sure I am answering the right question. Can you please explain what the end goal is? From what I gather is you have a quad that you want to translate/scale by screen pixels instead of world-space units? Like move the quad 50 pixels to the right? – ssell Mar 12 '17 at 19:51
  • 1
    @BoHalim Is this for a GUI? Or is the quad going to be an object in your 3D scene? – ssell Mar 12 '17 at 19:59
  • Yes that's it (for GUI), I would like to create my own ! – Bo Halim Mar 12 '17 at 20:01
  • @BoHalim Then, as you mentioned in your question, the easiest solution will be to use an Orthographic Projection. I will revise my answer with an overview of what to do. – ssell Mar 12 '17 at 20:02
  • @BoHalim Please see my updated answer and let me know if it has helped or not. – ssell Mar 12 '17 at 20:45
1

NDC coordinates are transformed to screen (i.e. window) coordinates using glViewport. This function (you must use t in your app) defines a portion of the window by an origin and a size.

The formulas used can be seen at https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glViewport.xml
(x,y) are the origin, normally (0,0) the bottom left corner of the window.

While you can derivate the inverse formulas on your own, here you have them: https://www.khronos.org/opengl/wiki/Compute_eye_space_from_window_space#From_window_to_ndc

Ripi2
  • 7,031
  • 1
  • 17
  • 33
1

If I understand the question, you're trying to get screen space coords (the ones that define the size of your screen) to the -1 to 1 coords. If yes then it's quite simple. The equation is:

((coords_of_NDC_space / width_or_height_of_screen) * 2) - 1  

This would work because for example a screen of 800 × 600:

  800 / 800 = 1
  1 * 2 = 2
  2 - 1 = 1

and to check for a coordinate from half the screen on the height:

  300 / 600 = 0.5
  0.5 * 2 = 1
  1 - 1 = 0 (NDC is from -1 to 1 so 0 is middle)

karel
  • 5,489
  • 46
  • 45
  • 50