在下面的实时摘录中(也在https://jsfiddle.net/gpolyn/bpo7t7f6),我在three.js渲染周期中提供了PerspectiveCamera的lookAt
参数和fov
的可选动态更新。 (我的目标是通过所有轨道位置尽可能多地填充主题立方体的视口。)
我怀疑用于计算动态fov
和lookAt
参数(three.js uses Float32
arrays)的矩阵数学代码缺乏精确度会导致抖动/抖动使用从控件中选择的动态选项进行轨道运动时,在立方体中注意。
(矩阵操作可以在代码段addExtrema
函数中找到。)
在我的演示中,我的最高目标是消除案例1中的抖动/抖动,如下所述:
fov
和lookAt
更新,无论轨道位置如何,都会在立方体中产生相当多的抖动;可以看到fov
和lookAt
参数在演示的右下方状态框中波动; fov
更新导致多维数据集中出现一些抖动,具体取决于轨道运行;右下方状态框中的fov
参数会因动态重新计算而有所不同; fov
,lookAt
更新,并且立方体在轨道上没有抖动/抖动 - fov
和lookAt
参数在右下方状态框。(轨道位置在演示的左下角报告,而其中一个方框角有绿点,有助于讨论抖动效果。)
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>