Я 'довольно новичок в 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, а не вектора вверх. Это также приведет к появлению новой правой оси камеры. Затем поверните камеру по вертикали (глядя вверх / вниз) вокруг новой правой оси. Однако следует быть осторожным, если камера смотрит прямо вверх или вниз, так как в этом случае вы не сможете использовать перекрестное произведение векторов направления взгляда и вверх для получения правого вектора.