Part of the Khronos Group
OpenGL.org

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

Results 1 to 10 of 10

Thread: True Camera via OpenGL4.5

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Junior Member Regular Contributor
    Join Date
    Dec 2010
    Location
    Oakville, ON, CA
    Posts
    165

    True Camera via OpenGL4.5

    Gentlemen, I want to share the new (at least to my knowledge) technique with you.

    As we know, rendering large scenes had always been tricky: if near clipping plane set too close, depth-fighting for far objects becomes noticeable; if zNear is pushed away, then objects at near are partially culled. There are ways to work it around, but they all come at some expense.

    But now, with ARB_clip_control in OpenGL4.5 core we can finally simulate a camera with truly real properties: with no far clipping plane (infinite drawing distance) and very small zNear. It will let us fit a simulated camera object in an any tiny hole in the scene and let it capture the whole world around without any artifacts:
    Click image for larger version. 

Name:	TrueCamera.jpg 
Views:	461 
Size:	11.4 KB 
ID:	1745
    On the picture the far mountains are ~60km away while the camera is sitting almost at the ground level and has only 1mm distance to the near clipping plane. The demo project can be downloaded here.

    So here is the way to make those drawing properties possible.
    We need a floating point depth buffer, therefore we need a FBO and can no longer render directly to the window (but who does nowadays, right?).
    Then we need to change the mapping of depth values from default [-1...1] to required [0...1]:

    glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);

    By doing so we keep the Zndc coordinate in [-1, 1] range from being mapped to the [0, 1] range in window space. This is crucial, because scaling by 0.5 and adding 0.5 causes loss of precision for any values of near-zero ranges; therefore, 1e-10 and 2e-10 will both result in the same value in depth buffer. As we will see later on, those tiny values will form the majority of the depth buffer contents, so we need to tolerate them.

    Now the core of the idea: the projection matrix. It has to be constructed in some unusual way:
    Code :
        | f/aspect  0      0      0    |
        |                              |
        |    0      f      0      0    |
    P = |                              |
        |    0      0      0    zNear  |
        |                              |
        |    0      0     -1      0    |
    here:
    f = ctan(ViewAngleVertical/2)
    aspect = Viewport.x / Viewport.y;
    zNear: distance to the near clipping plane.

    Such projection matrix results in a reversed depth range: the further the object, the smaller the depth values of it's fragments. For most objects of the scene the gl_FragCoord.z will look like XXXe-YYY. But as long as the number fits into the range the float number can represent - we still have 23 bits of mantissa's precision (before underflow). Therefore, the depth testing has to be set like this:

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_GEQUAL);

    It should be mentioned, though, that the precision changes with distance, therefore the depth values of adjacent surfaces may become equal at a certain distance. But this error increases linearly with the distance, just like level-of-detalization for objects should, ideally. In any case, there are no doubts that the technique has superior advantage over the conventional camera setup and can compete with w-buffer technique of DirectX, being an OpenGL alternative to that.
    Last edited by Yandersen; 04-13-2015 at 04:00 PM.

  2. #2
    Senior Member OpenGL Lord
    Join Date
    May 2009
    Posts
    6,044
    By doing so we make our gl_FragDepth written to the depth buffer as it is, without any remapping performed. It is crucial, because default scaling by 0.5 and adding 0.5 causes loss of precision for any gl_FragDepth values of near-zero ranges
    There is a mistake in your terminology here. gl_FragDepth is the fragment shader output that defines the fragment's depth component. It doesn't get scaled. The range of gl_FragDepth is [0, 1], and always has been.

    The particular scaling/offset you're trying to avoid with glClipControl happens in the transformation from clip-space to NDC space, and the later transformation from NDC to window space. These happen per-vertex, not per-fragment.

  3. #3
    Junior Member Regular Contributor
    Join Date
    Dec 2010
    Location
    Oakville, ON, CA
    Posts
    165
    Changed to gl_FragCoord.z. Thank you, Alfonse!

  4. #4
    Senior Member OpenGL Lord
    Join Date
    May 2009
    Posts
    6,044
    Quote Originally Posted by Yandersen View Post
    Changed to gl_FragCoord.z. Thank you, Alfonse!
    That's also technically still wrong, at least the first time you mention it. The post-clipping transform (and accompanying loss of precision) all happen before the generation of fragments.

    So you would need to change the earlier paragraph to something along these lines:

    By doing so we keep the vertex's Z coordinate from being mapped to the [-1, 1] range, then back to the [0, 1] range in window space. This is crucial, because scaling by 0.5 and adding 0.5 causes loss of precision for any values of near-zero ranges; therefore, 1e-10 and 2e-10 will both result in the same value in depth buffer. As we will see later on, those tiny values will form the majority of the depth buffer contents, so we need to tolerate them.
    The rest however is fine.

  5. #5
    Junior Member Regular Contributor
    Join Date
    Dec 2010
    Location
    Oakville, ON, CA
    Posts
    165
    Hm, thanks, but I will change it a bit, because vertex' Z is not mapped to [-1...1] range, actually...

  6. #6
    Senior Member OpenGL Lord
    Join Date
    May 2009
    Posts
    6,044
    Quote Originally Posted by Yandersen View Post
    Hm, thanks, but I will change it a bit, because vertex' Z is not mapped to [-1...1] range, actually...
    That's not quite how it works. From the ARB_clip_control specification:

    Code :
        "Primitives are clipped to the clip volume. In clip coordinates,
        the view volume is defined by
     
            -w_c <= x_c <= w_c
            -w_c <= y_c <= w_c
              zm <= z_c <= w_c
     
         where zm is -w_c when the clip control depth mode is
         NEGATIVE_ONE_TO_ONE or zero when the mode is ZERO_TO_ONE."

    So in clip-space, if you use ZERO_TO_ONE, then clipZ is clipped to the range [0, clipW]. So when you perform the transform to NDC space by dividing by clipW, you get an NDC_z value on the range [0, 1]. So by changing the clip control, you're changing the NDC-space range for Z.

    And here's the transform from NDC space to window space, from the ARB_clip_control spec:

    Code :
        "The vertex's window coordinates, (x_w y_w z_w)^T are given by:
     
            ( x_w )     ( p_x/2 x_d + o_x )
            ( y_w )  =  ( p_y/2 y_d + o_y )
            ( z_w )     (     s z_d + b   )
     
        where s is (f-n)/2 and b is (n+f)/2 when the clip control depth mode
        is NEGATIVE_ONE_TO_ONE; or s is (f-n) and b is n when the mode
        is ZERO_TO_ONE."

    Notice that the `s` and `b` values when using ZERO_TO_ONE are designed to map only the [0, 1] range of NDC values into the depth range. Whereas when using NEGATIVE_ONE_TO_ONE, this maps [-1, 1] into the depth range.

    So clip control very much prevents the NDC-space Z from being in the [0, 1] range. The whole point of this method is so that the range of Z values are always on the range [0, X], no matter what space they are in. This is why you can't just use shader logic to fix the problem; because the window-space transform (which is hard-coded) would always transform from a [-1, 1] NDC depth space.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •