在Three.js中将Z位置从透视图移至正交摄影机 [英] Transpose z-position from perspective to orthographic camera in three.js

查看:122
本文介绍了在Three.js中将Z位置从透视图移至正交摄影机的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个场景,我想将透视对象(即,距离较远时看起来较小的对象)与正射影像对象(即,与距离无关而大小相同的对象)组合在一起。透视图对象是渲染的世界的一部分,而正交图对象是装饰物,如标签或图标。与HUD不同,我希望将正投影对象渲染在世界内部,这意味着它们可以被世界对象覆盖(想象飞机在标签前经过)。



我的解决方案是使用一个渲染器,但使用两个场景,一个场景使用 PerspectiveCamera ,另一个场景使用 OrthogographicCamera 。我按顺序渲染它们,而没有清除z缓冲区(渲染器的 autoClear 属性设置为 false )。我面临的问题是我需要同步每个场景中对象的放置,以便为一个场景中的对象分配一个z位置,该位置位于另一个场景中的 中的对象后面



为此,我将透视图场景指定为领先场景,即。根据该场景分配所有对象(透视图和正交图)的所有坐标。透视图对象直接使用这些坐标,并在该场景内以及使用透视图相机进行渲染。将正射影像对象的坐标转换为正射影像场景中的坐标,然后使用正射影像相机在该场景中进行渲染。我通过将透视场景中的坐标投影到透视相机的视图窗格,然后使用正交摄影机返回正交场景来进行转换:

  position.project(perspectiveCamera).unproject(orthogographicCamera); 

A,这并非按预期运行。正交对象始终在透视对象之前进行渲染,即使它们应位于透视对象之间。考虑以下示例,在该示例中,蓝色圆圈应该显示在红色正方形的下面,而不是红色正方形,但是应该显示在绿色正方形的前面(不是):

  var pScene = new THREE.Scene(); 
var oScene =新的THREE.Scene();

var pCam =新的THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,1,1000);
pCam.position.set(0,40,50);
pCam.lookAt(new THREE.Vector3(0,0,-50));

var oCam = new THREE.OrthographicCamera(window.innerWidth / -2,window.innerWidth / 2,window.innerHeight / 2,window.innerHeight / -2,1,500);
oCam.Position = pCam.position.clone();

pScene.add(pCam);
pScene.add(新的三个.AmbientLight(0xFFFFFF));

oScene.add(oCam);
oScene.add(新的三个.AmbientLight(0xFFFFFF));

var frontPlane =新的THREE.Mesh(新的THREE.PlaneGeometry(20,20),新的THREE.MeshBasicMaterial({color:0x990000}));
frontPlane.position.z = -50;
pScene.add(frontPlane);

var backPlane =新的THREE.Mesh(新的THREE.PlaneGeometry(20,20),新的THREE.MeshBasicMaterial({color:0x009900}));
backPlane.position.z = -100;
pScene.add(backPlane);

var circle =新的THREE.Mesh(新的THREE.CircleGeometry(60,20),新的THREE.MeshBasicMaterial({color:0x000099})));
circle.position.z = -75;

//将位置从透视相机转换为正交相机->无效,该圆圈显示在
前面的circle.position.project(pCam).unproject(oCam);

oScene.add(圆圈);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);

renderer.autoClear = false;
renderer.render(oScene,oCam);
renderer.render(pScene,pCam);

您可以



正交投影矩阵:

  r =右,l =左,b =底部,t =顶部,n =附近,f =远

2 /(rl)0 0 0
0 2 /(tb)0 0
0 0 -2 /(fn)0
-(r + l)/(rl)-(t + b)/(tb)-(f + n)/(fn)1

在正交投影中,Z分量由线性函数计算:

  z_ndc = z_eye * -2 /(fn)-(f + n)/(fn)






透视投影



在透视投影中,投影矩阵描述了从3D点在针孔相机中看到的映射,到视口的2D点。
摄像机视锥中的眼睛空间坐标(截断的金字塔)映射到立方体(规范化的设备坐标)。



透视投影




请参见堆栈溢出问题答案的详细说明如何在片段着色器中使用gl_FragCoord.z在现代OpenGL中线性渲染深度?





在您的情况下,这意味着您必须以这种方式选择正投影中圆的Z坐标,即 depth 值介于之间透视投影中对象的深度

由于这两种情况下的深度值都不是 depth = z ndc * 0.5 + 0.5 ,因此也可以通过归一化来进行计算设备坐标而不是深度值。



归一化的设备坐标可以通过 project 函数< a href = https://threejs.org/docs/#api/cameras/PerspectiveCamera rel = nofollow noreferrer> THREE.PerspectiveCamera 项目从世界空间转换为视图空间,从视图空间转换为规范化的设备坐标。



查找Z正射投影之间的中间坐标Z坐标必须转换为视图空间Z坐标。这可以通过unproject 函数完成。 > THREE.PerspectiveCamera unproject 从规范化的设备坐标转换为视图空间,从视图空间转换为世界空间。



请参见 OpenGL-鼠标坐标为空间坐标



