Computer Graphics: Moving in the world

2019-07-10 15:41发布

问题:

What I have implemented already

I have implemented an app which is able to render to the screen a list of polygons, with a specific static camera setting (position, look at and up vector), all in plain Java AWT without OpenGL.

I apply a model-view matrix first, then projection-to-2D matrix and then a viewport matrix.

I also implemented some basic transformation matrices on the world, such as translation, rotation of X/Y/Z axis around lookAt point and scaling around lookAt point.

What I want to achieve now

I want to be able to "move" in the world. Specifically, I want to navigate forward, backward, left and right with the keyboard arrows, and to be able to look at different points with the mouse. Just like in real games.

I guess this is done via changing the camera parameters each time and render the world again.

Is it that simple, though?

Moving in the world is just adding x,y values to camera position and look at a point?

In addition, is moving the mouse is just adding x,y to the look at a point?

Do I have to touch the up vector under any condition?

Informative answers, with additional relevant links, would also really help.

回答1:

You can implement this behavior with a structured management of matrices. Especially the view matrix. Adding something to the x/y position is not correct in most cases because the camera may be rotated.

Let's denote the current view matrix with V. If you want to move the camera, you have to calculate a new view transform:

V := Translate(-x, -y, -z) * V

, where Translate(a, b, c) is a translation matrix. -x represents the movement in the left/right direction. -y represents up/down. -z represents forward/backward.

If you want to rotate the camera, this can be done similarly:

V := Rotate(-angle, axis) * V

, where Rotate(-angle, axis) is a rotation matrix. Use rotation about the x-axis to look up/down and rotation about the y-axis to look left/right. Rotation about the z-axis is usually not needed because it introduces a roll to the camera.

The reason why most parameters are negated is that view transforms are inverse transforms. I.e. they do not position the camera object, but they re-position the world as if the camera was at the origin.



回答2:

first take a look at Understanding 4x4 homogenous transform matrices

  1. extract your camera axises vectors

    If you look at the 1st image in the linked answer you will see where are the vectors stored. Beware if you got transposed layout then the vectors are also transposed.

  2. movement

    The movement is easy you just add/sub the direction vector to the origin of your camera matrix. For example my cameras have Z- as a forward direction. So I take Z-axis vector from the matrix (which is in my case already unit) multiply it with speed and time (of the timer interval where the movement code is running). Here is example how my movement code usually looks like:

    //---------------------------------------------------------------------------
    void __fastcall Twin_main::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
        {
        // Keyboard event called on any key press
        keys.set(Key,Shift); // set key in my keymap as pressed
        _redraw=true; // key press means something in scene might change so redraw
        }
    //---------------------------------------------------------------------------
    void __fastcall Twin_main::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
        {
        // Keyboard event called on any key release
        keys.rst(Key,Shift); // set key in my keymap as not pressed
        }
    //---------------------------------------------------------------------------
    void __fastcall Twin_main::tim_updateTimer(TObject *Sender)
        {
        // Timer runing every tim_update->Interval [ms]
        double   dt=double(tim_update->Interval)/1000.0;
    
        // set speeds and transitions between view modes
        double   alfa=2.0*deg;       // angular speed
        double   v=100000.0/3.6;     // movement speed [km/hod] -> [m/s]
        // precision control
        if (keys.Shift.Contains(ssAlt )) { v*= 0.1; alfa*= 0.1; }
        if (keys.Shift.Contains(ssCtrl)) { v*=10.0; alfa*=10.0; }
    
        // rotations (local to camera space)
        if (keys.get(104)) { _redraw=true; eye.rep.lroty(+alfa); }                              // num8
        if (keys.get(105)) { _redraw=true; eye.rep.lroty(-alfa); }                              // num9
        if (keys.get(100)) { _redraw=true; eye.rep.lrotx(+alfa); }                              // num4
        if (keys.get( 97)) { _redraw=true; eye.rep.lrotx(-alfa); }                              // num1
        if (keys.get(111)) { _redraw=true; eye.rep.lrotz(+alfa); }                              // num/
        if (keys.get(106)) { _redraw=true; eye.rep.lrotz(-alfa); }                              // num*
        if (keys.get( 37)) { _redraw=true; eye.rep.lroty(+alfa); }                              // left
        if (keys.get( 39)) { _redraw=true; eye.rep.lroty(-alfa); }                              // right
    
        // movements
        if (keys.get( 96)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,-v*dt)); }        // num0
        if (keys.get(110)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,+v*dt)); }        // num.
        if (keys.get( 98)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,-v*dt,0.0)); }        // num2
        if (keys.get(101)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,+v*dt,0.0)); }        // num5
        if (keys.get(102)) { _redraw=true; eye.rep.lpos_set(vector_ld(-v*dt,0.0,0.0)); }        // num6
        if (keys.get( 99)) { _redraw=true; eye.rep.lpos_set(vector_ld(+v*dt,0.0,0.0)); }        // num3
        if (keys.get( 38)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,-v*dt)); }        // up
        if (keys.get( 40)) { _redraw=true; eye.rep.lpos_set(vector_ld(0.0,0.0,+v*dt)); }        // down
    
        keys.rfskey(); // just do some stuff for advanced keyboard things not used here
        if (_redraw) { draw(); }
        }
    //---------------------------------------------------------------------------
    

    The keys is my class holding single bit bool for eaach of the 16 bit key code allowing me to handle multiple pressed keys at once. The eye.rep is my class holding both direct and inverse transformation matrix. Its member lpos_set takes a 3D vector transforms it from local to global coordinates and set this as matrix origin. It has the same effect as the above process I mentioned. The deg is just constant 1 degree in radians and lrot? are the local rotations see next bullet.

  3. rotations

    I do not use Euler angles as I hate them. They got glitches needed handling poles differently and often causing problems. Have you play any games and the camera angular rotation got stuck not allowing you rotate where you want? Or even reverse? Then it is due to Euler angles used for camera.

    I use local rotations lrot? instead (see the linked answer how they work). They have no bounds or glitches. The only thing that you need to take in mind is accuracy. If you are rotating matrix many times you loosing precision. So every few rotations ensure its orthogonality or orthonormality. For that you need just use cross product to make the axises perpendicular again and set their length to 1 or whatever you use. I have a counter in my matrix class incrementing each change and when it hits a treshold the orthonormality is restored.

As you can see I use numpad for the movements the arrows are for turn left/right and go forward/backward. Most people nowadays use WSAD but that is extremly uncomfortable for me. The code is taken from one of my VCL apps so you need to change the events to match yours.

You can also add mouse handler for turning ... In case you need Joystick see

  • How to get JoyStick Z rotation messages in C++

but I doubt if it is applicable for JAVA as it is for Windows.