OpenGL.Shader: 5-Camera3D lens operation of game engine

OpenGL.Shader: 5-Camera3D lens operation of game engine

 

This article records the most common lens operations in game engine development. Those who have played popular mobile games such as "Glory of the King", "PlayerUnknown's Battlegrounds", and "Onmyoji" should know how common this lens operation is. Next, operate a wave of show code around this lens. Related project address: https://github.com/MrZhaozhirong/NativeCppApp


#include "CELLMath.hpp"
using namespace CELL;

class Camera3D
{
public:
    real3       _eye;   //视点
    real3       _target;//观察点
    real3       _dir;   //观察向量:视点->观察点
    real3       _up;    //观察向量竖直向量
    real3       _right; //观察向量右向向量

    matrix4r    _matView;       //视图矩阵
    matrix4r    _matProj;       //投影矩阵
    matrix4r    _matWorld;      //世界坐标

    real2       _ViewPortSize;  //视口大小

    Camera3D(   const real3& target = real3(0,0,0),
                const real3& eye = real3(0,10,10),
                const real3& right = real3(1,0,0)   )
    {
        _target     =   target;
        _eye        =   eye;
        _right      =   right;
        _up         =   normalize(cross(_right, _dir));
        _dir        =   normalize(_target - _eye);
        // 初始化单位矩阵
        _matView    =   CELL::matrix4r(1);
        _matProj    =   CELL::matrix4r(1);
        _matWorld   =   CELL::matrix4r(1);
    }

    ~Camera3D() {}

};

First, let’s take a look at the composition of Camera3D, where real2 real3 matrix4r is the custom object of CELLMath.hpp introduced earlier, real represents the decimal precision value, mantissa 2/3/4 represents the number of elements, real2 That is (x, y), real3 stands for (x, y, z), real4 stands for (x, y, z, w), which is enough for us to use in OpenGL. matrix4r is also easy to understand, which is a 4x4 matrix space. (The custom object also includes related function methods, operator overloading, and the use of Cpp templates. It requires a certain degree of C++ knowledge and linear algebra knowledge to master, students who have time strongly recommend reading them multiple times.)

_eye: point of view, marking the position of our eyes; _target: observation point, indicating the target position of our eyes;

_dir: Observation direction, vector _target-vector _eye = vector (eye->target), the direction is the minus to the minus, which is the geometric knowledge of high school mathematics;

_up and _right are two direction vectors, used to indicate the viewing direction of our eyes, they are perpendicular to each other, and then perpendicular to the _dir viewing direction;

Based on this vertical characteristic, we can calculate the direction of another vector through the cross product of any two vectors, and then normalize to get the unit length.

As shown in the following schematic diagram: the yellow point is _eye, the black point is _target; the golden line from yellow to black is _dir; the blue line is _up; the gray is naturally _right; it can be seen that the three are in pairs It forms a plane and is perpendicular to this plane to form a complete camera coordinate system. (But don’t confuse it with the world coordinate system)

Then there are three matrices: _matView view matrix, _matProj projection matrix, _matWorld world coordinate matrix;

The first two matrices have been introduced before, please go here if you have any questions . How to understand this _matWorld world coordinate matrix is ​​actually a model matrix, but this model is for the entire OpenGL world coordinate. Don’t just zoom in and zoom out the global model of the _matWorld world (_matWorld) casually.

virtual void    update()
{
    _matView    =   CELL::lookAt(_eye,_target,_up);
}
// 正交投影
void    ortho( real left, real right, real bottom, real top, real zNear, real zFar )
{
    _matProj    =   CELL::ortho(left,right,bottom,top,zNear,zFar);
}
// 透视投影
void    perspective(real fovy, real aspect, real zNear, real zFar)
{
   _matProj    =   CELL::perspective<real>(fovy,aspect,zNear,zFar);
}

The related methods are as above, the other get/set will not be posted to save chapters. The core implementation is in CELLMath.hpp.

 

0. Introduce simple lens operation

