如何将矩形投影到 Mesh/Terrain 对象上,用作 select 选取框? (在three.js)

How to project a rectangle onto a Mesh/Terrain object, for use as a select marquee? (in three.js)

我有一个地形是使用 THREE.Terrain 库生成的。我希望能够单击并拖出地形网格表面上的选取框和 select 对象。

目前我正在检测拖动的开始和结束,并在全局 XZ 平面中绘制一个矩形,但我希望它与表面齐平。

目前是这样的;

然而我的目标更像是这样;

我想知道我是否错过了一些使用核心 three.js 功能的明显方法。

总是有一种蛮力方法,即围绕矩形的周边每隔一段时间投射光线,并创建一系列线段来近似投影的矩形,但我想知道是否有原生方法。

(我这周才开始看 three.js,所以我可能漏掉了一些明显的东西......虽然我花了最后一天时间进行试验,但运气不佳)

更新

根据@prisoner849 的建议,我 mashed up his code with the Terrain demo 似乎效果很好。

    varying vec2 vPos;

    void main() {
      vec2 Ro = size * .5;
      vec2 Uo = abs( vPos - center.xz ) - Ro;

      vec3 c = mix(vec3(1.), vec3(1.,0.,0.), float(enabled && (abs(max(Uo.x,Uo.y)) < lineHalfWidth)  ));

      gl_FragColor = vec4(c, float(enabled && (abs(max(Uo.x,Uo.y)) < lineHalfWidth)  ));
    }

  `;

代码需要大量清理,选取框需要旋转以匹配相机视角,按住 ctrl 键单击添加到 selection 集等会更好.

但原则上片段着色器运行良好...

在我发表评论后,我的想法与 Don McCurdy 完全一样(干杯,Don :))。 在 https://www.shadertoy.com gave me that shader https://www.shadertoy.com/view/XlsBRB 上快速搜索(查看那里来自 FabriceNeyret2 的精彩评论)。 所以我只是为这个非常粗略的概念改编了那个片段着色器。

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

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

var geom = new THREE.PlaneGeometry(20, 20, 10, 10);
geom.vertices.forEach(v => {
  v.z = THREE.Math.randFloat(-1, 1);
});
geom.rotateX(-Math.PI * .5);
geom.computeFaceNormals();
geom.computeVertexNormals();

var uniforms = {
  center: {
    value: new THREE.Vector3()
  },
  size: {
    value: new THREE.Vector2(1, 1)
  },
  lineHalfWidth: {
    value: 0.1
  }
}

var matShader = new THREE.ShaderMaterial({
  uniforms: uniforms,
  vertexShader: vertShader,
  fragmentShader: fragShader
});

var matWire = new THREE.MeshBasicMaterial({
  color: "gray",
  wireframe: true
});

var obj = THREE.SceneUtils.createMultiMaterialObject(geom, [matShader, matWire]);

scene.add(obj);

var gui = new dat.GUI();
gui.add(uniforms.size.value, "x", .5, 5.0).name("size.x");
gui.add(uniforms.size.value, "y", .5, 5.0).name("size.y");
gui.add(uniforms.lineHalfWidth, "value", .05, 2.0).name("line half-width");

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var point = new THREE.Vector3();

window.addEventListener("mousemove", function(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  intersects = raycaster.intersectObject(obj, true);
  if (intersects.length === 0) return;
  obj.worldToLocal(point.copy(intersects[0].point));
  uniforms.center.value.copy(point);

}, false);


render();

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/utils/SceneUtils.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/0949e59f/examples/js/libs/dat.gui.min.js"></script>
<script>
  var vertShader = `
    varying vec2 vPos;
    void main() {
      vPos = position.xz;
      gl_Position = projectionMatrix *
                    modelViewMatrix *
                    vec4(position,1.0);
    }
  `;

  var fragShader = `
    uniform vec3 center;
    uniform vec2 size;
    uniform float lineHalfWidth;
    
    varying vec2 vPos;
    
    void main() {
      vec2 Ro = size * .5;
      vec2 Uo = abs( vPos - center.xz ) - Ro;
      
      vec3 c = mix(vec3(1.), vec3(1.,0.,0.), float(abs(max(Uo.x,Uo.y)) < lineHalfWidth));
      
      gl_FragColor = vec4(c, 1.  );
    }
    
  `;
</script>

如果其他人在理解着色器时遇到困难,我会尝试解释。

size 包含矩形的宽度和高度 Ro是宽度的一半,高度的一半

Uo 是衡量您离矩形边缘有多近的度量。如果 vPos 在其中一条边上,则 X 或 Y 的表达式将为 0。以左侧的点为例 - abs(vPos.x - centre.x) 将等于 Ro,所以当你减去 Ro 结果是 0.

c - mix 是一个在两个值之间进行线性插值的函数 - 你给它一个开始、结束和一个用于进行插值的值。在这里,我们在 WHITERED 之间进行插值,我们使用表示我们离矩形边缘有多近的表达式 (float(abs(max(Uo.x,Uo.y)) < lineHalfWidth) 来进行插值。

gl_FragColor — 我们需要 vec4 对应 color,因此我们通过在 [=16] 的所有值末尾添加 1 从 c 创建一个=].