javascript - 如何计算在给定坐标处相机可见的矩形的大小?

标签 javascript three.js perspectivecamera frustum

这个问题在这里已经有了答案:





Three.js - Width of view

(2 个回答)


4年前关闭。




我制作了一个小的 three.js 应用程序,它将一堆圆圈从 Canvas 底部移动到顶部:

let renderer, scene, light, circles, camera;

initialize();
animate();

function initialize() {
  renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  scene = new THREE.Scene();

  light = new THREE.AmbientLight();
  scene.add(light);

  circles = new THREE.Group();
  scene.add(circles);

  camera = new THREE.PerspectiveCamera(45, renderer.domElement.clientWidth / renderer.domElement.clientHeight, 1);
  camera.position.z = circles.position.z + 500;
}


function animate() {
  // Update each circle.
  Array.from(circles.children).forEach(circle => {
    if (circle.position.y < visibleBox(circle.position.z).max.y) {
      circle.position.y += 4;
    } else {
      circles.remove(circle);
    }
  });

  // Create a new circle.
  let circle = new THREE.Mesh();
  circle.geometry = new THREE.CircleGeometry(30, 30);
  circle.material = new THREE.MeshToonMaterial({ color: randomColor(), transparent: true, opacity: 0.5 });
  circle.position.z = _.random(camera.position.z - camera.far, camera.position.z - (camera.far / 10));
  circle.position.x = _.random(visibleBox(circle.position.z).min.x, visibleBox(circle.position.z).max.x);
  circle.position.y = visibleBox(circle.position.z).min.y;
  circles.add(circle);

  // Render the scene.
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

function visibleBox(z) {
 return new THREE.Box2(
   new THREE.Vector2(-1000, -1000),
   new THREE.Vector2(1000, 1000)
 );
}

function randomColor() {
  return `#${ _.sampleSize("abcdef0123456789", 6).join("")}`;
}
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.js">
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js">
</script>


我使用函数 visibleBox(z) 来确定在哪里创建和销毁每个圆。我已经为这个函数硬编码了一个返回值,但我希望它计算在给定深度 z 对相机可见的矩形的大小。

frustum

换句话说,我希望每个圆都精确地创建在相机截锥体的底部(上图中红色矩形的底部边缘),并在它到达截锥体的顶部时准确地销毁(顶部边缘的顶部边缘)红色矩形)。

那么,我如何计算这个矩形呢?

最佳答案

像这样改变函数:

function visibleBox(z) {
    var t = Math.tan( THREE.Math.degToRad( camera.fov ) / 2 )
    var h = t * 2 * z;
    var w = h * camera.aspect;
    return new THREE.Box2(new THREE.Vector2(-w, h), new THREE.Vector2(w, -h));
}

并像这样设置圆圈位置:
circle.position.z = _.random(-camera.near, -camera.far);
var visBox = visibleBox(circle.position.z)
circle.position.x = _.random(visBox.min.x, visBox.max.x);
circle.position.y = visBox.min.y;

代码演示:

let renderer, scene, light, circles, camera;

initialize();
animate();

function initialize() {
  renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  scene = new THREE.Scene();

  light = new THREE.AmbientLight();
  scene.add(light);

  circles = new THREE.Group();
  scene.add(circles);

  camera = new THREE.PerspectiveCamera(45, renderer.domElement.clientWidth / renderer.domElement.clientHeight, 1);
  camera.position.z = circles.position.z + 500;
}