Requirements:
Swipe the screen with your finger left and right, the lens can rotate around the observation point (_target);
slide your finger up and down the screen, the lens can also adjust the eye point (_eye) around the observation point;
two-finger operation can zoom in/push away from the observation point The distance from (_target) to the viewpoint (_eye);

Add OnTouchListener to SurfaceView to parse UP/DOWN/MOVE and other related events. The approximate code is as follows:

    private class GLViewTouchListener implements View.OnTouchListener {
        private int mode = 0;
        private float oldDist;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // -------------判断多少个触碰点---------------------------------
            switch (event.getAction() & MotionEvent.ACTION_MASK ){
                case MotionEvent.ACTION_DOWN:
                    mode = 1;
                    break;
                case MotionEvent.ACTION_UP:
                    mode = 0;
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    mode -= 1;
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    oldDist = spacing(event);
                    mode += 1;
                    break;
            }

            if(event.getAction() == MotionEvent.ACTION_DOWN){
                if(mode == 1) {
                    final float x = event.getX();
                    final float y = event.getY();
                    nativeEGL.handleTouchDown(x, y);
                }

            }else if(event.getAction() ==MotionEvent.ACTION_MOVE){
                if (mode == 1) {
                    final float x = event.getX();
                    final float y = event.getY();
                    nativeEGL.handleTouchDrag(x,y);
                }
                if (mode == 2) {
                    //双指操作
                    float newDist = spacing(event);
                    if ( (newDist > oldDist + 20) || (newDist < oldDist - 20) ) {
                        final float distance = newDist - oldDist;
                        nativeEGL.handleMultiTouch(distance);
                        oldDist = newDist;
                    }
                }
            }else if(event.getAction() == MotionEvent.ACTION_UP){
                if (mode == 1) {
                    final float x = event.getX();
                    final float y = event.getY();
                    nativeEGL.handleTouchUp(x,y);
                }
            }
            return true;
        }
    }

First analyze the sliding operation of a single finger. Paste the relevant code directly:

void GL3DRender::handleTouchDown(float x, float y) {
    this->mLastX = x;
    this->mLastY = y;
}

void GL3DRender::handleTouchDrag(float x, float y) {
    float offsetX = this->mLastX - x;
    offsetX /= 10;
    mCamera3D.rotateViewY(offsetX);

    float offsetY = this->mLastY - y;
    offsetY /= 50;
    mCamera3D.rotateViewX(offsetY);

    this->mLastX = x;
    this->mLastY = y;
}

void GL3DRender::handleTouchUp(float x, float y) {
    this->mLastX = 0;
    this->mLastY = 0;
}

The handleTouchDrag function is to handle the logic of sliding, passing in the position of the screen coordinate point that slides at each moment, and subtracting the input coordinate point from the coordinate point at the previous moment to obtain the offset value.

 

1. The lens slides left and right with the screen

The screen coordinate system x is horizontal, and the requirement is to rotate left and right with sliding, that is, rotate along the Y axis of the OpenGL coordinate system as the center of the axis. The logic is implemented in the rotateViewY method. Next, let's see the detailed implementation.

    /**
    *   下面的函数的功能是将摄像机的观察方向绕某个方向轴旋转一定的角度
    *   改变观察者的位置,目标的位置不变化
    */
    virtual void    rotateViewY(real angle)
    {
        real        len(0);
        matrix4r    mat(1);
        mat.rotate(angle, real3(0, 1, 0));
        // 定义单位矩阵,沿着Y轴旋转angle个角度。

        _dir        =   _dir * mat; //新的观察方向
        _up         =   _up * mat; //新的头顶方向
        _right      =   CELL::normalize(cross(_dir, _up));
                                    //用新的_dir和_up计算出新的_right
        //接下来重点算出新的视点
        // 先用旧的_eye和_target计算两点间的距离长度,这相当于旧的_dir.length
        len         =   CELL::length(_eye - _target);
        // 然后保持观察点不变,利用新的_dir为方向,len为步伐,计算出新的_eye
        _eye        =   _target - _dir * len;
        // 更新视图矩阵
        _matView    =   CELL::lookAt<real>(_eye, _target, _up);
    }

