如何在OrbitControl和PerspectiveCamera的特殊应用中消除抖动/抖动?

时间:2016-06-26 00:01:59

标签: javascript 3d three.js matrix-multiplication

在下面的实时摘录中(也在https://jsfiddle.net/gpolyn/bpo7t7f6),我在three.js渲染周期中提供了PerspectiveCamera的lookAt参数和fov的可选动态更新。 (我的目标是通过所有轨道位置尽可能多地填充主题立方体的视口。)

我怀疑用于计算动态fovlookAt参数(three.js uses Float32 arrays)的矩阵数学代码缺乏精确度会导致抖动/抖动使用从控件中选择的动态选项进行轨道运动时,在立方体中注意。 (矩阵操作可以在代码段addExtrema函数中找到。)

在我的演示中,我的最高目标是消除案例1中的抖动/抖动,如下所述:

  1. “dynamicFovAndLookAt”控制选项使用动态fovlookAt更新,无论轨道位置如何,都会在立方体中产生相当多的抖动;可以看到fovlookAt参数在演示的右下方状态框中波动;
  2. “dynamicFov”使用动态fov更新导致多维数据集中出现一些抖动,具体取决于轨道运行;右下方状态框中的fov参数会因动态重新计算而有所不同;
  3. “boundingSphere”选项不使用动态fovlookAt更新,并且立方体在轨道上没有抖动/抖动 - fovlookAt参数在右下方状态框。
  4. (轨道位置在演示的左下角报告,而其中一个方框角有绿点,有助于讨论抖动效果。)

    	var renderer, scene, camera, controls;
    	var object;
    	var vertices3;
    	var cloud;
    	var boxToBufferAlphaMapping = {
    	  0: 0,
    	  2: 1,
    	  1: 2,
    	  3: 4,
    	  6: 7,
    	  7: 10,
    	  5: 8,
    	  4: 6
    	}
    	var lastAlphas = [];
    	var canvasWidth, canvasHeight;
    	var windowMatrix;
    	var boundingSphere;
    	var figure;
    	var fovWidth, fovDistance, fovHeight;
    	var newFov, newLookAt;
    	var dist, height, fov, lookAt;
    	var aspect;
    	var CONSTANT_FOR_FOV_CALC = 180 / Math.PI;
    	var mat3;
    	var CORNERS = 8;
    	var ndc = new Array(CORNERS);
    	var USE_GREEN_DOTS = false;
    	var stats, orbitPosition, cameraFacts;
    	var useDynamicFov;
    	var datGuiData = {};
    
    
    	init();
    	render();
    	afterInit();
    	animate();
    
    	function init() {
    
    	  mat3 = new THREE.Matrix4();
    
    	  canvasWidth = window.innerWidth;
    	  canvasHeight = window.innerHeight;
    	  aspect = canvasWidth / canvasHeight;
    	  // renderer
    	  <!-- renderer = new THREE.WebGLRenderer({antialias:true, logarithmicDepthBuffer:true}); -->
    	  renderer = new THREE.WebGLRenderer({
    	    antialias: true
    	  });
    	  renderer.setSize(canvasWidth, canvasHeight);
    	  document.body.appendChild(renderer.domElement);
    
    	  // scene
    	  scene = new THREE.Scene();
    
    	  // object
    	  var geometry = new THREE.BoxGeometry(4, 4, 6);
    
    	  // too lazy to add edges without EdgesHelper...
    	  var material = new THREE.MeshBasicMaterial({
    	    transparent: true,
    	    opacity: 0
    	  });
    	  var cube = new THREE.Mesh(geometry, material);
    	  object = cube;
    
    	  // bounding sphere used for orbiting control in render
    	  object.geometry.computeBoundingSphere();
    	  boundingSphere = object.geometry.boundingSphere;
    
    	  cube.position.set(2, 2, 3)
    	    // awkward, but couldn't transfer cube position to sphere...
    	  boundingSphere.translate(new THREE.Vector3(2, 2, 3));
    
    	  // save vertices for subsequent use
    	  vertices = cube.geometry.vertices;
    
    	  var edges = new THREE.EdgesHelper(cube)
    	  scene.add(edges);
    	  scene.add(cube);
    
    	  <!-- if (USE_GREEN_DOTS) addGreenDotsToScene(geometry); -->
    	  addGreenDotsToScene(geometry);
    
    	  // camera
    	  <!-- camera = new THREE.PerspectiveCamera( 17, window.innerWidth / window.innerHeight, 20, 40 ); -->
    	  camera = new THREE.PerspectiveCamera(17, window.innerWidth / window.innerHeight);
    	  camera.position.set(20, 20, 20);
    
    	  // controls
    	  controls = new THREE.OrbitControls(camera);
    	  controls.maxPolarAngle = 0.5 * Math.PI;
    	  controls.minAzimuthAngle = 0;
    	  controls.maxAzimuthAngle = 0.5 * Math.PI;
    	  controls.enableZoom = false;
    
    	  // performance monitor
    	  stats = new Stats();
    	  document.body.appendChild(stats.dom);
    
    	  // orbitposition tracker
    	  orbitPosition = new THREEg.OrbitReporter()
    	  orbitPosition.domElement.style.position = 'absolute'
    	  orbitPosition.domElement.style.left = '0px'
    	  orbitPosition.domElement.style.bottom = '0px'
    	  document.body.appendChild(orbitPosition.domElement)
    
    	  // camera facts
    	  cameraFacts = new THREEg.CameraReporter()
    	  cameraFacts.domElement.style.position = 'absolute'
    	  cameraFacts.domElement.style.right = '0px'
    	  cameraFacts.domElement.style.bottom = '0px'
    	  document.body.appendChild(cameraFacts.domElement)
    
    	  // ambient
    	  scene.add(new THREE.AmbientLight(0x222222));
    
    	  // axes
    	  scene.add(new THREE.AxisHelper(20));
    
    	  // initial settings
    	  dist = boundingSphere.distanceToPoint(camera.position);
    	  height = boundingSphere.radius * 2;
    	  fov = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
    	  newFov = fov;
    	  lookAt = new THREE.Vector3(2, 2, 3); // center of box
    	  newLookAt = lookAt;
    
    	  // dat.gui
    	  window.onload = function() {
    	    var view = datGuiData;
    	    view.boundingSphere = true;
    	    view.dynamicFov = false;
    	    view.dynamicFovAndLookAt = false;
    
    	    var gui = new dat.GUI();
    
    	    var CB1Controller = gui.add(view, 'boundingSphere').listen();
    	    CB1Controller.onChange(function(value) {
    	      view.boundingSphere = true;
    	      view.dynamicFov = false;
    	      view.dynamicFovAndLookAt = false;
    	    });
    
    	    var CB2Controller = gui.add(view, 'dynamicFov').listen();
    	    CB2Controller.onChange(function(value) {
    	      view.boundingSphere = false;
    	      view.dynamicFov = true;
    	      view.dynamicFovAndLookAt = false;
    	    });
    
    	    var CB3Controller = gui.add(view, 'dynamicFovAndLookAt').listen();
    	    CB3Controller.onChange(function(value) {
    	      view.boundingSphere = false;
    	      view.dynamicFov = true;
    	      view.dynamicFovAndLookAt = true;
    	    });
    	  };
    
    	}
    
    	function addExtrema() {
    
    	  // thread A		
    	  mat3.multiplyMatrices(camera.matrixWorld, mat3.getInverse(camera.projectionMatrix));
    
    	  // thread B	
    	  var scratchVar;
    
    	  var topIdx, bottomIdx, leftIdx, rightIdx;
    	  var top = Number.NEGATIVE_INFINITY;
    	  var bottom = Number.POSITIVE_INFINITY;
    	  var right = Number.NEGATIVE_INFINITY;
    	  var left = Number.POSITIVE_INFINITY;
    	  var closestVertex, closestVertexDistance = Number.POSITIVE_INFINITY;
    	  var vtx;
    
    	  for (var i = 0; i < CORNERS; i++) {
    
    	    scratchVar = vertices3[i].clone().applyMatrix4(camera.matrixWorldInverse);
    	    scratchVar.applyMatrix4(camera.projectionMatrix);
    
    	    scratchVar.divideScalar(scratchVar.w)
    	    ndc[i] = scratchVar;
    
    	    vtx = ndc[i];
    
    	    if (vtx.x < left) {
    	      left = vtx.x;
    	      leftIdx = i;
    	    } else if (vtx.x > right) {
    	      right = vtx.x;
    	      rightIdx = i;
    	    }
    
    	    if (vtx.y < bottom) {
    	      bottom = vtx.y;
    	      bottomIdx = i;
    	    } else if (vtx.y > top) {
    	      top = vtx.y;
    	      topIdx = i;
    	    }
    
    	    if (vtx.z < closestVertexDistance) {
    	      closestVertex = i;
    	      closestVertexDistance = vtx.z;
    	    }
    
    	  }
    
    	  var yNDCPercentCoverage = (Math.abs(ndc[topIdx].y) + Math.abs(ndc[bottomIdx].y)) / 2;
    	  yNDCPercentCoverage = Math.min(1, yNDCPercentCoverage);
    
    	  var xNDCPercentCoverage = (Math.abs(ndc[leftIdx].x) + Math.abs(ndc[rightIdx].x)) / 2;
    	  xNDCPercentCoverage = Math.min(1, xNDCPercentCoverage);
    
    	  var ulCoords = [ndc[leftIdx].x, ndc[topIdx].y, closestVertexDistance, 1]
    	  var blCoords = [ndc[leftIdx].x, ndc[bottomIdx].y, closestVertexDistance, 1]
    	  var urCoords = [ndc[rightIdx].x, ndc[topIdx].y, closestVertexDistance, 1]
    
    	  var ul = new THREE.Vector4().fromArray(ulCoords);
    	  ul.applyMatrix4(mat3).divideScalar(ul.w);
    
    	  var bl = new THREE.Vector4().fromArray(blCoords);
    	  bl.applyMatrix4(mat3).divideScalar(bl.w);
    
    	  var ur = new THREE.Vector4().fromArray(urCoords);
    	  ur.applyMatrix4(mat3).divideScalar(ur.w);
    
    	  var center = new THREE.Vector3();
    	  center.addVectors(ur, bl);
    	  center.divideScalar(2);
    
    	  var dist = camera.position.distanceTo(center);
    
    
    	  var upperLeft = new THREE.Vector3().fromArray(ul.toArray().slice(0, 3));
    	  var p;
    
    	  if ((1 - yNDCPercentCoverage) < (1 - xNDCPercentCoverage)) { // height case
    	    var bottomLeft = new THREE.Vector3().fromArray(bl.toArray().slice(0, 3));
    	    var height = upperLeft.distanceTo(bottomLeft);
    	    p = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
    	  } else { // width case
    	    var upperRight = new THREE.Vector3().fromArray(ur.toArray().slice(0, 3));
    	    var width = upperRight.distanceTo(upperLeft);
    	    p = 2 * Math.atan((width / aspect) / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
    	  }
    
    	  if (datGuiData.dynamicFovAndLookAt || datGuiData.dynamicFov) {
    	    newFov = p;
    	  } else {
    	    dist = boundingSphere.distanceToPoint(camera.position);
    	    height = boundingSphere.radius * 2;
    	    newFov = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
    	  }
    
    	  if (datGuiData.dynamicFovAndLookAt == true) {
    	    newLookAt = center;
    	  } else {
    	    newLookAt = lookAt;
    	  }
    
    	  if (USE_GREEN_DOTS) {
    	    var alphas = cloud.geometry.attributes.alpha;
    
    	    // make last points invisible
    	    lastAlphas.forEach(function(alphaIndex) {
    	      alphas.array[alphaIndex] = 0.0;
    	    });
    	    // now, make new points visible...
    	    // (boxToBufferAlphaMapping is a BufferGeometry-Object3D geometry
    	    // map between the object and green dots)
    	    alphas.array[boxToBufferAlphaMapping[rightIdx]] = 1.0;
    	    alphas.array[boxToBufferAlphaMapping[bottomIdx]] = 1.0;
    	    alphas.array[boxToBufferAlphaMapping[topIdx]] = 1.0;
    	    alphas.array[boxToBufferAlphaMapping[leftIdx]] = 1.0;
    
    	    // store visible points for next cycle
    	    lastAlphas = [boxToBufferAlphaMapping[rightIdx]];
    	    lastAlphas.push(boxToBufferAlphaMapping[bottomIdx])
    	    lastAlphas.push(boxToBufferAlphaMapping[topIdx])
    	    lastAlphas.push(boxToBufferAlphaMapping[leftIdx])
    
    	    alphas.needsUpdate = true;
    	  }
    
    	}
    
    	function addGreenDotsToScene(geometry) {
    
    	  var bg = new THREE.BufferGeometry();
    	  bg.fromGeometry(geometry);
    	  bg.translate(2, 2, 3); // yucky, and quick
    
    	  var numVertices = bg.attributes.position.count;
    	  var alphas = new Float32Array(numVertices * 1); // 1 values per vertex
    
    	  <!-- for( var i = 0; i < numVertices; i ++ ) { -->
    	  <!--     alphas[ i ] = 1; -->
    	  <!-- } -->
    
    	  alphas[2] = 1;
    
    	  bg.addAttribute('alpha', new THREE.BufferAttribute(alphas, 1));
    
    	  var uniforms = {
    	    color: {
    	      type: "c",
    	      value: new THREE.Color(0x00ff00)
    	    },
    	  };
    
    	  var shaderMaterial = new THREE.ShaderMaterial({
    	    uniforms: uniforms,
    	    vertexShader: document.getElementById('vertexshader').textContent,
    	    fragmentShader: document.getElementById('fragmentshader').textContent,
    	    transparent: true
    	  });
    
    	  cloud = new THREE.Points(bg, shaderMaterial);
    	  scene.add(cloud);
    
    	}
    
    	function afterInit() {
    
    	  windowMatrix = new THREE.Matrix4();
    	  windowMatrix.set(canvasWidth / 2, 0, 0, canvasWidth / 2, 0, canvasHeight / 2, 0, canvasHeight / 2, 0, 0, 0.5, 0.5, 0, 0, 0, 1);
    
    	  var vertices2 = object.geometry.vertices.map(function(vtx) {
    	    return (new THREE.Vector4(vtx.x, vtx.y, vtx.z));
    	  });
    
    	  // create 'world-space' geometry points, using
    	  // model ('world') matrix
    	  vertices3 = vertices2.map(function(vt) {
    	    return vt.applyMatrix4(object.matrixWorld);
    	  })
    
    	}
    
    	function render() {
    
    	  <!-- console.log({far: camera.far, camera_near: camera.near}) -->
    	  camera.lookAt(newLookAt);
    	  camera.fov = newFov;
    	  camera.updateProjectionMatrix();
    	  renderer.render(scene, camera);
    
    	}
    
    	function animate() {
    
    	  requestAnimationFrame(animate);
    	  render();
    	  addExtrema()
    	  stats.update();
    	  orbitPosition.update(controls);
    	  cameraFacts.update(camera, newLookAt);
    
    	}
    			body {
    			  background-color: #000;
    			  margin: 0px;
    			  overflow: hidden;
    			}
    			.dg .c {
    			  width: 40%
    			}
    			.dg .property-name {
    			  width: 60%
    			}
    			
    <script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script>
    <script src="https://rawgit.com/mrdoob/three.js/master/examples/js/libs/stats.min.js"></script>
    <script src="https://rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
    <script src="https://rawgit.com/gpolyn/789d63a662c1768320756f68a6099f15/raw/3a0f323bb284b09e624a11f93ff4055e23adea80/OrbitReporter.js"></script>
    <script src="https://rawgit.com/gpolyn/70352cb34c7900ed2489400d4ecc45f7/raw/7b6e7e6bb3e175d4145879ef1afdeb38c31cf785/camera_reporter.js"></script>
    <script src="https://rawgit.com/mrdoob/three.js/master/examples/js/libs/dat.gui.min.js"></script>
    <script type="x-shader/x-vertex" id="vertexshader">
    
      attribute float alpha; varying float vAlpha; void main() { vAlpha = alpha; vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); gl_PointSize = 8.0; gl_Position = projectionMatrix * mvPosition; }
    
    </script>
    
    <script type="x-shader/x-fragment" id="fragmentshader">
    
      uniform vec3 color; varying float vAlpha; void main() { gl_FragColor = vec4( color, vAlpha ); }
    
    </script>

0 个答案:

没有答案