function animate() {
  // Update each circle.
  Array.from(circles.children).forEach(circle => {
    if (circle.position.y < visibleBox(circle.position.z).max.y) {
      circle.position.y += 4;
    } else {
      circles.remove(circle);
    }
  });

  // Create a new circle.
  let circle = new THREE.Mesh();
  circle.geometry = new THREE.CircleGeometry(30, 30);
  circle.material = new THREE.MeshToonMaterial({ color: randomColor(), transparent: true, opacity: 0.5 });
  circle.position.z = _.random(-(camera.near+(camera.far-camera.near)/5), -camera.far);
  var visBox = visibleBox(circle.position.z)
  circle.position.x = _.random(visBox.min.x, visBox.max.x);
  circle.position.y = visBox.min.y;
  circles.add(circle);

  // Render the scene.
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

function visibleBox(z) {
    var t = Math.tan( THREE.Math.degToRad( camera.fov ) / 2 )
    var h = t * 2 * z;
    var w = h * camera.aspect;
    return new THREE.Box2(new THREE.Vector2(-w, h), new THREE.Vector2(w, -h));
}

function randomColor() {
  return `#${ _.sampleSize("abcdef0123456789", 6).join("")}`;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.js">
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js">
</script>



解释

投影矩阵描述了从场景的 3D 点到视口(viewport)的 2D 点的映射。它从眼睛空间变换到剪辑空间,剪辑空间中的坐标通过除以剪辑坐标的 w 分量转换为归一化设备坐标(NDC)。 NDC 在 (-1,-1,-1) 到 (1,1,1) 范围内。

enter image description here

在透视投影中,深度值和到相机的 z 距离之间的关系不是线性的。
透视投影矩阵如下所示:
r = right, l = left, b = bottom, t = top, n = near, f = far

2*n/(r-l)      0              0               0
0              2*n/(t-b)      0               0
(r+l)/(r-l)    (t+b)/(t-b)    -(f+n)/(f-n)    -1    
0              0              -2*f*n/(f-n)    0

由此遵循 View 空间中的 z 坐标与归一化设备坐标 z 分量和深度之间的关系:
z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye
depth = (z_ndc + 1.0) / 2.0

反向操作如下所示:
n = near, f = far

z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));

如果透视投影矩阵已知,则可以按如下方式完成:
A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)

How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?

视空间中的投影面积与视空间Z坐标之间的关系是线性的。它取决于视场 Angular 和纵横比。

enter image description here

标准化的设备大小可以转换为 View 空间中的大小,如下所示:
aspect = w / h
tanFov = tan( fov_y * 0.5 );

size_x = ndx_size_x * (tanFov * aspect) * z_eye;
size_y = ndx_size_y * tanFov * z_eye;

如果透视投影矩阵已知并且投影是对称的(视线在视口(viewport)的中心并且视野没有移位),则可以这样做:
size_x = ndx_size_x * / (prj_mat[0][0] * z_eye);
size_y = ndx_size_y * / (prj_mat[1][1] * z_eye);

Field of view + Aspect Ratio + View Matrix from Projection Matrix (HMD OST Calibration)

请注意,标准化设备坐标中的每个位置都可以通过逆投影矩阵转换为 View 空间坐标:
mat4 inversePrjMat = inverse( prjMat );
vec4 viewPosH      = inversePrjMat * vec3( ndc_x, ndc_y, 2.0 * depth - 1.0, 1.0 );
vec3 viewPos       = viewPos.xyz / viewPos.w;

How to recover view space position given view space depth value and ndc xy

这意味着具有特定深度的未投影矩形可以这样计算:
vec4 viewLowerLeftH  = inversePrjMat * vec3( -1.0, -1.0, 2.0 * depth - 1.0, 1.0 );
vec4 viewUpperRightH = inversePrjMat * vec3(  1.0,  1.0, 2.0 * depth - 1.0, 1.0 );
vec3 viewLowerLeft   = viewLowerLeftH.xyz / viewLowerLeftH.w;
vec3 viewUpperRight  = viewUpperRightH.xyz / viewUpperRightH.w;

关于javascript - 如何计算在给定坐标处相机可见的矩形的大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46578529/

相关文章:

javascript - 从 window.location.href 上升一级

javascript - 点击任何 li 项目后 ul li 下拉菜单不会显示 li 项目

javascript - PHP Cookie 未设置/工作

3d - 在 Three.Js 中使物体完全适合相机视锥

javascript - 获取 Jquery 数据表中的总行数

javascript - 在不泄漏内存的情况下动态创建和销毁 Three.js 场景

javascript - 如何使用 JSONLoader 更改 Three.js 加载对象的颜色

javascript - 在 THREE.js 中为螺旋星系生成粒子

c++ - 使用OpenGL进行多边形的透视投影

javascript - 带有两个(切换)相机的 Three.js 轨道控制