From the comments, we know the whole mathematical logic process. It is worth noting to calculate the new viewpoint: many students forgot to find out the step length of len. This len is the real space distance between _eye and _target, _dir = _target-_eye This is a direction vector, which starts from _eye and points to the target point at _target. The difference in meaning between the two is quite big. Since dir = target-eye, then the rotated eye = (keep unchanged) target-the rotated dir*len, and finally update the view matrix.

Effect picture:

 

2. The lens slides up and down with the screen

Similarly, the screen coordinate y is vertical, and the requirement is to adjust it up and down with sliding, that is, rotate along the horizon of the OpenGL coordinate system (also known as the X axis but not the X axis) as the axis center, and the logic is implemented in the rotateViewX method .

    virtual void    rotateViewX(real angle)
    {
        real        len(0);
        matrix4r    mat(1);
        mat.rotate(angle, _right); 
        // 重点 为何不是的模板代码?real3(1, 0, 0)
        
        _dir        =   _dir * mat;
        _up         =   _up * mat;
        _right      =   CELL::normalize(cross(_dir,_up));
        len         =   CELL::length(_eye - _target);
        _eye        =   _target - _dir * len;
        _matView    =   CELL::lookAt(_eye,_target,_up);
    }

The code logic is roughly the same as the rotateViewY above, the only difference is the axis of rotation, why not the template code mat.rotate(angle, real3(1, 0, 0)); This is because it is not fixed around the X of OpenGL The axis rotation is to rotate in the horizontal direction. Please understand this point clearly!

If you don’t understand, try it. The operation is as follows: the camera is at the position (0,0,10), facing the XY plane, and then rotateViewY(90) to the right reaches the new position (10,0,0). ), is your horizontal direction still the X axis direction at this time? Obviously not.

Then some students may ask, why the rotateViewY method is not mat.rotate(angle, _up)? Because of the logic of rotating left and right, it is indeed rotating along the Y axis! Still the same example: the camera is at the (0,0,10) position, facing the XY plane, and then rotateViewX(90) up the original _up to the new position (0,10,0) Directly facing the XZ plane, the current _up is along the Z axis, which is obviously not the axis we want to rotate.

Effect picture:

 

3. Close/push the distance between the camera and the observation point

void GL3DRender::handleMultiTouch(float distance) {
    LOGD("handleMultiTouch distance:%f", distance);
    real    present =   distance > 0 ? 0.9f : 1.1f;
    real3 target = mCamera3D.getTarget();
    mCamera3D.scaleCameraByPos(target, present);
}

The logic interface is very simple, judging whether it is stretching or approaching by the difference between the two fingers at each moment.

The real implementation is scaleCameraByPos, which can be known by naming that this is a designated fixed point, and zoom the camera according to the fixed point position. Here we have been looking at _target, so pos = _target. Then in-depth analysis of the geometric mathematical principles behind.

    // 指定点推进摄像机
    virtual void    scaleCameraByPos(const real3& pos,real present)
    {   
        // 计算眼睛到定点的方向
        real3   dir     =   CELL::normalize(pos - _eye);
        // 计算眼睛到定点 下一刻 的距离,即拉近/伸远的距离
        real    dis     =   CELL::length(pos - _eye) * present;
        // 计算眼睛到当前观察点的距离
        real    disCam  =   CELL::length(_target - _eye) * present;
        // 计算眼睛到当前观测点的方向向量
        real3   dirCam  =   CELL::normalize(_target - _eye);

        // 新的眼睛 = pos - 新的dir*新的距离dis
        _eye    =   pos - dir * dis;
        // 再利用新的眼睛位置,推算新的target
        _target =   _eye + dirCam * disCam;

        _matView=   CELL::lookAt<real>(_eye, _target, _up);
    }

This is a general method, because pos is any point, so we can imitate the function of other 3D games to walk automatically.

Effect picture:

Related project source code:  https://github.com/MrZhaozhirong/NativeCppApp   reference GL3DRender.cpp Camera3D.hpp

Guess you like

Origin blog.csdn.net/a360940265a/article/details/89527676