Вращение 3D-камеры в OpenGL: как предотвратить дрожание камеры?

Я 'довольно новичок в OpenGL и 3D программировании, но я'начал реализовывать вращение камеры с помощью кватернионов, основываясь на учебнике с http://www.cprogramming.com/tutorial/3d/quaternions.html . Все это написано на Java с использованием JOGL.

Я понимаю, что подобные вопросы задаются довольно часто, но я искал и не могу найти решение, которое работает, поэтому я решил, что это может быть проблема конкретно с моим кодом.

Итак, проблема заключается в том, что если я делаю два разных последовательных вращения по одной или нескольким осям, происходит дрожание и странное вращение. Первое вращение по оси an, как отрицательное, так и положительное, работает нормально. Однако, если я вращаю положительно вдоль оси an, а затем вращаю отрицательно по этой оси, то вращение будет дрожать туда-сюда, как если бы я чередовал положительное и отрицательное вращение.

Если я автоматизирую вращение (например, вращение влево 500 раз, затем вращение вправо 500 раз), то оно работает правильно, что навело меня на мысль, что это может быть связано с нажатием клавиш. Однако вращение также происходит неправильно (за неимением лучшего слова), если я вращаю вокруг оси x, а затем вращаю вокруг оси y.

В любом случае, у меня есть класс рендерера со следующим циклом отображения для отрисовки "узлов сцены':

private void render(GLAutoDrawable drawable) {
    GL2 gl = drawable.getGL().getGL2();
    gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);

    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective(70, Constants.viewWidth / Constants.viewHeight, 0.1, 30000);
    gl.glScalef(1.0f, -1.0f, 1.0f); //flip the y axis

    gl.glMatrixMode(GL2.GL_MODELVIEW);
    gl.glLoadIdentity();

    camera.rotateCamera();
    glu.gluLookAt(camera.getCamX(), camera.getCamY(), camera.getCamZ(), camera.getViewX(), camera.getViewY(), camera.getViewZ(), 0, 1, 0);
    drawSceneNodes(gl);
}

private void drawSceneNodes(GL2 gl) {
    if (currentEvent != null) {
        ArrayList<SceneNode> sceneNodes = currentEvent.getSceneNodes();
        for (SceneNode sceneNode : sceneNodes) {
            sceneNode.update(gl);
        }
    }

    if (renderQueue.size() > 0) {
        currentEvent = renderQueue.remove(0);
    }
}

Вращение выполняется в классе камеры следующим образом:

public class Camera {
    private double width;
    private double height;
    private double rotation = 0;

    private Vector3D cam = new Vector3D(0, 0, 0);
    private Vector3D view = new Vector3D(0, 0, 0);
    private Vector3D axis = new Vector3D(0, 0, 0);

    private Rotation total = new Rotation(0, 0, 0, 1, true);

    public Camera(GL2 gl, Vector3D cam, Vector3D view, int width, int height) {
        this.cam = cam;
        this.view = view;
        this.width = width;
        this.height = height;
    }

    public void rotateCamera() {
        if (rotation != 0) {
            //generate local quaternion from new axis and new rotation
            Rotation local = new Rotation(Math.cos(rotation/2), Math.sin(rotation/2 * axis.getX()), Math.sin(rotation/2 * axis.getY()), Math.sin(rotation/2 * axis.getZ()), true);

            //multiply local quaternion and total quaternion
            total = total.applyTo(local);

            //rotate the position of the camera with the new total quaternion
            cam = rotatePoint(cam);

            //set next rotation to 0
            rotation = 0;
        }
    }

    public Vector3D rotatePoint(Vector3D point) {
        //set world centre to origin, i.e. (width/2, height/2, 0) to (0, 0, 0)
        point = new Vector3D(point.getX() - width/2, point.getY() - height/2, point.getZ());

        //rotate point
        point = total.applyTo(point);

        //set point in world coordinates, i.e. (0, 0, 0) to (width/2, height/2, 0) 
        return new Vector3D(point.getX() + width/2, point.getY() + height/2, point.getZ());
    }

    public void setAxis(Vector3D axis) {
        this.axis = axis;
    }

    public void setRotation(double rotation) {
        this.rotation = rotation;
    }
}

Метод rotateCamera генерирует новые постоянные кватернионы из нового вращения и предыдущих вращений, а метод rotatePoint просто умножает точку на матрицу вращения, сгенерированную из постоянных кватернионов.

Ось вращения и угол поворота задаются простым нажатием клавиш следующим образом:

    @Override
public void keyPressed(KeyEvent e) {
    if (e.getKeyCode() == KeyEvent.VK_W) {
        camera.setAxis(new float[] {1, 0, 0});
        camera.setRotation(0.1f);
    }
    if (e.getKeyCode() == KeyEvent.VK_A) {
        camera.setAxis(new float[] {0, 1, 0});
        camera.setRotation(0.1f);
    }
    if (e.getKeyCode() == KeyEvent.VK_S) {
        camera.setAxis(new float[] {1, 0, 0});
        camera.setRotation(-0.1f);
    }
    if (e.getKeyCode() == KeyEvent.VK_D) {
        camera.setAxis(new float[] {0, 1, 0});
        camera.setRotation(-0.1f);
    }
}

Надеюсь, я достаточно подробно описал ситуацию. Любая помощь будет очень признательна.

По поводу дрожания: Я не вижу никакого цикла рендеринга в вашем коде. Как запускается метод рендеринга? По таймеру или по событию?

Непонятные вращения при вращении вокруг двух осей, вероятно, связаны с тем, что вам нужно вращать ось второго вращения вместе с общим вращением первой оси. Вы не можете просто применить вращение вокруг оси X или Y глобальной системы координат. Вы должны применить вращение вокруг осей вверх и вправо камеры.

Я предлагаю создать класс камеры, который хранит векторы направления камеры вверх, вправо и вид, и применить вращение непосредственно к этим осям. Если это камера для FPS, то вы захотите повернуть камеру горизонтально (взгляд влево/вправо) вокруг абсолютной оси Y, а не вектора вверх. Это также приведет к появлению новой правой оси камеры. Затем поверните камеру по вертикали (глядя вверх / вниз) вокруг новой правой оси. Однако следует быть осторожным, если камера смотрит прямо вверх или вниз, так как в этом случае вы не сможете использовать перекрестное произведение векторов направления взгляда и вверх для получения правого вектора.

Комментарии (5)