Three.js 在鼠标点击的地方绘制,但完全平行于相机方向

Three.js Draw where mouse clicks, but entirely parallel to camera orientation

我不太确定我该怎么做。我读了很多关于光线投射的文章,这似乎有助于找到与某物相交的点,但在这种情况下,我只想让它插值 2d 鼠标坐标到鼠标点击的 3d 点,无论缩放、旋转、那里是否有对象等。

我想到但没有采用的一种方法是制作一个与相机平行的不可见平面,该平面始终直立并且始终与 y 轴相交。然后使用光线投射器击中平面,根据需要绘制,然后删除平面。不过这样做似乎很愚蠢。

目前我有一个方法效果很好,但是当线离原点越来越远,或者相机被放大时,它会出现一些问题

在这张照片中,我从两个不同的角度画了两条线。垂直线是当相机与 x 轴和 z 轴水平时的样子,我沿着 y 轴画一条直线,而水平线是当我将相机面朝下涂写时发生的情况。 https://i.imgur.com/f8qw5xV.png

如您所见,似乎是使用到相机的距离来进行此计算,因此距相机的距离越远,计算中的失真就越大。如何摆脱这种扭曲?

来源:https://github.com/AskAlice/mandala-3d-threejs 现场演示:https://askalice.me/mandala/

相关代码如下:

js/content.js@112

function get3dPointZAxis(event)
{
    camPos = camera.position;
    var mv = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY/window.innerHeight) * 2 + 1, 1).unproject(camera);
    var m2 = new THREE.Vector3(0,0,0);
    var pos = camPos.clone(); 
    pos.add(mv.sub(camPos).normalize().multiplyScalar(m2.distanceTo(camPos)));
    return pos;
}

我使用了来自两个 Whosebug post 的信息来想出这个,它有我描述的问题。

首先,这个 post 展示了如何绘制并将其转换为 z 轴。它是平坦的。但是我在尝试在三个维度上工作时遇到了很多麻烦。

How to draw a line segment at run time using three.js

然后我使用下面的信息 post 至少让它在 x-z 轴上与相机平行,如下所示: https://i.imgur.com/E9AQNpH.png

Moving objects parallel to projection plane in three.js

带有 THREE.Plane()THREE.Raycaster().ray.intersectPlane() 的选项:

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();

function getPoint(event){
  mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  planeNormal.copy(camera.position).normalize();
  plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
  raycaster.setFromCamera(mouse, camera);
  raycaster.ray.intersectPlane(plane, point);
}

运行代码段,点击"draw"复选框将其设置为选中,随机移动鼠标(不按下鼠标),再次点击复选框,用mousedown旋转场景。所有点都在同一平面上。

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();

document.addEventListener("mousedown", onMouseDown, false);
document.addEventListener("mousemove", onMouseMove, false);

function getPoint(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  planeNormal.copy(camera.position).normalize();
  plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
  raycaster.setFromCamera(mouse, camera);
  raycaster.ray.intersectPlane(plane, point);
}

function setPoint() {
  var sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(.125, 4, 2), new THREE.MeshBasicMaterial({
    color: "yellow",
    wireframe: true
  }));
  sphere.position.copy(point);
  scene.add(sphere);
}

function onMouseDown(event) {
  getPoint(event);
  if (draw.checked) setPoint();
}

function onMouseMove(event) {
  getPoint(event);
  if (draw.checked) setPoint();
}

render();

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div style="position:absolute;">
  <input id="draw" type="checkbox">
  <label for="draw" style="color: white;">draw</label>
</div>