Создание 3D-камеры в WebGL - почему ни один из этих методов не работает? - PullRequest
5 голосов
/ 04 октября 2011

РЕДАКТИРОВАТЬ

ОК, я пробовал камеру с кватернионами:

qyaw = [Math.cos(rot[0]/2), 0, Math.sin(rot[0]/2), 0];
qpitch = [Math.cos(rot[1]/2), 0, 0, Math.sin(rot[1]/2)];
rotQuat = quat4.multiply (qpitch, qyaw);
camRot = quat4.toMat4(rotQuat);
camMat = mat4.multiply(camMat,camRot);

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

Я пытаюсь создать в webGL 3d-камеру, которая может перемещаться по миру и вращаться вокруг осей x и y (вправо и вверх).

У меня возникает знакомая проблема (возможно, карданная блокировка?), Что как только одна из осей вращается, вращение вокруг другой облажается;например, когда вы поворачиваете вокруг оси Y на 90 градусов, вращение вокруг x становится вращением вокруг z.

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

Я использую одно решение это (адаптировано из http://www.toymaker.info/Games/html/camera.html):

function updateCam(){
    yAx = [0,1,0]; 
    xAx = [1,0,0];
    zAx = [0,0,1];

    mat4.identity(camMat);

    xRotMat = mat4.create();
    mat4.identity(xRotMat)
    mat4.rotate(xRotMat,rot[0],xAx);
    mat4.multiplyVec3(xRotMat,zAx); 
    mat4.multiplyVec3(xRotMat,yAx);


    yRotMat = mat4.create();
    mat4.identity(yRotMat)
    mat4.rotate(yRotMat,rot[1],yAx);
    mat4.multiplyVec3(yRotMat,zAx); 
    mat4.multiplyVec3(yRotMat,xAx);


    zRotMat = mat4.create();
    mat4.identity(zRotMat)
    mat4.rotate(zRotMat,rot[2],zAx);
    mat4.multiplyVec3(zRotMat,yAx); 
    mat4.multiplyVec3(zRotMat,xAx);


    camMat[0] = xAx[0];
    camMat[1] = yAx[0];
    camMat[2] = zAx[0];
    //camMat[3] = 
    camMat[4] = xAx[1]
    camMat[5] = yAx[1]; 
    camMat[6] = zAx[1];
    //camMat[7] = 
    camMat[8] = xAx[2]
    camMat[9] = yAx[2];
    camMat[10]= zAx[2];
    //camMat[11]=
    camMat[12]= -1* vec3.dot(camPos, xAx); 
    camMat[13]= -1* vec3.dot(camPos, yAx);
    camMat[14]= -1* vec3.dot(camPos, zAx);
    //camMat[15]=

    var movSpeed = 1.5 * forward;
    var movVec= vec3.create(zAx);
    vec3.scale(movVec, movSpeed);
    vec3.add(camPos, movVec);
    movVec= vec3.create(xAx);
    movSpeed = 1.5 * strafe;
    vec3.scale(movVec, movSpeed);
    vec3.add(camPos, movVec);

}

Я также пытался использовать этот метод, используя

mat4.rotate(camMat, rot[1], yAx);

вместо явного построения матрицы камеры - тот же результат.

Мой второй (фактически первый ...) метод выглядит следующим образом (rot - это массив, содержащий текущие повороты вокруг x,y и z (z всегда равно нулю):

   function updateCam(){
        mat4.identity(camRot);
        mat4.identity(camMat);
        camRot = fullRotate(rot);
        mat4.set(camRot,camMat);
        mat4.translate(camMat, camPos); 
    }

    function fullRotate(angles){
        var cosX = Math.cos(angles[0]);
        var sinX = Math.sin(angles[0]);
        var cosY = Math.cos(angles[1]);
        var sinY = Math.sin(angles[1]);
        var cosZ = Math.cos(angles[2]);
        var sinZ = Math.sin(angles[2]); 
        rotMatrix = mat4.create([cosZ*cosY, -1*sinZ*cosX + cosZ*sinY*sinX, sinZ*sinX+cosZ*sinY*cosX, 0,
            sinZ*cosY, cosZ*cosX + sinZ*sinY*sinX, -1*cosZ*sinX + sinZ*sinY*cosX, 0,
            -1*sinY, cosY*sinX, cosY*cosX, 0,
            0,0,0,1 ] );
        mat4.transpose(rotMatrix);
        return (rotMatrix);
    }

Код (я вынул большую часть стандартного освещения и т. д. и только что оставил преобразования), чтобы фактически нарисовать сцену:

   function drawScene() {
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 2000.0, pMatrix);

    mat4.identity(mvMatrix);

    for(var i=0; i<planets.length; i++){
        if (planets[i].type =="sun"){
            currentProgram = perVertexSunProgram;
        } else {
            currentProgram = perVertexNormalProgram;
        }
        alpha = planets[i].alphaFlag;

        mat4.identity(planets[i].rotMat);

        mvPushMatrix(); 
            //all the following puts planets in orbit around a central sun, but it's not really relevant to my current problem
            var rot = [0,rotCount*planets[i].orbitSpeed,0];

            var planetMat;
            planetMat = mat4.create(fullRotate(rot));

            mat4.multiply(planets[i].rotMat, planetMat);

            mat4.translate(planets[i].rotMat, planets[i].position);

            if (planets[i].type == "moon"){
                var rot = [0,rotCount*planets[i].moonOrbitSpeed,0];
                moonMat = mat4.create(fullRotate(rot));
                mat4.multiply(planets[i].rotMat, moonMat);
                mat4.translate(planets[i].rotMat, planets[i].moonPosition);
                mat4.multiply(planets[i].rotMat, mat4.inverse(moonMat));
            }

            mat4.multiply(planets[i].rotMat, mat4.inverse(planetMat));
            mat4.rotate(planets[i].rotMat, rotCount*planets[i].spinSpd, [0, 1, 0]);


                        //this bit does the work - multiplying the model view by the camera matrix, then by the matrix of the object we want to render
            mat4.multiply(mvMatrix, camMat);
            mat4.multiply(mvMatrix, planets[i].rotMat);



            gl.useProgram(currentProgram);

            setMatrixUniforms();
            gl.drawElements(gl.TRIANGLES, planets[i].VertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);     
        mvPopMatrix();
        }
    }

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

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

PS - Я 'Я все еще очень начинаю изучать WebGLd 3d математика, так что будьте нежны и говорите со мной, как кто-то, кто не слышал о матрице пару месяцев назад ... Кроме того, я знаю, что кватернионы являются хорошим решением для 3d-вращения, и это будет моей следующей попыткойТем не менее, я думаю, что мне нужно понять, почему эти два метода не работают в первую очередь ...

Ответы [ 3 ]

6 голосов
/ 05 октября 2011

Ради пояснения подумайте о блокировке карданного подвеса следующим образом: вы играли в Quake / Unreal / Call of Duty / Any Shooter от первого лица, верно? Вы знаете, как, когда вы смотрите вперед и перемещаете мышь из стороны в сторону, ваш взгляд разворачивается по хорошей широкой дуге, но если вы смотрите прямо вверх или вниз и перемещаете мышь из стороны в сторону, вы в основном просто вращаетесь вокруг одной точки? Это замок на кардан. Это то, что в значительной степени использует любая игра FPS, потому что она имитирует то, что мы делаем в реальной жизни, и поэтому большинство людей обычно не думают об этом как о проблеме.

Однако для чего-то вроде симулятора космического полета или (чаще) скелетной анимации этот тип эффекта нежелателен, и поэтому мы используем такие вещи, как кватернионы, чтобы помочь нам обойти это. вас забота о блокировке карданного подвеса для вашей камеры зависит от того, какого эффекта вы хотите достичь.

Однако я не думаю, что вы испытываете это. Это звучит так, что ваш порядок умножения матриц нарушен, и в результате ваш взгляд вращается так, как вы этого не ожидаете. Я бы попробовал поиграть с порядком, в котором вы выполняете свои вращения X / Y / Z, и посмотреть, сможете ли вы найти заказ, который даст вам желаемые результаты.

Теперь я ненавижу делать дампы кода, но это может быть полезно для вас, поэтому здесь мы идем: это код, который я использую в большинстве моих новых проектов WebGL для управления свободно плавающей камерой. Это карданный замок, но, как я упоминал ранее, в данном случае это не имеет значения. По сути, это просто дает вам элементы управления в стиле FPS, которые вы можете использовать для полета по сцене.

/**
 * A Flying Camera allows free motion around the scene using FPS style controls (WASD + mouselook)
 * This type of camera is good for displaying large scenes
 */
var FlyingCamera = Object.create(Object, {
    _angles: {
        value: null
    },

    angles: {
        get: function() {
            return this._angles;
        },
        set: function(value) {
            this._angles = value;
            this._dirty = true;
        }
    },

    _position: {
        value: null
    },

    position: {
        get: function() {
            return this._position;
        },
        set: function(value) {
            this._position = value;
            this._dirty = true;
        }
    },

    speed: {
        value: 100
    },

    _dirty: {
        value: true
    },

    _cameraMat: {
        value: null
    },

    _pressedKeys: {
        value: null
    },

    _viewMat: {
        value: null
    },

    viewMat: {
        get: function() {
            if(this._dirty) {
                var mv = this._viewMat;
                mat4.identity(mv);
                mat4.rotateX(mv, this.angles[0]-Math.PI/2.0);
                mat4.rotateZ(mv, this.angles[1]);
                mat4.rotateY(mv, this.angles[2]);
                mat4.translate(mv, [-this.position[0], -this.position[1], - this.position[2]]);
                this._dirty = false;
            }

            return this._viewMat;
        }
    },

    init: {
        value: function(canvas) {
            this.angles = vec3.create();
            this.position = vec3.create();
            this.pressedKeys = new Array(128);

            // Initialize the matricies
            this.projectionMat = mat4.create();
            this._viewMat = mat4.create();
            this._cameraMat = mat4.create();

            // Set up the appropriate event hooks
            var moving = false;
            var lastX, lastY;
            var self = this;

            window.addEventListener("keydown", function(event) {
                self.pressedKeys[event.keyCode] = true;
            }, false);

            window.addEventListener("keyup", function(event) {
                self.pressedKeys[event.keyCode] = false;
            }, false);

            canvas.addEventListener('mousedown', function(event) {
                if(event.which == 1) {
                    moving = true;
                }
                lastX = event.pageX;
                lastY = event.pageY;
            }, false);

            canvas.addEventListener('mousemove', function(event) {
                if (moving) {
                    var xDelta = event.pageX  - lastX;
                    var yDelta = event.pageY  - lastY;
                    lastX = event.pageX;
                    lastY = event.pageY;

                    self.angles[1] += xDelta*0.025;
                    while (self.angles[1] < 0)
                        self.angles[1] += Math.PI*2;
                    while (self.angles[1] >= Math.PI*2)
                        self.angles[1] -= Math.PI*2;

                    self.angles[0] += yDelta*0.025;
                    while (self.angles[0] < -Math.PI*0.5)
                        self.angles[0] = -Math.PI*0.5;
                    while (self.angles[0] > Math.PI*0.5)
                        self.angles[0] = Math.PI*0.5;

                    self._dirty = true;
                }
            }, false);

            canvas.addEventListener('mouseup', function(event) {
                moving = false;
            }, false);

            return this;
        }
    },

    update: {
        value: function(frameTime) {
            var dir = [0, 0, 0];

            var speed = (this.speed / 1000) * frameTime;

            // This is our first person movement code. It's not really pretty, but it works
            if(this.pressedKeys['W'.charCodeAt(0)]) {
                dir[1] += speed;
            }
            if(this.pressedKeys['S'.charCodeAt(0)]) {
                dir[1] -= speed;
            }
            if(this.pressedKeys['A'.charCodeAt(0)]) {
                dir[0] -= speed;
            }
            if(this.pressedKeys['D'.charCodeAt(0)]) {
                dir[0] += speed;
            }
            if(this.pressedKeys[32]) { // Space, moves up
                dir[2] += speed;
            }
            if(this.pressedKeys[17]) { // Ctrl, moves down
                dir[2] -= speed;
            }

            if(dir[0] != 0 || dir[1] != 0 || dir[2] != 0) {
                var cam = this._cameraMat;
                mat4.identity(cam);
                mat4.rotateX(cam, this.angles[0]);
                mat4.rotateZ(cam, this.angles[1]);
                mat4.inverse(cam);

                mat4.multiplyVec3(cam, dir);

                // Move the camera in the direction we are facing
                vec3.add(this.position, dir);

                this._dirty = true;
            }
        }
    }
});

В этой камере предполагается, что Z - это ваша ось "вверх", что может или не может быть правдой для вас. Он также использует объекты стиля ECMAScript 5, но это не должно быть проблемой для любого браузера с поддержкой WebGL, и он использует мою библиотеку glMatrix, но похоже, что вы уже используете это в любом случае. Основное использование довольно просто:

// During your init code
var camera = Object.create(FlyingCamera).init(canvasElement);

// During your draw loop
camera.update(16); // 16ms per-frame == 60 FPS

// Bind a shader, etc, etc...
gl.uniformMatrix4fv(shaderUniformModelViewMat, false, camera.viewMat);

Все остальное обрабатывается для вас внутренне, включая управление с клавиатуры и мыши. Может не совсем соответствовать вашим потребностям, но, надеюсь, вы сможете найти то, что вам нужно оттуда. (Примечание. По сути, это то же самое, что и камера, используемая в моей демонстрации Quake 3 , так что это должно дать вам представление о том, как она работает.)

Ладно, хватит мне болтать за один пост! Удачи!

1 голос
/ 04 октября 2011

Неважно, как вы строите свои матрицы, использование поворота эйлеровых углов (как это делают оба ваших фрагмента кода) всегда приводит к преобразованию, которое показывает проблему блокировки gimble.Взгляните на https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation в качестве отправной точки для создания преобразований, которые позволяют избежать блокировок с потрохами.

0 голосов
/ 24 июня 2013

Попробуйте мой новый проект (часть webGL2 игрового движка visual-js), основанный на glmatrix 2.0.

Активируйте события для использования камеры: App.camera.FirstPersonController = true;

примеры в реальном времени

Для важных функций камеры:

Взаимодействие с камерой

App.operation.CameraPerspective = function() {
    this.GL.gl.viewport(0, 0, wd, ht);
    this.GL.gl.clear(this.GL.gl.COLOR_BUFFER_BIT | this.GL.gl.DEPTH_BUFFER_BIT);

   // mat4.identity( world.mvMatrix )
  //  mat4.translate(world.mvMatrix  , world.mvMatrix, [ 10 , 10 , 10] );


    /* Field of view, Width height ratio, min distance of viewpoint, max distance of viewpoint, */
    mat4.perspective(this.pMatrix, degToRad( App.camera.viewAngle ), (this.GL.gl.viewportWidth / this.GL.gl.viewportHeight), App.camera.nearViewpoint , App.camera.farViewpoint );
};

manifest.js:

var App = {

    name : "webgl2 experimental",
    version : 0.3,
    events : true,
    logs : false ,
    draw_interval : 10 ,
    antialias : false ,
    camera : { viewAngle : 45 ,
               nearViewpoint : 0.1 ,
               farViewpoint : 1000 ,
               edgeMarginValue : 100 ,
               FirstPersonController : false },

    textures : [] , //readOnly in manifest
    tools : {}, //readOnly in manifest

скачать источник из: webGL 2 часть проекта visual-js GE

Old:

opengles 1.1 https://stackoverflow.com/a/17261523/1513187

Очень быстрый контроллер от первого лица с glmatrix 0.9 на основе http://learningwebgl.com/ примеров.

...