单击可放大 WebGL

发布于 2024-10-31 20:59:11 字数 599 浏览 5 评论 0原文

我需要在 WebGL 中实现相当于“Google 地图”样式的缩放效果。具体来说,我有一个简单的二维场景,它始终垂直于相机。当用户单击场景时,相机应缩放到单击上方但更接近二维场景的位置。

例如,请参阅这个 jsfiddle,它实现了场景但没有缩放:

http://jsfiddle.net/JqBs8/4/

如果您有启用 WebGL 的浏览器,您应该会看到在 Z 轴 -7 处渲染的三角形和正方形(二维)。我已经放入了一个占位符 handleMouseUp() 事件处理程序来记录任何单击事件,但我对如何将单击事件给出的坐标转换为相机的新位置(或者我猜相当于一个新位置)有点迷失视图矩阵)。

(jsfiddle 基于 Learningwebgl.com 的教程 1,并使用 glMatrix http://code.google。 com/p/glmatrix/ 用于矩阵运算的库,请记住,这是 WebGL,与 OpenGL ES 类似,并且无法访问 glu* 函数。)

I need to implement what amounts to a "Google Maps" style zoom effect in WebGL. Specifically I have a simple 2-dimensional scene that is always perpendicular to the camera. When a user clicks on the scene, the camera should zoom to a location that is over the click, but closer to the 2-dimensional scene.

For example see this jsfiddle, that implements the scene but no zooming:

http://jsfiddle.net/JqBs8/4/

If you have a WebGL enabled browser, you should see a triangle and a square (2-dimensional) rendered at -7 on the Z axis. I have put in a placeholder handleMouseUp() event handler that logs any click events, but I'm a little lost as to how to translate the coordinates given by the click event into a new location for the camera (or I guess equivalently a new view matrix).

(The jsfiddle is based on tutorial 1 from learningwebgl.com and uses the glMatrix http://code.google.com/p/glmatrix/ library for matrix operations. Keep in mind that this is WebGL, which is similar to OpenGL ES, and has no access to glu* functions.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

若沐 2024-11-07 20:59:11

我在这个 jsfiddle 中写了一些应该对你有帮助的东西。

https://jsfiddle.net/pliepa/Lfroxj02/01

(或 https://codepen.io/brainjam/pen/gBZyGm)

只需单击 WebGL 窗口即可放大到所在位置鼠标正在指向。

基本思想是,WebGL 窗口中的一个点是通过使用投影矩阵 pMatrix 和视图矩阵从 3 空间投影得到的(视图矩阵取决于相机的位置和方向)它正在寻找)。这些矩阵的组合在代码中被命名为pvMatrix

如果您想要从窗口返回到三个空间的相反变换,则必须采用剪辑空间坐标 (x,y,z) 并使用 pvMatrix 的逆将其“取消投影”回 3D。在剪辑空间中,坐标的范围是 [-1,1],z 坐标是深度。

在 OpenGL 世界中,这些转换在 gluProject()gluUnproject() 中实现(您可以通过 Google 获取更多信息和源代码)。

在 jsfiddle 示例中,我们计算剪辑空间中的 (x,y) 坐标,然后针对两个不同的 z 值取消投影 (x,y,z)。由此我们得到 3D 空间中映射到 (x,y) 的两个点,并且我们可以推断出方向向量。然后我们可以朝那个方向移动相机以获得变焦效果。

在代码中,相机位置位于eye向量的负值处。

此示例向您展示如何按照您单击的方向移动相机。如果你想真正移向场景中的特定对象,你必须实现对象选择之类的东西,这是另一回事。我给出的示例不知道场景中的对象。

I've written something in this jsfiddle that should help you.

https://jsfiddle.net/pliepa/Lfroxj02/01

(or https://codepen.io/brainjam/pen/gBZyGm)

Just click on the WebGL window to zoom in to where the mouse is pointing.

The basic idea is that a point in the WebGL window is obtained by projecting it from 3-space using the projection matrix pMatrix and the view matrix (the view matrix depends on where the camera is and the direction in which it is looking). The composition of these matrices is named pvMatrix in the code.

If you want the opposite transform from the window back to three space, you have to take a clip space coordinate (x,y,z) and 'unproject' it back into 3D using the inverse of pvMatrix. In clip space, coordinates are in the range [-1,1], and the z coordinate is depth.

In the OpenGL world, these transforms are implemented in gluProject() and gluUnproject() (which you can Google for more information and source code).

In the jsfiddle example, we calculate the (x,y) coordinates in clip space, and then unproject (x,y,z) for two different values of z. From that we get two points in 3D space that map onto (x,y), and we can infer a direction vector. We can then move the camera in that direction to get a zoom effect.

In the code, the camera position is at the negation of the eye vector.

This example shows you how to move the camera in the direction that you are clicking. If you want to actually move towards specific objects in the scene, you have to implement something like object selection, which is a different kettle of fish. The example I've given is unaware of the objects in the scene.

蓝眸 2024-11-07 20:59:11

这确实是 Brainjam 答案的一部分,但为了防止 jsfiddle 消失,我想确保代码已存档。这是主要部分:

  function handleMouseUp(event) {
      var world1 = [0,0,0,0] ;
      var world2 = [0,0,0,0] ;
      var dir = [0,0,0] ;
      var w = event.srcElement.clientWidth ;
      var h = event.srcElement.clientHeight ;
      // calculate x,y clip space coordinates
      var x = (event.offsetX-w/2)/(w/2) ;
      var y = -(event.offsetY-h/2)/(h/2) ;
      mat4.inverse(pvMatrix, pvMatrixInverse) ;
      // convert clip space coordinates into world space
      mat4.multiplyVec4(pvMatrixInverse, [x,y,-1,1], world1) ;
      vec3.scale(world1,1/world1[3]) ;
      mat4.multiplyVec4(pvMatrixInverse, [x,y,0,1], world2) ;
      vec3.scale(world2,1/world2[3]) ;
      // calculate world space view vector
      vec3.subtract(world2,world1,dir) ;
      vec3.normalize(dir) ;
      vec3.scale(dir,0.3) ;
      // move eye in direction of world space view vector
      vec3.subtract(eye,dir) ;
      drawScene();
      console.log(event)
  }

以及整个 JS...

    var gl;
    function initGL(canvas) {
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }


    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }

        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }

        var shader;
        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }

        gl.shaderSource(shader, str);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }

        return shader;
    }


    var shaderProgram;

    function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(shaderProgram);

        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    }


    var mvMatrix = mat4.create();
    var pMatrix = mat4.create();

    function setMatrixUniforms() {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
    }



    var triangleVertexPositionBuffer;
    var squareVertexPositionBuffer;

    function initBuffers() {
        triangleVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        var vertices = [
             0.0,  1.0,  0.0,
            -1.0, -1.0,  0.0,
             1.0, -1.0,  0.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        triangleVertexPositionBuffer.itemSize = 3;
        triangleVertexPositionBuffer.numItems = 3;

        squareVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
        vertices = [
             1.0,  1.0,  0.0,
            -1.0,  1.0,  0.0,
             1.0, -1.0,  0.0,
            -1.0, -1.0,  0.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        squareVertexPositionBuffer.itemSize = 3;
        squareVertexPositionBuffer.numItems = 4;
    }

var eye = vec3.create([0,0,0]) ;  // negation of actual eye position

var pvMatrix = mat4.create();

var pvMatrixInverse = mat4.create();

    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, 100.0, pMatrix);

        mat4.identity(mvMatrix);

        // calculate the view transform mvMatrix, and the projection-view matrix pvMatrix
        mat4.translate(mvMatrix, eye);        
        mat4.multiply(pMatrix,mvMatrix,pvMatrix) ;

        mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);


        mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
    }

  function handleMouseUp(event) {
      var world1 = [0,0,0,0] ;
      var world2 = [0,0,0,0] ;
      var dir = [0,0,0] ;
      var w = event.srcElement.clientWidth ;
      var h = event.srcElement.clientHeight ;
      // calculate x,y clip space coordinates
      var x = (event.offsetX-w/2)/(w/2) ;
      var y = -(event.offsetY-h/2)/(h/2) ;
      mat4.inverse(pvMatrix, pvMatrixInverse) ;
      // convert clip space coordinates into world space
      mat4.multiplyVec4(pvMatrixInverse, [x,y,-1,1], world1) ;
      vec3.scale(world1,1/world1[3]) ;
      mat4.multiplyVec4(pvMatrixInverse, [x,y,0,1], world2) ;
      vec3.scale(world2,1/world2[3]) ;
      // calculate world space view vector
      vec3.subtract(world2,world1,dir) ;
      vec3.normalize(dir) ;
      vec3.scale(dir,0.3) ;
      // move eye in direction of world space view vector
      vec3.subtract(eye,dir) ;
      drawScene();
      console.log(event)
  }

    function webGLStart() {
        var canvas = document.getElementById("lesson01-canvas");
        initGL(canvas);
        initShaders();
        initBuffers();

        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.enable(gl.DEPTH_TEST);

        canvas.onmouseup = handleMouseUp;

        drawScene();
    }

