WebGL: Zooming to and stopping at object in a scene in WebGL

后端 未结 1 1429
甜味超标
甜味超标 2020-12-04 04:24

We\'ve created a WebGl application which displays a scene containing multiple objects. The entire scene can be rotated in multiple directions. The application requires the

1条回答
  •  一生所求
    2020-12-04 04:35

    Off the top of my head.

    First off you need to know the size of each object in world space. For example if one object is 10 units big and another is 100 units big you probably want to be a different distance from the 100 unit object as the 10 unit object. By world space I also mean if you're scaling the 10 unit object by 9 then in world space it would be 90 units big and again you'd want to get a different distance away then if it was 10 units

    You generally compute the size of an object in local space by computing the extents of its vertices. Just go through all the vertices and keep track of the min and max values in x, y, and z. Whether you want to take the biggest value from the object's origin or compute an actual center point is up to you.

    So, given the size we can compute how far away you need to be to see the entire object. For the standard perspective matrix you can just work backward. If you know your object is 10 units big then you need to fit 10 units in your frustum. You'd probably actually pick something like 14 units (say size * 1.4) so there's some space around the object.

    enter image description here

    We know halfFovy, halfSizeToFitOnScreen, we need to compute distance

    sohcahtoa
    tangent = opposite / adjacent
    opposite = halfsizeToFitOnScreen
    adjacent = distance
    tangent  = Math.tan(halfFovY)
    

    Therefore

    tangent = sizeToFitOnScreen / distance
    tangent * distance = sizeToFitOnScreen
    distance = sizeToFitOnScreen / tangent
    distance = sizeToFitOnScreen / Math.tan(halfFovY)
    

    So now we know the camera needs to be distance away from the object. There's an entire sphere that's distance away from the object. Where you pick on that sphere is up to you. Assuming you go from where the camera currently is you can compute the direction from the object to the camera

    direction = normalize(cameraPos - objectPos)
    

    Now you can compute a point distance away in that direction.

    desiredCameraPosition = direction * distance
    

    Now either put the camera there using some lookAt function

    matrix = lookAt(desiredCameraPosition, objectPosition, up)
    

    Or lerp between where the camera currently is to it's new desired position

    var m4 = twgl.m4;
        var v3 = twgl.v3;
        twgl.setAttributePrefix("a_");
        var gl = twgl.getWebGLContext(document.getElementById("c"));
        var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
    
        var shapes = [
          twgl.primitives.createCubeBufferInfo(gl, 2),
          twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12),
          twgl.primitives.createTruncatedConeBufferInfo(gl, 1, 0, 2, 24, 1),
        ];
    
        function rand(min, max) {
          return min + Math.random() * (max - min);
        }
    
        function easeInOut(t, start, end) {
          var c = end - start;
          if ((t /= 0.5) < 1) {
            return c / 2 * t * t + start;
          } else {
            return -c / 2 * ((--t) * (t - 2) - 1) + start;
          }
        }
    
        // Shared values
        var lightWorldPosition = [1, 8, -10];
        var lightColor = [1, 1, 1, 1];
        var camera = m4.identity();
        var view = m4.identity();
        var viewProjection = m4.identity();
        var targetNdx = 0;
        var targetTimer = 0;
        var zoomTimer = 0;
        var eye = v3.copy([1, 4, -60]);
        var target = v3.copy([0, 0, 0]);
        var up = [0, 1, 0];
        var zoomScale = 1.4;
        var zoomDuration = 2;
        var targetChangeInterval = 3;
        var oldEye;
        var oldTarget;
        var newEye;
        var newTarget;
    
        var tex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
          255,255,255,255,
          192,192,192,255,
          192,192,192,255,
          255,255,255,255]));
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    
        var objects = [];
        var drawObjects = [];
        var numObjects = 100;
        var baseHue = rand(0, 360);
        for (var ii = 0; ii < numObjects; ++ii) {
          var uniforms = {
            u_lightWorldPos: lightWorldPosition,
            u_lightColor: lightColor,
            u_diffuseMult: chroma.hsv((baseHue + rand(0, 60)) % 360, 0.4, 0.8).gl(),
            u_specular: [1, 1, 1, 1],
            u_shininess: 50,
            u_specularFactor: 1,
            u_diffuse: tex,
            u_viewInverse: camera,
            u_world: m4.identity(),
            u_worldInverseTranspose: m4.identity(),
            u_worldViewProjection: m4.identity(),
          };
          drawObjects.push({
            programInfo: programInfo,
            bufferInfo: shapes[ii % shapes.length],
            uniforms: uniforms,
          });
          objects.push({
            translation: [rand(-50, 50), rand(-50, 50), rand(-50, 50)],
            scale: rand(1, 5),
            size: 2,
            xSpeed: rand(0.2, 0.7),
            zSpeed: rand(0.2, 0.7),
            uniforms: uniforms,
          });
        }
    
        var then = Date.now() * 0.001;
    
        function render() {
          twgl.resizeCanvasToDisplaySize(gl.canvas);
          gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    
          gl.enable(gl.DEPTH_TEST);
          gl.enable(gl.CULL_FACE);
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
          var time = Date.now() * 0.001;
          var elapsed = time - then;
          then = time;
    
          var radius = 6;
          var fovy = 30 * Math.PI / 180;
          var projection = m4.perspective(fovy, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 200);
    
          targetTimer -= elapsed;
          if (targetTimer <= 0) {
            targetTimer = targetChangeInterval;
            zoomTimer = 0;
            targetNdx = (targetNdx + 1) % objects.length;
            oldEye = v3.copy(eye);
            oldTarget = v3.copy(target);
            var targetObj = objects[targetNdx];
            newTarget = targetObj.translation;
            var halfSize = targetObj.size * targetObj.scale * zoomScale * 0.5;
            var distance = halfSize / Math.tan(fovy * 0.5);
            var direction = v3.normalize(v3.subtract(eye, newTarget));
            newEye = v3.add(newTarget, v3.mulScalar(direction, distance));
          }
    
          zoomTimer += elapsed;
          var lerp = easeInOut(Math.min(1, zoomTimer / zoomDuration), 0, 1);
          eye = v3.lerp(oldEye, newEye, lerp);
          target = v3.lerp(oldTarget, newTarget, lerp);
    
          m4.lookAt(eye, target, up, camera);
          m4.inverse(camera, view);
          m4.multiply(projection, view, viewProjection);
    
          objects.forEach(function(obj, ndx) {
            var uni = obj.uniforms;
            var world = uni.u_world;
            m4.identity(world);
            m4.translate(world, obj.translation, world);
            m4.rotateX(world, time * obj.xSpeed, world);
            m4.rotateZ(world, time * obj.zSpeed, world);
            m4.scale(world, [obj.scale, obj.scale, obj.scale], world);
            m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose);
            m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection);
          });
    
          twgl.drawObjectList(gl, drawObjects);
    
          requestAnimationFrame(render);
        }
        render();
    body {
      margin: 0;
    }
    canvas {
      width: 100vw;
      height: 100vh;
      display: block;
    }
    
    
    
    
      

    0 讨论(0)
提交回复
热议问题