Opengl es 在WebGL中创建3D自由摄影机-为什么这两种方法都不起作用?

Opengl es 在WebGL中创建3D自由摄影机-为什么这两种方法都不起作用?,opengl-es,3d,camera,webgl,Opengl Es,3d,Camera,Webgl,编辑 好的,我试过使用四元数的相机: 我也遇到了同样的问题。所以我猜这不是万向节锁。我试着改变矩阵相乘的顺序,但它只是相机矩阵*模型视图矩阵,然后是对象矩阵*模型视图。没错,不是吗 我正在尝试在webGL中构建一个3d相机,它可以在世界各地移动,并且可以绕x轴和y轴(右轴和上轴)旋转 我得到了一个熟悉的问题(可能是万向节锁?),一旦其中一个轴旋转,围绕另一个轴的旋转就被拧紧了;例如,当围绕Y轴旋转90度时,围绕x的旋转将变成围绕z的旋转 我知道这是一个常见的问题,有大量的指南来构建一个可以避免这

编辑

好的,我试过使用四元数的相机:

我也遇到了同样的问题。所以我猜这不是万向节锁。我试着改变矩阵相乘的顺序,但它只是相机矩阵*模型视图矩阵,然后是对象矩阵*模型视图。没错,不是吗

我正在尝试在webGL中构建一个3d相机,它可以在世界各地移动,并且可以绕x轴和y轴(右轴和上轴)旋转

我得到了一个熟悉的问题(可能是万向节锁?),一旦其中一个轴旋转,围绕另一个轴的旋转就被拧紧了;例如,当围绕Y轴旋转90度时,围绕x的旋转将变成围绕z的旋转

我知道这是一个常见的问题,有大量的指南来构建一个可以避免这个问题的相机,但据我所知,我已经实现了两种不同的解决方案,我仍然遇到同样的问题。坦白地说,这让我头疼

我使用的一个解决方案是(改编自):

我也试着使用这个方法

mat4.rotate(camMat, rot[1], yAx);
而不是显式构建摄影机矩阵-相同的结果

我的第二个(实际上是第一个…)方法如下所示(rot是一个数组,包含当前围绕x、y和z的旋转(z始终为零):

实际绘制场景的代码(我去掉了大部分样板gl照明材料等,只留下了变换)是:

   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();
        }
    }
函数drawsecene(){
总图视口(0,0,总图视口宽度,总图视口高度);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.透视图(45,gl.viewportWidth/gl.viewportHeight,0.12000.0,pMatrix);
mat4.恒等式(mvMatrix);

对于(var i=0;i,无论您如何构建矩阵,使用euler角度旋转(就像您的两个代码片段所做的那样)总是会导致显示万向锁问题的转换


你可能想把它作为创建避免框架锁的转换的起点。

为了澄清起见,可以这样考虑框架锁:你玩过地震/虚幻/使命召唤/任何第一人称射击游戏,对吗?你知道当你向前看时,如何左右移动鼠标,你的视线就会摇摆一个很宽的弧线,但是如果你向上或向下看并左右移动鼠标,你基本上只是紧紧地围绕一个点旋转?这是万向锁。这是几乎所有FPS游戏都使用的东西,因为它恰好模仿了我们在现实生活中会做的事情,因此大多数人通常不会认为这是一个问题

然而,对于太空飞行模拟或(更常见的)骨骼动画之类的东西,这种效果是不可取的,因此我们使用四元数之类的东西来帮助我们绕过它。您是否关心相机的万向节锁取决于您希望达到的效果

然而,我认为你没有经历过这种情况。这听起来像是你的矩阵乘法顺序被弄乱了,结果你的视图以一种你意想不到的方式旋转。我会尝试按照你进行X/Y/Z旋转的顺序,看看你是否能找到一个比预期结果更高的顺序

现在,我讨厌做代码转储,但这可能对你有用,所以我们来吧:这是我在大多数更新的WebGL项目中用于管理自由浮动相机的代码。它是万向节锁定的,但正如我前面提到的,在这种情况下,它实际上并不重要。基本上,它只提供FPS样式的控件,你可以使用它在场景中飞行e

/**
 * 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;
            }
        }
    }
});
其他一切都由您内部处理,包括键盘和鼠标控制。可能不完全符合您的需要,但希望您能从中收集到所需的内容。(注意:这与my中使用的摄像头基本相同,因此您应该了解它的工作原理。)

好了,我的一篇博文就到此为止!祝你好运!

试试我基于glmatrix 2.0的新项目(visual js游戏引擎的webGL2部分)

激活摄像机使用事件:App.camera.FirstPersonController=true

对于摄像头的重要功能:

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
下载来源:

旧版:

opengles 1.1


基于示例的glmatrix 0.9非常快速的第一人称控制器。

正如我所说,我在这方面很新,所以我不太想问,但确实是这样吗?我可以看到如何绕一个轴(0,1,0)旋转,然后简单地绕另一个轴旋转,比如(1,0,0),将不起作用,因为轴在第一次旋转后会移动。然而,我了解到上述两种方法,即使它们使用欧拉,也避免了该问题?事实上,我遇到的是万向节锁吗?发生时你如何识别它?无论如何,我下一步将尝试四元数-我担心的是不管怎么说,我的问题是源于代码中的其他内容……识别万向节锁的最简单方法是查看给定配置中的某些旋转情况。给定旋转顺序y、x、z和旋转(0°、90°、0°),旋转y和z会导致绕同一轴旋转。我不知道万向锁是否真的对您的应用程序有问题。通常,Euler旋转对于“检查相机”来说是可以的对于3d场景,但对于太空飞行模拟器可能不合适。感谢Tobias的回复。这可能是我的问题-我只是尝试绕x和y旋转,当x为90度时,绕y旋转确实绕z旋转,但在0-90度之间的所有旋转中效果都是明显的,在90度时效果最差。哦,是时候潜水了nto四元数…只需再澄清一点,连续的Euler旋转,无论您使用的轴的顺序如何,对于给定的90°旋转,始终会在两个轴上产生万向节锁,这就是为什么四元数更好
   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();
        }
    }
/**
 * 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;
            }
        }
    }
});
// 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);
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 );
};
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