屏幕空间中投影球体的半径 [英] Radius of projected sphere in screen space

查看:41
本文介绍了屏幕空间中投影球体的半径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在投影到屏幕空间后,我试图找到球体的可见大小(以像素为单位).球体以原点为中心,相机正对着它.因此,投影球体应该是二维的完美圆.我知道这个 完成的小演示.分别使用n/f, m/ps/w 键对.屏幕空间中渲染的黄色线段显示了屏幕空间中球体半径的计算结果.此计算在函数 computeProjectedRadius() 中完成.

projected-sphere.js:

"使用严格";函数计算投影半径(fovy,d,r){变量 fov;fov = fovy/2 * Math.PI/180.0;//返回 1.0/Math.tan(fov) * r/d;//错误的返回 1.0/Math.tan(fov) * r/Math.sqrt(d * d - r * r);//对}功能演示(){this.width = 0;this.height = 0;this.scene = null;this.mesh = null;this.camera = null;this.screenLine = null;this.screenScene = null;this.screenCamera = null;this.renderer = null;this.fovy = 60.0;this.d = 10.0;this.r = 1.0;this.pr = computeProjectedRadius(this.fovy, this.d, this.r);}Demo.prototype.init = function() {var 方面;无功光;var 容器;this.width = window.innerWidth;this.height = window.innerHeight;//世界场景方面 = this.width/this.height;this.camera = new THREE.PerspectiveCamera(this.fovy, aspect, 0.1, 100.0);this.scene = new THREE.Scene();this.scene.add(THREE.AmbientLight(0x1F1F1F));光 = 新 THREE.DirectionalLight(0xFFFFFF);light.position.set(1.0, 1.0, 1.0).normalize();this.scene.add(light);//屏幕场景this.screenCamera = new THREE.OrthographicCamera(-aspect, aspect,-1.0, 1.0,0.1, 100.0);this.screenScene = new THREE.Scene();this.updateScenes();this.renderer = new THREE.WebGLRenderer({抗锯齿:真});this.renderer.setSize(this.width, this.height);this.renderer.domElement.style.position = "relative";this.renderer.autoClear = false;container = document.createElement('div');container.appendChild(this.renderer.domElement);document.body.appendChild(container);}Demo.prototype.render = function() {this.renderer.clear();this.renderer.setViewport(0, 0, this.width, this.height);this.renderer.render(this.scene, this.camera);this.renderer.render(this.screenScene, this.screenCamera);}Demo.prototype.updateScenes = function() {var 几何;this.camera.fov = this.fovy;this.camera.updateProjectionMatrix();如果(this.mesh){this.scene.remove(this.mesh);}this.mesh = 新的 THREE.Mesh(新的 THREE.SphereGeometry(this.r, 16, 16),新的 THREE.MeshLambertMaterial({颜色:0xFF0000}));this.mesh.position.z = -this.d;this.scene.add(this.mesh);this.pr = computeProjectedRadius(this.fovy, this.d, this.r);如果(this.screenLine){this.screenScene.remove(this.screenLine);}几何=新三.几何();geometry.vertices.push(new THREE.Vector3(0.0, 0.0, -1.0));geometry.vertices.push(new THREE.Vector3(0.0, -this.pr, -1.0));this.screenLine = new THREE.Line(几何学,新的 THREE.LineBasicMaterial({颜色:0xFFFF00}));this.screenScene = new THREE.Scene();this.screenScene.add(this.screenLine);}Demo.prototype.onKeyDown = 函数(事件){控制台日志(事件.keyCode)开关(事件.keyCode){case 78://'n'this.d/= 1.1;this.updateScenes();休息;case 70://'f'this.d *= 1.1;this.updateScenes();休息;case 77://'m'this.r/= 1.1;this.updateScenes();休息;case 80://'p'这个.r * = 1.1;this.updateScenes();休息;case 83://'s'this.fovy/= 1.1;this.updateScenes();休息;case 87://'w'这个.fovy *= 1.1;this.updateScenes();休息;}}Demo.prototype.onResize = 函数(事件){var 方面;this.width = window.innerWidth;this.height = window.innerHeight;this.renderer.setSize(this.width, this.height);方面 = this.width/this.height;this.camera.aspect = 方面;this.camera.updateProjectionMatrix();this.screenCamera.left = -aspect;this.screenCamera.right = 方面;this.screenCamera.updateProjectionMatrix();}函数 onLoad() {无功演示;演示 = 新演示();演示.init();函数动画循环(){演示.render();window.requestAnimationFrame(animationLoop);}函数 onResizeHandler(event) {demo.onResize(事件);}函数 onKeyDownHandler(event) {demo.onKeyDown(事件);}window.addEventListener('resize', onResizeHandler, false);window.addEventListener('keydown', onKeyDownHandler, false);window.requestAnimationFrame(animationLoop);}

index.html:

<头><title>投影球体</title><风格>身体 {背景色:#000000;}</风格><script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r61/three.min.js"></script><script src="projected-sphere.js"></script><body onLoad="onLoad()"><div id="容器"></div>

I'm trying to find the visible size of a sphere in pixels, after projection to screen space. The sphere is centered at the origin with the camera looking right at it. Thus the projected sphere should be a perfect circle in two dimensions. I am aware of this 1 existing question. However, the formula given there doesn't seem to produce the result I want. It is too small by a few percent. I assume this is because it is not correctly taking perspective into account. After projecting to screen space you do not see half the sphere but significantly less, due to perspective foreshortening (you see just a cap of the sphere instead of the full hemisphere 2).

How can I derive an exact 2D bounding circle?

解决方案

Indeed, with a perspective projection you need to compute the height of the sphere "horizon" from the eye / center of the camera (this "horizon" is determined by rays from the eye tangent to the sphere).

Notations:

d: distance between the eye and the center of the sphere
r: radius of the sphere
l: distance between the eye and a point on the sphere "horizon", l = sqrt(d^2 - r^2)
h: height / radius of the sphere "horizon"
theta: (half-)angle of the "horizon" cone from the eye
phi: complementary angle of theta

h / l = cos(phi)

but:

r / d = cos(phi)

so, in the end:

h = l * r / d = sqrt(d^2 - r^2) * r / d

Then once you have h, simply apply the standard formula (the one from the question you linked) to get the projected radius pr in the normalized viewport:

pr = cot(fovy / 2) * h / z

with z the distance from the eye to the plane of the sphere "horizon":

z = l * cos(theta) = sqrt(d^2 - r^2) * h / r

so:

pr = cot(fovy / 2) * r / sqrt(d^2 - r^2)

And finally, multiply pr by height / 2 to get the actual screen radius in pixels.

What follows is a small demo done with three.js. The sphere distance, radius and the vertical field of view of the camera can be changed by using respectively the n / f, m / p and s / w pairs of keys. A yellow line segment rendered in screen-space shows the result of the computation of the radius of the sphere in screen-space. This computation is done in the function computeProjectedRadius().

projected-sphere.js:

"use strict";

function computeProjectedRadius(fovy, d, r) {
  var fov;

  fov = fovy / 2 * Math.PI / 180.0;

//return 1.0 / Math.tan(fov) * r / d; // Wrong
  return 1.0 / Math.tan(fov) * r / Math.sqrt(d * d - r * r); // Right
}

function Demo() {
  this.width = 0;
  this.height = 0;

  this.scene = null;
  this.mesh = null;
  this.camera = null;

  this.screenLine = null;
  this.screenScene = null;
  this.screenCamera = null;

  this.renderer = null;

  this.fovy = 60.0;
  this.d = 10.0;
  this.r = 1.0;
  this.pr = computeProjectedRadius(this.fovy, this.d, this.r);
}

Demo.prototype.init = function() {
  var aspect;
  var light;
  var container;

  this.width = window.innerWidth;
  this.height = window.innerHeight;

  // World scene
  aspect = this.width / this.height;
  this.camera = new THREE.PerspectiveCamera(this.fovy, aspect, 0.1, 100.0);

  this.scene = new THREE.Scene();
  this.scene.add(THREE.AmbientLight(0x1F1F1F));

  light = new THREE.DirectionalLight(0xFFFFFF);
  light.position.set(1.0, 1.0, 1.0).normalize();
  this.scene.add(light);

  // Screen scene
  this.screenCamera = new THREE.OrthographicCamera(-aspect, aspect,
                                                   -1.0, 1.0,
                                                   0.1, 100.0);
  this.screenScene = new THREE.Scene();

  this.updateScenes();

  this.renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  this.renderer.setSize(this.width, this.height);
  this.renderer.domElement.style.position = "relative";
  this.renderer.autoClear = false;

  container = document.createElement('div');
  container.appendChild(this.renderer.domElement);
  document.body.appendChild(container);
}

Demo.prototype.render = function() {
  this.renderer.clear();
  this.renderer.setViewport(0, 0, this.width, this.height);
  this.renderer.render(this.scene, this.camera);
  this.renderer.render(this.screenScene, this.screenCamera);
}

Demo.prototype.updateScenes = function() {
  var geometry;

  this.camera.fov = this.fovy;
  this.camera.updateProjectionMatrix();

  if (this.mesh) {
    this.scene.remove(this.mesh);
  }

  this.mesh = new THREE.Mesh(
    new THREE.SphereGeometry(this.r, 16, 16),
    new THREE.MeshLambertMaterial({
      color: 0xFF0000
    })
  );
  this.mesh.position.z = -this.d;
  this.scene.add(this.mesh);

  this.pr = computeProjectedRadius(this.fovy, this.d, this.r);

  if (this.screenLine) {
    this.screenScene.remove(this.screenLine);
  }

  geometry = new THREE.Geometry();
  geometry.vertices.push(new THREE.Vector3(0.0, 0.0, -1.0));
  geometry.vertices.push(new THREE.Vector3(0.0, -this.pr, -1.0));

  this.screenLine = new THREE.Line(
    geometry,
    new THREE.LineBasicMaterial({
      color: 0xFFFF00
    })
  );

  this.screenScene = new THREE.Scene();
  this.screenScene.add(this.screenLine);
}

Demo.prototype.onKeyDown = function(event) {
  console.log(event.keyCode)
  switch (event.keyCode) {
    case 78: // 'n'
      this.d /= 1.1;
      this.updateScenes();
      break;
    case 70: // 'f'
      this.d *= 1.1;
      this.updateScenes();
      break;
    case 77: // 'm'
      this.r /= 1.1;
      this.updateScenes();
      break;
    case 80: // 'p'
      this.r *= 1.1;
      this.updateScenes();
      break;
    case 83: // 's'
      this.fovy /= 1.1;
      this.updateScenes();
      break;
    case 87: // 'w'
      this.fovy *= 1.1;
      this.updateScenes();
      break;
  }
}

Demo.prototype.onResize = function(event) {
  var aspect;

  this.width = window.innerWidth;
  this.height = window.innerHeight;

  this.renderer.setSize(this.width, this.height);

  aspect = this.width / this.height;
  this.camera.aspect = aspect;
  this.camera.updateProjectionMatrix();

  this.screenCamera.left = -aspect;
  this.screenCamera.right = aspect;
  this.screenCamera.updateProjectionMatrix();
}

function onLoad() {
  var demo;

  demo = new Demo();
  demo.init();

  function animationLoop() {
    demo.render();
    window.requestAnimationFrame(animationLoop);
  }

  function onResizeHandler(event) {
    demo.onResize(event);
  }

  function onKeyDownHandler(event) {
    demo.onKeyDown(event);
  }

  window.addEventListener('resize', onResizeHandler, false);
  window.addEventListener('keydown', onKeyDownHandler, false);
  window.requestAnimationFrame(animationLoop);
}

index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>Projected sphere</title>
      <style>
        body {
            background-color: #000000;
        }
      </style>
      <script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r61/three.min.js"></script>
      <script src="projected-sphere.js"></script>
    </head>
    <body onLoad="onLoad()">
      <div id="container"></div>
    </body>
</html>

这篇关于屏幕空间中投影球体的半径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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