不等边的 3D 框如何填充视口,无论其透视方向如何? [英] How can a 3D box with unequal sides fill the viewport, no matter its orientation in perspective?

查看:20
本文介绍了不等边的 3D 框如何填充视口,无论其透视方向如何?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如包含的 (three.js) 实时代码段所示(也在 jsfiddle.net/gpolyn/une6tst5/21) *,我有一个不等边的盒子,观众可以重新定位通过拖动.视口中最左侧、最右侧、最顶部或最底部的框角由绿色方点动态指示.

As shown in the included (three.js) live snippet (also at jsfiddle.net/gpolyn/une6tst5/21) *, I have a box with unequal sides that a viewer may re-position by dragging. The extreme left, right, top or bottom box corners in the viewport are dynamically indicated by green square dots.

我的建模挑战如下:对于给定的视口,展示我的框,以便通过所有位置,它们之间具有最长窗口距离的点位于各自的视口边缘.

因此,对于一个对象方向,对象可能会在视口的左右边缘呈现左右虚线角,而另一种方向可能会导致在视口顶部和底部呈现顶部和底部绿色圆点底部.

So, for one object orientation, the object may be presented with left and right dotted corners at the viewport's left and right edges, while another orientation might result in a presentation of the top and bottom green dotted corners at the viewport's top and bottom.

我目前的方法使用边界球体,但这并不能实现我对每个甚至许多对象方向的目标.

My current approach uses a bounding sphere, but that doesn't accomplish my goal for every, or even many, object orientations.

我怀疑其中可能有更好的方法:

I suspect a better approach may lie somewhere among these:

  1. 根据最极端对象点的窗口坐标,修改视图或投影矩阵或两者,以表示对象
  2. 将边界球方法替换为边界框方法
  3. 获取围绕绿色圆点的虚拟"框架的窗口坐标,并将框架图像投影到窗口上(类似于 1.)

* 我的代码在很大程度上依赖于 Eric Haines 在 www.realtimerendering 上的出色演示.com/udacity/transforms.html,而绿点技术来自 西兰利

* My code depends heavily on an excellent presentation by Eric Haines at www.realtimerendering.com/udacity/transforms.html, while the green dot technique is from one of the many highly useful three.js answers posted on this forum by WestLangley

	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;

	init();
	render();
	afterInit();
	animate();

	function init() {

	  canvasWidth = window.innerWidth;
	  canvasHeight = window.innerHeight;

	  // renderer
	  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);
	  addGreenDotsToScene(geometry);

	  // camera
	  camera = new THREE.PerspectiveCamera(17, window.innerWidth / window.innerHeight, 1, 10000);
	  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;

	  // ambient
	  scene.add(new THREE.AmbientLight(0x222222));

	  // axes
	  scene.add(new THREE.AxisHelper(20));

	}

	 // determine which object points are in the most extreme top-,
	 // left-, right- and bottom-most positions in the window space
	 // and illuminate them
	function addExtrema() {

	  // object view-space points, using view (camera) matrix
	  var viewSpacePts = vertices3.map(function(vt) {
	    return vt.clone().applyMatrix4(camera.matrixWorldInverse);
	  })

	  // object clip coords, using projection matrix
	  var clipCoords = viewSpacePts.map(function(vt) {
	    return vt.applyMatrix4(camera.projectionMatrix);
	  })

	  // w-divide clip coords for NDC
	  var ndc = clipCoords.map(function(vt) {
	    return vt.divideScalar(vt.w);
	  })

	  // object window coordinates, using window matrix
	  var windowCoords = ndc.map(function(vt) {
	    return vt.applyMatrix4(windowMatrix);
	  })

	  // arbitrary selection to start
	  var topIdx = 0,
	    bottomIdx = 0,
	    leftIdx = 0,
	    rightIdx = 0;
	  var top = windowCoords[0].y;
	  var bottom = windowCoords[0].y
	  var right = windowCoords[0].x;
	  var left = windowCoords[0].x;

	  for (var i = 1; i < windowCoords.length; i++) {
	    vtx = windowCoords[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;
	    }
	  }

	  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] = 0;
	  }

	  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() {

	  var dist = boundingSphere.distanceToPoint(camera.position);

	  // from stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
	  var height = boundingSphere.radius * 2;
	  var fov = 2 * Math.atan(height / (2 * dist)) * (180 / Math.PI);

	  // not sure why, but factor is needed to maximize fit of object
	  var mysteryFactor = 0.875;
	  camera.fov = fov * mysteryFactor;
	  camera.updateProjectionMatrix();
	  camera.lookAt(boundingSphere.center);

	  renderer.render(scene, camera);

	}

	function animate() {

	  requestAnimationFrame(animate);
	  render();
	  addExtrema()

	}

			body {
			  background-color: #000;
			  margin: 0px;
			  overflow: hidden;
			}
			

<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/controls/OrbitControls.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>

推荐答案

找到了一个合理的解决方案(包含在此处的实时代码段中),主要归功于以下两个相关帖子:

Found a reasonable solution (included in live snippet, here), largely thanks to these two related posts:

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;
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;


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
  });
  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);

  // camera
  camera = new THREE.PerspectiveCamera(17, window.innerWidth / window.innerHeight, 1, 10000);
  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;

  // 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;
  newLookAt = new THREE.Vector3(2, 2, 3); // center of box

}

function addExtrema() {

  // thread A		
  mat3.multiplyMatrices(camera.matrixWorld, mat3.getInverse(camera.projectionMatrix));

  // thread B	
  var scratchVar;

  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;

  }

  // arbitrary selection to start
  var topIdx = 0,
    bottomIdx = 0,
    leftIdx = 0,
    rightIdx = 0;
  var top = ndc[0].y;
  var bottom = ndc[0].y
  var right = ndc[0].x;
  var left = ndc[0].x;
  var closestVertex, closestVertexDistance = Number.POSITIVE_INFINITY;
  var vtx;

  for (var i = 1; i < CORNERS; i++) {

    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);
  newLookAt = center;

  var upperLeft = new THREE.Vector3().fromArray(ul.toArray().slice(0, 3));

  if ((1 - yNDCPercentCoverage) < (1 - xNDCPercentCoverage)) { // height case
    var bottomLeft = new THREE.Vector3().fromArray(bl.toArray().slice(0, 3));
    var height = upperLeft.distanceTo(bottomLeft);
    newFov = 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);
    newFov = 2 * Math.atan((width / aspect) / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
  }

  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] = 0;
  }

  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() {

  camera.lookAt(newLookAt);
  camera.fov = newFov;
  camera.updateProjectionMatrix();
  renderer.render(scene, camera);

}

function animate() {

  requestAnimationFrame(animate);
  render();
  addExtrema()

}

body {
  background-color: #000;
  margin: 0px;
  overflow: hidden;
}

<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/controls/OrbitControls.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>

这篇关于不等边的 3D 框如何填充视口,无论其透视方向如何?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