如何在three.js中将矩形投影到Mesh/Terrain对象上,以便用作选择标记?

5
我有一个地形,是使用 THREE.Terrain库生成的。 我想能够点击和拖动出一个选框,并选择在地形网格表面上的对象。
目前,我正在检测拖动的开始和结束,并在全局XZ平面上绘制一个矩形,但我希望它与表面贴合。
目前它看起来像这样;

enter image description here

然而,我所追求的更像是这样的东西;

enter image description here

我在想是否错过了使用核心three.js功能的明显方法。
总是有一种暴力方法,即在矩形周围间隔地投射射线,并创建一系列线段来近似投影的矩形,但我想知道是否有本地方法。
(我只是这个星期开始接触three.js,所以可能错过了一些明显的东西...虽然我已经花了一整天进行实验,但没有太多运气)
更新
基于@prisoner849的建议,我混合他的代码和地形演示,这似乎工作得很好。
    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-click添加到选择集等等。但原则上片段着色器运行良好...

enter image description here


2
你可以查看 THREE.DecalGeometry() 的源代码。我不能确定它是否完全符合你的需求,但可以作为一种选项。 - prisoner849
谢谢,我会看一下。我愿意尝试任何东西来解决这个问题;-) - Tom
1
另一个想法是在地形上使用片段着色器,将minX,minY,maxX,maxY作为统一变量,并对那些距离这些线一定范围内的像素进行着色。需要注意保持线宽在斜坡上的一致性。 - Don McCurdy
1
@TomH:“跑马灯需要旋转以匹配相机视角”。看一下这个jsfiddle :) - prisoner849
1
嘿!我添加了你的更改:https://jsfiddle.net/tolland/502rjmq6/3/ - Tom
2个回答

10

我刚发布评论后,突然和Don McCurdy想到一模一样的想法(感谢你,Don :))。 在https://www.shadertoy.com上进行快速搜索,我找到了这个着色器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>


4
很棒,喜欢这个演示! :) - Don McCurdy
您的代码和地形演示的混合似乎非常有效; https://jsfiddle.net/tolland/vsee0sdz/ 不幸的是,SO限制可以粘贴到代码片段运行器中的行数... :-( - Tom
@TomH 不用谢 :) 你的 jsfiddle 做得很棒!我喜欢它 ;) - prisoner849

-1

如果有人对着色器的理解有困难,这里是我的解释尝试。

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,因此我们从c创建一个,将所有c的值末尾都加上1。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接