查看眼动追踪方案所需的矩阵

时间:2017-09-05 15:43:27

标签: javascript math 3d three.js

我正在忙着开展一个小型项目,在那里我不会使用网络摄像头来跟踪用户的眼睛位置(从人眼位置相对于屏幕中心的向量)并使用这个“摄像机角度”进行操作一个threeJS场景(或者可能只是直接的WebGL),使得它看起来像是一个进入3d环境的“窗口”。太完成这一点我相信所需要的一切(假设你已经有'眼睛摄像机角度')是操纵视图矩阵。例如,给定直的摄像机角度,左侧的图像将是标准视图矩阵的典型视锥体。如果“眼睛摄像机位置”向上移动,右侧的图像将是理想的矩阵并产生视锥体。

enter image description here

这是否像交换使用的标准视图矩阵一样简单? 什么会说矩阵看起来像。

回答时请记住,我对线性代数的了解与一年级学生一样多。 另外,对于更多细节,该项目将使用WebGL和JavaScript基于浏览器。

BTW我刚刚发现,我在这里要做的就是:'头部耦合透视'

1 个答案:

答案 0 :(得分:0)

这不是一个完整的答案,但......目前我太懒惰/忙碌,但如果我理解正确,你基本上想要一个像glFrustum这样的功能。算术在那里,但格式很差。这使您可以生成平截头体矩阵,其中眼睛不在中心(就像透视函数那样)。 Three.js也支持Camera.setViewOffset

要使用Camera.setViewOffset基本上你定义一个更大的视图和画布代表那个更大视图的three.js画布的哪个子部分。在这种情况下,我们不关心更大的视图,因此我们可以将较大的视图和子视图设置为相同的大小,但仍然偏移较小的视图。

因此,有效地匹配用户的眼睛位置,你想要选择较大区域内的中心区域移动相机。

我现在懒得做的就是弄清楚每个移动多少。虽然相机在世界单位中移动并且setViewOffset以像素为单位工作,但它们似乎是相同的。 CSS像素应该是每英寸96像素,因此对于每英寸眼睛移动我们需要偏移视图96 *眼睛英寸移动的相反方向。

我们还需要知道用户眼睛离屏幕的距离屏幕的大小,以便我们可以计算屏幕的视野。 60英寸电视用户站在3英尺远的地方,11英寸笔记本电脑前面的用户会有一个不同的视野,他们的脸在1英尺远的地方

let mouse = new THREE.Vector2(), INTERSECTED;
let radius = 100, theta = 0;

const camera = new THREE.PerspectiveCamera(70, 1, 1, 10000);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set( 1, 1, 1 ).normalize();
scene.add( light );

const geometry = new THREE.BoxBufferGeometry(1, 1, 1);

for (let z = 0; z < 50; ++z) {
  for (let y = -1; y <= 1; ++y) {
    for (let x = -1; x <= 1; ++x) {
      if (x === 0 && y === 0) {
         continue;
      }
      const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff}));
  
      object.position.x =  x * 1.5;
      object.position.y =  y * 1.5;
      object.position.z = -z * 2.5;

      scene.add(object);
    }
  }
}

renderer = new THREE.WebGLRenderer({canvas: document.querySelector("canvas")});
resize(true);
document.addEventListener( 'mousemove', onDocumentMouseMove, false );

requestAnimationFrame(render);

function resize(force) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  if (force || canvas.width !== width || canvas.height !== height) {
    renderer.setSize(width, height, true);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function onDocumentMouseMove( event ) {
  event.preventDefault();
  const canvas = renderer.domElement;
  // compute mouse movement in inches
  mouse.x = (event.clientX - canvas.clientWidth  / 2) / 96;
  mouse.y = (event.clientY - canvas.clientHeight / 2) / 96;
}

function render(time) {
  const worldUnits = 1;
  const pixelUnits = worldUnits * 96;  

  const canvas = renderer.domElement;
  const fullWidth  = canvas.clientWidth;
  const fullHeight = canvas.clientHeight;
  const aspect = fullWidth / fullHeight;
  
  const eyeOffsetX = mouse.x;
  const eyeOffsetY = mouse.y;
  
  const eyeDistFromScreenInches = 12;
  const halfHeightOfScreenInches = fullHeight / 96 * .5;
  const halfFov = Math.atan2(halfHeightOfScreenInches, eyeDistFromScreenInches) * 180 / Math.PI;
  const width   = fullWidth;
  const height  = fullHeight;
  const centerX = fullWidth / 2;
  const centerY = fullHeight / 2;
  const left    = centerX - width /  2 - eyeOffsetX * pixelUnits;
  const top     = centerY - height / 2 - eyeOffsetY * pixelUnits;
  
  camera.fov = halfFov * 2;  
  camera.position.x =  eyeOffsetX * worldUnits;
  camera.position.y = -eyeOffsetY * worldUnits;
  
  camera.setViewOffset(fullWidth, fullHeight, left, top, width, height);
  camera.updateProjectionMatrix();
  
  renderer.render(scene, camera);
  
  requestAnimationFrame(render);
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
<canvas></canvas>