如何计算给定坐标下相机可见的矩形大小? [英] How to compute the size of the rectangle that is visible to the camera at a given coordinate?

查看:95
本文介绍了如何计算给定坐标下相机可见的矩形大小?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



 让渲染器,场景,灯光,圆圈,相机; initialize(); animate();函数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(光); circles = new THREE.Group(); scene.add(圆圈); camera = new THREE.PerspectiveCamera(45,renderer.domElement.clientWidth / renderer.domElement.clientHeight,1); camera.position.z = circles.position.z + 500;} function animate(){//更新每个圆圈。 Array.from(circles.children).forEach(circle => {if(circle.position.y< visibleBox(circle.position.z).max.y){circle.position.y + = 4;} else {circles.remove(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(圆圈); //渲染场景renderer.render(场景,相机); 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%;身高: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





换句话说,我希望每个圆圈都精确地创建在相机平截头体的底部(上图中红色矩形的下边缘),并在到达顶部时完全被破坏截锥体(红色矩形的上边缘)。



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

解决方案

改变这样的函数:

  function visibleBox(z){
var t = Math.tan(THREE.Math.degToRad(camera.fov)/ 2)
var h = t * 2 * z;
var w = h * camera.aspect;
返回新的THREE.Box2(新的THREE.Vector2(-w,h),新的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;

代码演示:



 让渲染器,场景,灯光,圆圈,相机; initialize(); animate();函数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(光); circles = new THREE.Group(); scene.add(圆圈); camera = new THREE.PerspectiveCamera(45,renderer.domElement.clientWidth / renderer.domElement.clientHeight,1); camera.position.z = circles.position.z + 500;} function animate(){//更新每个圆圈。 Array.from(circles.children).forEach(circle => {if(circle.position.y< visibleBox(circle.position.z).max.y){circle.position.y + = 4;} else {circles.remove(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(圆圈); //渲染场景renderer.render(场景,相机); 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;返回新的THREE.Box2(新的THREE.Vector2(-w,h),新的THREE.Vector2(w,-h));}函数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点到视口的2D点的映射。它从眼睛空间转换到剪辑空间,并通过除以剪辑坐标的 w 组件将剪辑空间中的坐标转换为规范化设备坐标(NDC) 。 NDC在(-1,-1,-1)到(1,1,1)的范围内。







视图空间中投影区域与视图空间Z坐标之间的实际关系是线性的。它取决于视角和纵横比。





规范化的设备大小可以转换为视图空间中的大小,如下所示:

  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;

如果透视投影矩阵已知并且投影是对称的(视线位于中心)视口和视野没有被取代),这可以按如下方式完成:

  size_x = ndx_size_x * /( prj_mat [0] [0] * z_eye); 
size_y = ndx_size_y * /(prj_mat [1] [1] * z_eye);

参见视场+纵横比+从投影矩阵查看矩阵(HMD OST校准)





注意,归一化设备坐标中的每个位置都可以通过逆投影矩阵转换为视图空间坐标:

  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;

参见如何在给定视图空间深度值和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;


I made a small three.js app that moves a bunch of circles from the bottom of the canvas to the top:

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>

I use the function visibleBox(z) to determine where to create and destroy each circle. I've hard-coded a return value for this function, but instead I would like it to compute the size of the rectangle that is visible to the camera at a given depth, z.

In other words, I want each circle to be created exactly at the bottom of the camera frustum (the bottom edge of the red rectangle in the image above), and destroyed exactly when it reaches the top of the frustum (the top edge of the red rectangle).

So, how I do compute this rectangle?

解决方案

Change the function like this:

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

And set up the circle position like this:

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;

Code demonstration:

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>


Explanation

The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. It transforms from eye space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) by dividing with the w component of the clip coordinates. The NDC are in range (-1,-1,-1) to (1,1,1).


In the perspective projection the relation between the depth value and the z distance to the camera is not linear.
A perspective projection matrix looks like this:

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

From this follows the relation between the z coordinate in view space and the normalized device coordinates z component and the depth.:

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

The reverse operation looks like this:

n = near, f = far

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

If the perspective projection matrix is known this can be done as follows:

A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)

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


The realtion between the projected area in view space and the Z coordinate of the view space is linear. It dpends on the field of view angle and the aspect ratio.

The normaized dievice size can be transformed to a size in view space like this:

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;

if the perspective projection matrix is known and the projection is symmetrically (the line of sight is in the center of the viewport and the field of view is not displaced), this can be done as follows:

size_x = ndx_size_x * / (prj_mat[0][0] * z_eye);
size_y = ndx_size_y * / (prj_mat[1][1] * z_eye);

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


Note each position in normalized device coordinates can be transformed to view space coordinates by the inverse projection matrix:

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;

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


This means the unprojected rectangle with a specific depth, can be calculated like this:

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;

这篇关于如何计算给定坐标下相机可见的矩形大小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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