I was wondering how OpenGL handles viewport transformation to the window.
As I understand viewport transformation is that it strethes the scene onto the OpenGL window by applying the viewport transformation to that scene.
Please correct me if I'm wrong.
After clipping and perspective divide, all remaining (visible) vertex coordinates x,y,z are between -1 and +1 -- these are called normalized device coordinates. These are mapped to device coordinates by the appropriate scale and shift -- i.e, the viewport transformation.
For example, if the viewport has size 1024x768 with a 16-bit depth buffer and the origin is (0,0), then the points will be scaled by (512,384,2^14) and shifted by (512,384,2^14) yielding the appropriate pixel and depth values for the device.
http://www.songho.ca/opengl/gl_transform.html:
Window Coordinates (Screen Coordinates)
It is yielded by applying normalized device coordinates (NDC) to
viewport transformation. The NDC are scaled and translated in order to
fit into the rendering screen. The window coordinates finally are
passed to the raterization process of OpenGL pipeline to become a
fragment. glViewport() command is used to define the rectangle of the
rendering area where the final image is mapped. And, glDepthRange() is
used to determine the z value of the window coordinates. The window
coordinates are computed with the given parameters of the above 2
functions;
Follow the link to see the math details.
Related
Is there a way to set a transformation for NDC to window, but separately specify the clipping region so it matches the actual window size?
Background: I have a bunch of openGL code that renders a 2D map to a window. It's a lot of complex code, because I use both the GPU and the CPU to draw on the map, so it's important that I keep to a consistent coordinate system in both places. To keep that simple, I use glViewport(0,0,mapSizeX, mapSizeY), and now map coordinates correspond well to pixel coordinates in the frame buffer, exactly what I need. I can use GLSL to draw some of the map, call glReadPixels and use the CPU to draw on top of that, and glDrawPixels to send that back to the frame buffer, all of that using the same coordinate system. Finally I use GLSL to draw a few final things over that (that I don't want zoomed). That all works, except...
The window isn't the same size as the map, and glViewport doesn't just set up the transformation. It also sets up clipping. So now when I go to draw a few last items, and the window is larger than the map, things I draw near the top of the screen get clipped away. Is there a workaround?
glViewport doesn't just set up the transformation. It also sets up clipping.
No, it just sets up the transformation. By the time the NDC-to-window space transform happens, clipping has already been done. That happened immediately after vertex processing; your vertex shader (or whatever you're doing to transform vertices) handled that based on how it transformed vertices into clip-space.
You should use the viewport to set up how you want the NDC box to visibly appear in the window. Your VS needs to handle the transformation into the clipping area. So it effectively decides how much of the world gets put into the NDC box that things get clipped to.
Basically, you have map space (the coordinates used by your map) and clip-space (the coordinates after vertex transformations). And you have some notion of which part of the map you want to actually draw to the window. You need to transform the region of your map that you want to see such that the corners of this region appear in the corners of the clipping box (for orthographic projections, this is typically [-1, 1]).
In compatibility OpenGL, this might be defined by using glOrtho for othographic projections to transform from you. In a proper vertex shader, you'll need to provide an appropriate orthographic matrix.
I have a viewport that is half of the screen size/framebuffer.
x, y, w, h = 0, 0, 512, 512
And my scissor region is the full framebuffer
x, y, w, h = 0, 0, 0 1024, 512
I am drawing a line, that is far outside of the viewport, from left to right. I am expecting the line to be drawn only inside the viewport. I have tested this on three different graphic cards, and on two of them, I get the result I am I expecting. However on the third one, the line is drawn outside the viewport, but inside the scissor region.
Which one of the results is correct here? As far as I understand it, the lines' two vertices should be moved to outer viewport positions. It should not be drawn outside of it.
If you read pitfall nr 10 on this site:
https://www.opengl.org/archives/resources/features/KilgardTechniques/oglpitfall/
They are talking about drawing outside the viewport, but that is just some special cases, for example where you have a really thick line, my line width is 1
EDIT: After a discussion in the KhronosGroup:
From the spec Vulkan 1.0.68:
If either of a line segment’s vertices lie outside of the clip volume,
the line segment may be clipped, with new vertex coordinates computed
for each vertex that lies outside the clip volume. A clipped line
segment endpoint lies on both the original line segment and the
boundary of the clip volume.
Nvidia:
We intentionally made this change for maintenance2, to allow for
pop-free points and lines. Point clipping behavior is queriable but
line clipping behavior is not, though I believe the "preferred"
behavior for lines is to be pop-free. We changed the NVIDIA driver
last year from tight clipping to pop-free, and even changed CTS tests
to allow this new behavior. So this is all working as designed.
Old answer:
The viewport defines a transformation from normalized device coordinates to window coordinates. The viewport transformation does not do any clipping.
However, the clipping does happen before NDC space, the view frustum clipping does guarantee that no vertex can fall outside the viewport. And if you are using orthogonal projection, clip space and NDC space are the same. So everything outside [-1,1] will be clipped. And if you are not doing any of the special cases the opengl link talks about the vertices should be clipped.
If two of your graphics cards draws inside the viewport and one outside, it's probably a driver bug. If you are using Vulkan, which is fairly new, that is most likely the case.
Edit: My answer below is technically correct but not useful. The viewport transform itself doesn't define a clip volume or scissor. But because of clipping to the view frustum ([-w,+w] for x and y, [0,+w] for z), all the (x,y) values entering the viewport transform will be in [-1,1], and will not be transformed to fall outside of the rectangle that defined the viewport.
Like in GL, the viewport only defines a transform from normalized device coordinates to framebuffer coordinates. The transform is based on the width/height/depth/origin of the viewport volume, but that volume doesn't define a clipping volume. The math is described in section 23.5 Controlling the Viewport.
In Vulkan you can also specify a scissor rectangle in framebuffer coordinates for each viewport, in VkPipelineViewportStateCreateInfo:: pScissors or vkCmdSetScissor. Pixels outside this rectangle are dropped. But you have to set the scissor rectangle yourself, and it doesn't have to match the viewport volume.
I understand i need to render only 1x1 or 3x3 pixel part of the screen where the mouse is, with object id's as colors and then get id from the color.
I have implemented ray-cast picking with spheres and i am guessing it has something to do with making camera look in direction of the mouse ray?
How do i render the correct few pixels?
Edit:
setting camera in direction of mouse ray works, but if i make the viewport smaller the picture scales but what (i think) i need is for it to be cropped rather than scaled. How would i achieve this?
The easiest solution is to use the scissor test. It allows you to render only pixels within a specified rectangular sub-region of your window.
For example, to limit your rendering to 3x3 pixels centered at pixel (x, y):
glScissor(x - 1, y - 1, 3, 3);
glEnable(GL_SCISSOR_TEST);
glDraw...(...);
glDisable(GL_SCISSOR_TEST);
Note that the origin of the coordinate system is at the bottom left of the window, while most window systems will give you mouse coordinates in a coordinate system that has its origin at the top left. If that's the case on your system, you will have to invert the y-coordinate by subtracting it from windowHeight - 1.
I am trying to render a small region of the screen to an off-screen texture. This is part of a screenshot function in my app where the user selects a region on the screen and saves this to an image. While the region on the screen might be 250x250px, the saved image can be a lot larger like 1000x1000px.
I understand the process of rendering to a texture using an FBO. I'm mostly stuck when it comes to defining the projection matrix that clips the scene so that only the screenshot region is rendered.
I believe you can do this without changing the projection matrix. After all, if you think about, you don't really want to change the projection. You want to change which part of the projected geometry gets mapped to your rendering surface. The coordinate system after projection is NDC (normalized device coordinates). The transform that controls how NDC is mapped to the rendering surface is the viewport transformation. You control the viewport transformation by the parameters to glViewport().
If you set the viewport dimensions to the size of your rendering surface, you map the NDC range of [-1.0, 1.0] to your rendering surface. To render a sub-range of that NDC range to your surface, you need to scale up the specified viewport size accordingly. Say to map 1/4 of your original image to the width of your surface, you set the viewport width to 4 times your surface width.
To map a sub-range of the standard NDC range to your surface, you will also need to adjust the origin of the viewport. The viewport origin values become negative in this case. Continuing the previous example, to map 1/4 or the original image starting in the middle of the image, the x-value of your viewport origin will be -2 times the surface width.
Here is what I came up with on how the viewport needs to be adjusted. Using the following definitions:
winWidth: width of original window
winHeight: height of original window
xMin: minimum x-value of zoomed region in original window coordinates
xMax: maximum x-value of zoomed region in original window coordinates
yMin: minimum y-value of zoomed region in original window coordinates
yMax: maximum y-value of zoomed region in original window coordinates
fboWidth: width of FBO you use for rendering zoomed region
fboHeight: height of FBO you use for rendering zoomed region
To avoid distortion, you will probably want to maintain the aspect ratio:
fboWidth / fboHeight = (xMax - xMin) / (yMax - yMin)
In all of the following, most of the operations (particularly the divisions) will have to be executed in floating point. Remember to use type casts if the original variables are integers, and round the results back to integer for the final results.
xZoom = winWidth / (xMax - xMin);
yZoom = winHeight / (yMax - yMin);
vpWidth = xZoom * fboWidth;
vpHeight = yZoom * fboHeight;
xVp = -(xMin / (xMax - xMin)) * fboWidth;
yVp = -(yMin / (yMax - yMin)) * fboHeight;
glViewport(xVp, yVp, vpWidth, vpHeight);
You might want to look into how gluPickMatrix works and replicate its functionality using modern OpenGL methods. You can find the gluPickMatrix source code in the implementation of Mesa3D.
While the original intent of gluPickMatrix was for selection mode rendering, it can be used for what you want to do as well.
I want to create skybox, which is just textured cube around camera. But actually i don't understand how this can work, because the camera viewing volume is frustum and the skybox is cube. According to this source:
http://www.songho.ca/opengl/gl_projectionmatrix.html
Note that the frustum culling (clipping) is performed in the clip
coordinates, just before dividing by wc. The clip coordinates, xc, yc
and zc are tested by comparing with wc. If any clip coordinate is less
than -wc, or greater than wc, then the vertex will be discarded.
vertexes of skybox faces should be clipped, if they are outside of frustum.
So it looks for me that the cube should be actually a frustum and should match exactly the gl frustum faces, so my whole scene is wrapped inside of that skybox, but i am sure this is bad. Is there any way how to fill whole screen with something, that wraps whole gl frustum?
The formulation from your link is rather bad. It is not actually vertices that get clipped, but rather fragments. Drawing a primitive with vertices completely off-screen does not prevent the fragments that would intersect with the screen from getting drawn. (The picture in the link also actually shows this being the case.)
That having been said, however, it may (or may not, depending on the design of your rendering code) be easier to simply draw a full-screen quad, and use the inverse of the projection matrix to calculate the texture coordinates instead.