How to implement camera pan like in 3dsMax?

后端 未结 2 680
庸人自扰
庸人自扰 2020-12-10 15:37

What are the necessary maths to achieve the camera panning effect that\'s used in 3ds max?

In 3ds max the distance between the cursor and the mesh will always remain

2条回答
  •  失恋的感觉
    2020-12-10 16:05

    [...] but there's still one case where his algorithm won't work properly. It doesn't handle properly the case where you panning is started from empty space [...]

    In the solution the depth of the object is taken from the depth buffer, at that position, where the mouse click occurs. If this is the "empty space", a position where no object was drawn, the depth is the maximum of the depth range (in common 1). This leads to a rapid paining.

    A solution or workaround would be use the depth of an representative position of the scene. e.g. the origin of the world:

    pt_drag = glm.vec3(0, 0, 0)
    

    Of course this may not lead to a proper result in each case. If the objects of the scene are not around the origin of the world, this approach will fail. I recommend to calculate the center of the axis aligned bounding box of the scene. Use this point for the representative "depth":

    box_min = ... # glm.vec3
    box_max = ... # glm.vec3
    
    pt_drag = (box_min + box_max) / 2
    

    The depth of a point can computed by the transformation with the view and projection matrix and a final perspective divide:

    o_clip = self.proj * self.view * glm.vec4(pt_drag, 1)
    o_ndc  = glm.vec3(o_clip) / o_clip.w
    

    This can be applied to the function glut_mouse:

    def glut_mouse(self, button, state, x, y):
        self.drag = state == GLUT_DOWN
        self.last_mouse_pos = glm.vec2(x, self.height-y)
        self.mouse_down_pos = glm.vec2(x, self.height-y)
    
        if self.drag:
            depth_buffer = glReadPixels(x, self.height-y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)
            self.last_depth = depth_buffer[0][0]
            if self.last_depth == 1:
                pt_drag = glm.vec3(0, 0, 0)
                o_clip  = self.proj * self.view * glm.vec4(pt_drag, 1)
                o_ndc   = glm.vec3(o_clip) / o_clip.w
                if o_ndc.z > -1 and o_ndc.z < 1:
                    self.last_depth = o_ndc.z * 0.5 + 0.5
    

    Preview:

    The key to a well feeling solution is to find the "correct" depth. At perspective projection the dragging, where the mouse movement effects the object in a 1:1 motion, projected on the viewport, only works correctly for a well defined depth. Objects with different depths are displaced by a different scale when they projected on the viewport, that's the "nature" of perspective.

    To find the "correct" depth, there are different possibilities, which depend on your needs:

    • Reading the depth from the depth buffer at the current mouse position:
    depth_buffer = glReadPixels(x, self.height-y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)    
    self.last_depth = depth_buffer[0][0]
    
    • Get the minimum and maximum depth of the depth buffer (except the value for the far plane, 1.0) and calculate the mean depth. Of course the entire depth buffer has to be investigated in this case:
    d_buf = glReadPixels(0, 0, self.width, self.height, GL_DEPTH_COMPONENT, GL_FLOAT)
    d_vals = [float(d_buf[i][j]) for i in range(self.width) for j in range(self.height) if d_buf[i][j] != 1]
    if len(d_vals) > 0:
        self.last_depth = (min(d_vals) + max(d_vals)) / 2 
    
    • Use the origin of the world:
    pt_drag = glm.vec3(0, 0, 0)
    o_clip  = self.proj * self.view * glm.vec4(pt_drag, 1)
    o_ndc   = glm.vec3(o_clip) / o_clip.w
    if o_ndc.z > -1 and o_ndc.z < 1:
        self.last_depth = o_ndc.z * 0.5 + 0.5 
    
    • Calculating the center of the bounding box of the scene.

    • Implement a raycasting, which identifies an object by a ray, which starts at the point of view a runs trough the cursor (mouse) position. This algorithm can be advanced by identifying the object which is "closest" to the ray, when no object is hit.

提交回复
热议问题