在three.js中将z位置从透视转置到正交相机 [英] Transpose z-position from perspective to orthographic camera in three.js

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

问题描述

我有一个场景,我想将透视对象(即在远处看起来更小的对象)与正交对象(即无论距离如何都显示相同大小的对象)组合在一起.透视对象是渲染世界"的一部分,而正交对象是装饰物,如标签或图标.与 HUD 不同,我希望将正交对象呈现在世界之内",这意味着它们可以被世界对象覆盖(想象一个平面经过标签之前).

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

为此,我将我的透视场景指定为领先"场景,即.所有对象的所有坐标(透视和正交)都是基于这个场景分配的.透视对象直接使用这些坐标,并在该场景中使用透视相机进行渲染.将正交对象的坐标转换为正交场景中的坐标,然后用正交相机在该场景中进行渲染.我通过将透视场景中的坐标投影到透视相机的视图窗格中进行转换,然后使用正交相机返回正交场景:

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

唉,这并没有按预期工作.正交对象总是在透视对象之前渲染,即使它们应该在它们之间.考虑这个例子,其中蓝色圆圈应该显示在红色方块之后,但之前显示在绿色方块之前(它不是):

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 }));前平面.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 }));圆.位置.z = -75;//将位置从透视相机转换为正交相机 ->不起作用,圆圈显示在前面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);渲染器.autoClear = false;renderer.render(oScene, oCam);renderer.render(pScene, pCam);

您可以

正交投影矩阵:

r = 右,l = 左,b = 下,t = 上,n = 近,f = 远2/(r-l) 0 0 00 2/(t-b) 0 00 0 -2/(f-n) 0-(r+l)/(r-l) -(t+b)/(t-b) -(f+n)/(f-n) 1

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

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


透视投影

在透视投影中,投影矩阵描述了从针孔相机看到的世界中的 3D 点到视口的 2D 点的映射.
相机平截头体(截断的金字塔)中的眼睛空间坐标映射到立方体(标准化设备坐标).

透视投影

透视投影矩阵:

r = 右,l = 左,b = 下,t = 上,n = 近,f = 远2*n/(r-l) 0 0 00 2*n/(t-b) 0 0(r+l)/(r-l) (t+b)/(t-b) -(f+n)/(f-n) -10 0 -2*f*n/(f-n) 0

在透视投影中,Z 分量由有理函数计算:

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

在 Stack Overflow 问题的答案中查看详细描述 如何在现代 OpenGL 中使用片段着色器中的 gl_FragCoord.z 线性渲染深度?


在您的情况下,这意味着您必须以这种方式在正交投影中选择圆的 Z 坐标,即 depth 值介于透视投影中的对象.
由于在这两种情况下深度值都在 depth = z ndc * 0.5 + 0.5 中,因此也可以通过标准化设备坐标而不是深度值进行计算.

标准化的设备坐标可以通过 THREE.PerspectiveCamera.project 从世界空间转换到视图空间,从视图空间转换到标准化设备坐标.

要在正交投影中找到介于两者之间的 Z 坐标,必须将中间标准化设备 Z 坐标转换为视图空间 Z 坐标.这可以通过 THREE 的 unproject 函数来完成.透视相机.unproject 将标准化设备坐标转换为视图空间,从视图空间转换为世界空间.

进一步查看 OpenGL - 鼠标坐标到空间坐标.


看例子:

var renderer, pScene, oScene, pCam, oCam, frontPlane, backPlane, circle;变量初始化=函数(){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 }));前平面.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 }));圆.位置.z = -75;//将位置从透视相机转换为正交相机 ->不起作用,圆圈显示在前面pCam.updateMatrixWorld ( 假 );oCam.updateMatrixWorld(假);circle.position.project(pCam).unproject(oCam);oScene.add(circle);渲染器 = 新的 THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);};var渲染=函数(){渲染器.autoClear = false;renderer.render(oScene, oCam);renderer.render(pScene, pCam);};var animate = 函数 () {requestAnimationFrame(动画);//controls.update();使成为();};在里面();动画();

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天全站免登陆