参见示例:



  var渲染器,pScene,oScene,pCam,oCam,frontPlane,backPlane,圆; var init = function(){pScene = new THREE.Scene(); oScene =新的THREE.Scene(); pCam =新的THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,1,1000); pCam.position.set(0,40,50); pCam.lookAt(new THREE.Vector3(0,0,-50)); oCam =新的THREE.OrthographicCamera(window.innerWidth / -2,window.innerWidth / 2,window.innerHeight / 2,window.innerHeight / -2,1,500); oCam.Position = pCam.position.clone(); pScene.add(pCam); pScene.add(new THREE.AmbientLight(0xFFFFFF)); oScene.add(oCam); oScene.add(new THREE.AmbientLight(0xFFFFFF)); frontPlane =新的THREE.Mesh(新的THREE.PlaneGeometry(20,20),新的THREE.MeshBasicMaterial({color:0x990000})); frontPlane.position.z = -50; pScene.add(frontPlane); backPlane =新的THREE.Mesh(新的THREE.PlaneGeometry(20,20),新的THREE.MeshBasicMaterial({color:0x009900}))); backPlane.position.z = -100; pScene.add(backPlane); circle = new THREE.Mesh(new THREE.CircleGeometry(20,20),new THREE.MeshBasicMaterial({color:0x000099})); circle.position.z = -75; //将位置从透视相机转换为正交相机->无效,圆圈显示在前面的pCam.updateMatrixWorld(false)中; oCam.updateMatrixWorld(false); circle.position.project(pCam).unproject(oCam); oScene.add(circle); renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth,window.innerHeight); document.body.appendChild(renderer.domElement); }; var render = function(){renderer.autoClear = false; renderer.render(oScene,oCam); renderer.render(pScene,pCam); }; var animate = function(){requestAnimationFrame(animate); //controls.update(); render(); };在里面(); animate();  

  html,body {高度:100% ;宽度:100%;边距:0;溢出:隐藏;}  

 < script src = https ://threejs.org/build/three.min.js>< / script>  


I have a scene where I want to combine perspective objects (ie. objects that appear smaller when they are far away) with orthogographic objects (ie. objects that appear the same size irrespective of distance). The perspective objects are part of the rendered "world", while the orthogographic objects are adornments, like labels or icons. Unlike a HUD, I want the orthogographic objects to be rendered "within" the world, which means that they can be covered by world objects (imagine a plane passing before a label).

My solution is to use one renderer, but two scenes, one with a PerspectiveCamera and one with an OrthogographicCamera. I render them in sequence without clearing the z buffer (the renderer's autoClear property is set to false). The problem that I am facing is that I need to synchronize the placement of the objects in each scene so that an object in one scene is assigned a z-position that is behind objects in the other scene that are before it, but before objects that are behind it.

To do that, I am designating my perspective scene as the "leading" scene, ie. all coordinates of all objects (perspective and orthogographic) are assigned based on this scene. The perspective objects use these coordinates directly and are rendered within that scene and with the perspective camera. The coordinates of the orthogographic objects are transformed to the coordinates in the orthogographic scene and then rendered in that scene with the orthogographic camera. I do the transformation by projecting the coordinates in the perspective scene to the perspective camera's view pane and then back to the orthogonal scene with the orthogographic camera:

position.project(perspectiveCamera).unproject(orthogographicCamera);

Alas, this is not working as indended. The orthogographic objects are always rendered before the perspective objects even if they should be between them. Consider this example, in which the blue circle should be displayed behind the red square, but before the green square (which it isn't):

var pScene = new THREE.Scene();
var oScene = new THREE.Scene();

var pCam = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
pCam.position.set(0, 40, 50);
pCam.lookAt(new THREE.Vector3(0, 0, -50));

var oCam = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, 1, 500);
oCam.Position = pCam.position.clone();

pScene.add(pCam);
pScene.add(new THREE.AmbientLight(0xFFFFFF));

oScene.add(oCam);
oScene.add(new THREE.AmbientLight(0xFFFFFF));

var frontPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x990000 }));
frontPlane.position.z = -50;
pScene.add(frontPlane);

var backPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x009900 }));
backPlane.position.z = -100;
pScene.add(backPlane);

var circle = new THREE.Mesh(new THREE.CircleGeometry(60, 20), new THREE.MeshBasicMaterial( { color: 0x000099 }));
circle.position.z = -75;

//Transform position from perspective camera to orthogonal camera -> doesn't work, the circle is displayed in front
circle.position.project(pCam).unproject(oCam);

oScene.add(circle);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

renderer.autoClear = false;
renderer.render(oScene, oCam);
renderer.render(pScene, pCam);

You can try out the code here.

In the perspective world the (world) z-position of the circle is -75, which is between the squares (-50 and -100). But it is actually displayed in front of both squares. If you manually set the circles z-position (in the orthogographic scene) to -500 it is displayed between the squares, so with the right positioning, what I'm trying should be possible in principle.

I know that I can not render a scene the same with orthogographic and perspective cameras. My intention is to reposition all orthogographic objects before each rendering so that they appear to be at the right position.