webGLStart();

This is really part of brainjam's answer, but just in case that jsfiddle were to go away, I wanted to make sure the code was archived. Here is the primary bit:

  function handleMouseUp(event) {
      var world1 = [0,0,0,0] ;
      var world2 = [0,0,0,0] ;
      var dir = [0,0,0] ;
      var w = event.srcElement.clientWidth ;
      var h = event.srcElement.clientHeight ;
      // calculate x,y clip space coordinates
      var x = (event.offsetX-w/2)/(w/2) ;
      var y = -(event.offsetY-h/2)/(h/2) ;
      mat4.inverse(pvMatrix, pvMatrixInverse) ;
      // convert clip space coordinates into world space
      mat4.multiplyVec4(pvMatrixInverse, [x,y,-1,1], world1) ;
      vec3.scale(world1,1/world1[3]) ;
      mat4.multiplyVec4(pvMatrixInverse, [x,y,0,1], world2) ;
      vec3.scale(world2,1/world2[3]) ;
      // calculate world space view vector
      vec3.subtract(world2,world1,dir) ;
      vec3.normalize(dir) ;
      vec3.scale(dir,0.3) ;
      // move eye in direction of world space view vector
      vec3.subtract(eye,dir) ;
      drawScene();
      console.log(event)
  }

And the entirety of the JS...

    var gl;
    function initGL(canvas) {
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }


    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }

        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }

        var shader;
        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }

        gl.shaderSource(shader, str);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }

        return shader;
    }


    var shaderProgram;

    function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(shaderProgram);

        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    }


    var mvMatrix = mat4.create();
    var pMatrix = mat4.create();

    function setMatrixUniforms() {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
    }



    var triangleVertexPositionBuffer;
    var squareVertexPositionBuffer;

    function initBuffers() {
        triangleVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        var vertices = [
             0.0,  1.0,  0.0,
            -1.0, -1.0,  0.0,
             1.0, -1.0,  0.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        triangleVertexPositionBuffer.itemSize = 3;
        triangleVertexPositionBuffer.numItems = 3;

        squareVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
        vertices = [
             1.0,  1.0,  0.0,
            -1.0,  1.0,  0.0,
             1.0, -1.0,  0.0,
            -1.0, -1.0,  0.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        squareVertexPositionBuffer.itemSize = 3;
        squareVertexPositionBuffer.numItems = 4;
    }

var eye = vec3.create([0,0,0]) ;  // negation of actual eye position

var pvMatrix = mat4.create();

var pvMatrixInverse = mat4.create();

    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, 100.0, pMatrix);

        mat4.identity(mvMatrix);

        // calculate the view transform mvMatrix, and the projection-view matrix pvMatrix
        mat4.translate(mvMatrix, eye);        
        mat4.multiply(pMatrix,mvMatrix,pvMatrix) ;

        mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);


        mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
    }

  function handleMouseUp(event) {
      var world1 = [0,0,0,0] ;
      var world2 = [0,0,0,0] ;
      var dir = [0,0,0] ;
      var w = event.srcElement.clientWidth ;
      var h = event.srcElement.clientHeight ;
      // calculate x,y clip space coordinates
      var x = (event.offsetX-w/2)/(w/2) ;
      var y = -(event.offsetY-h/2)/(h/2) ;
      mat4.inverse(pvMatrix, pvMatrixInverse) ;
      // convert clip space coordinates into world space
      mat4.multiplyVec4(pvMatrixInverse, [x,y,-1,1], world1) ;
      vec3.scale(world1,1/world1[3]) ;
      mat4.multiplyVec4(pvMatrixInverse, [x,y,0,1], world2) ;
      vec3.scale(world2,1/world2[3]) ;
      // calculate world space view vector
      vec3.subtract(world2,world1,dir) ;
      vec3.normalize(dir) ;
      vec3.scale(dir,0.3) ;
      // move eye in direction of world space view vector
      vec3.subtract(eye,dir) ;
      drawScene();
      console.log(event)
  }

    function webGLStart() {
        var canvas = document.getElementById("lesson01-canvas");
        initGL(canvas);
        initShaders();
        initBuffers();

        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.enable(gl.DEPTH_TEST);

        canvas.onmouseup = handleMouseUp;

        drawScene();
    }

webGLStart();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文