What do I have to do to calculate the orthogographic coordinates from the perspective coordinates so that the objects are rendered with the right depth values?

UPDATE:

I have added an answer with my current solution to the problem in case someone has a similar problem. However, since this solution does not provide the same quality as the orthogographic camera. So I would still be happy if somoeone could explain why the orthogographic camera does not work as expected and/or provide a solution to the problem.

解决方案

You are very close to the result what you have expected. You have forgotten to update the camera matrices, which have to be calculated that the operation project and project can proper work:

pCam.updateMatrixWorld ( false );
oCam.updateMatrixWorld ( false );
circle.position.project(pCam).unproject(oCam);

Explanation:

In a rendering, each mesh of the scene usually is transformed by the model matrix, the view matrix and the projection matrix.

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

  • View matrix:
    The view matrix describes the direction and position from which the scene is looked at. The view matrix transforms from the wolrd space to the view (eye) space. In the coordinat system on the viewport, the X-axis points to the left, the Y-axis up and the Z-axis out of the view (Note in a right hand system the Z-Axis is the cross product of the X-Axis and the Y-Axis).

  • Model matrix:
    The model matrix defines the location, oriantation and the relative size of a mesh in the scene. The model matrix transforms the vertex positions from of the mesh to the world space.


If a fragment is drawn "behind" or "before" another fragment, depends on the depth value of the fragment. While for orthographic projection the Z coordinate of the view space is linearly mapped to the depth value, in perspective projection it is not linear.

In general, the depth value is calculated as follows:

float ndc_depth = clip_space_pos.z / clip_space_pos.w;
float depth = (((farZ-nearZ) * ndc_depth) + nearZ + farZ) / 2.0;

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.

At Orthographic Projection the coordinates in the eye space are linearly mapped to normalized device coordinates.


Orthographic Projection

At Orthographic Projection the coordinates in the eye space are linearly mapped to normalized device coordinates.

Orthographic Projection Matrix:

r = right, l = left, b = bottom, t = top, n = near, f = far 

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

At Orthographic Projection, the Z component is calcualted by the linear function:

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


Perspective Projection

At Perspective Projection the projection matrix describes the mapping from 3D points in the world as they are seen from of a pinhole camera, to 2D points of the viewport.
The eye space coordinates in the camera frustum (a truncated pyramid) are mapped to a cube (the normalized device coordinates).

Perspective Projection

Perspective Projection Matrix:

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

At Perspective Projection, the Z component is calcualted by the rational function:

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

See a detailed description at the answer to the Stack Overflow question How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?


In your case this means, that you have to choose the Z coordinate of the circle in the orthographic projection in that way, that the depth value is inbetween of the depths of the objects in the perspective projection.
Since the depth value in nothing else than depth = z ndc * 0.5 + 0.5 in both cases, it also possible to do the calculations by normalized device coordinates instead of depth values.

The normalized device coordinates can easily be caluclated by the project function of the THREE.PerspectiveCamera. The project converrts from wolrd space to view space and from view space to normalized device coordinates.

To find a Z coordinate which is in between in orthographic projection, the middle normalized device Z coordinate, has to be transformed to a view space Z coordinate. This can be done by the unproject function of the THREE.PerspectiveCamera. The unproject converts from normalized device coordinates to view space and from view space to world sapce.

See further OpenGL - Mouse coordinates to Space coordinates.


See the example:

var renderer, pScene, oScene, pCam, oCam, frontPlane, backPlane, circle;

  var init = function () {
    pScene = new THREE.Scene();
    oScene = new THREE.Scene();
    
    pCam = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
    pCam.position.set(0, 40, 50);
    pCam.lookAt(new THREE.Vector3(0, 0, -50));
    
    oCam = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, 1, 500);
    oCam.Position = pCam.position.clone();
    
    pScene.add(pCam);
    pScene.add(new THREE.AmbientLight(0xFFFFFF));
    
    oScene.add(oCam);
    oScene.add(new THREE.AmbientLight(0xFFFFFF));
    
    
    frontPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x990000 }));
    frontPlane.position.z = -50;
    pScene.add(frontPlane);
    
    backPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x009900 }));
    backPlane.position.z = -100;
    pScene.add(backPlane);

    circle = new THREE.Mesh(new THREE.CircleGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x000099 }));
    circle.position.z = -75;

    
    //Transform position from perspective camera to orthogonal camera -> doesn't work, the circle is displayed in front
    pCam.updateMatrixWorld ( false );
    oCam.updateMatrixWorld ( false );
    circle.position.project(pCam).unproject(oCam);
    
    oScene.add(circle);
    
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
  };
  
  var render = function () {
  
    renderer.autoClear = false;
    renderer.render(oScene, oCam);
    renderer.render(pScene, pCam);
  };
  
  var animate = function () {
      requestAnimationFrame(animate);
      //controls.update();
      render();
  };
  
  
  init();
  animate();

html,body {
    height: 100%;
    width: 100%;
    margin: 0;
    overflow: hidden;
}

<script src="https://threejs.org/build/three.min.js"></script>

这篇关于在Three.js中将Z位置从透视图移至正交摄影机的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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