后处理(发光描边),射线拾取模型(模型点击事件),场景标注标签信息(在模型周围添加标签html)

135 阅读5分钟

后处理

实现高亮发光描边
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
// 创建后处理对象EffectComposer,WebGL渲染器作为参数
const composer = new EffectComposer(renderer);
// 引入渲染器通道RenderPass
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
// 创建后处理对象EffectComposer,WebGL渲染器作为参数
// 创建一个渲染器通道,场景和相机作为参数
const renderPass = new RenderPass(scene, camera);
// 设置renderPass通道
composer.addPass(renderPass);
//  OutlinePass`通道
// 引入OutlinePass通道
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
// OutlinePass第一个参数v2的尺寸和canvas画布保持一致
const v2 = new THREE.Vector2(window.innerWidth, window.innerHeight);
// const v2 = new THREE.Vector2(800, 600);
const outlinePass = new OutlinePass(v2, scene, camera);
// 一个模型对象
outlinePass.selectedObjects = [mesh];
// 多个模型对象
outlinePass.selectedObjects = [mesh1,mesh2,group];
// 设置OutlinePass通道
composer.addPass(outlinePass);
// 渲染循环
function render() {
    composer.render();
    // renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

射线拾取模型

Raycaster(鼠标点击选中模型)
renderer.domElement.addEventListener('click', function (event) {
    // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标px、py转WebGL标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
    //创建一个射线投射器`Raycaster`
    const raycaster = new THREE.Raycaster();
    //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
    // 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
    //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
    // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素
    const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
    console.log("射线器返回的对象", intersects);
    // intersects.length大于0说明,说明选中了模型
    if (intersects.length > 0) {
        // 选中模型的第一个模型,设置为红色
        intersects[0].object.material.color.set(0xff0000);
    }
})
// 鼠标单击选中工厂某个设备,并添加高亮发光描边后处理效果。
addEventListener('click', function (event) {
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标转标准设备坐标
    const x = (px / window.innerWidth) * 2 - 1;
    const y = -(py / window.innerHeight) * 2 + 1;
    const raycaster = new THREE.Raycaster();
    //.setFromCamera()在点击位置生成raycaster的射线ray
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
    const cunchu = model.getObjectByName('存储罐');
    // 射线拾取模型对象(包含多个Mesh)
    // 可以给待选对象的所有子孙后代Mesh,设置一个祖先属性ancestors,值指向祖先(待选对象)    
    for (let i = 0; i < cunchu.children.length; i++) {
        const group = cunchu.children[i];
        //递归遍历chooseObj,并给chooseObj的所有子孙后代设置一个ancestors属性指向自己
        group.traverse(function (obj) {
            if (obj.isMesh) {
                obj.ancestors = group;
            }
        })
    }
    // 射线交叉计算拾取模型
    const intersects = raycaster.intersectObjects(cunchu.children);
    console.log('intersects', intersects);
    if (intersects.length > 0) {
        // 通过.ancestors属性判断那个模型对象被选中了
        outlinePass.selectedObjects = [intersects[0].object.ancestors];
    }
})

场景标注标签信息

CSS2DRenderer(HTML标签)

// 通过`CSS2DRenderer.js`可以把HTML元素作为标签标注三维场景
CSS2模型对象`CSS2DObject`
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
// 通过`.position`属性设置标签模型对象的xyz坐标
tag.position.set(50,0,50);
// 把HTML元素对应的CSS2模型对象添加到其它模型对象或三维场景中。
scene.add(tag);
const group = new THREE.Group();
group.add(tag);
### 4. `CSS2Renderer.domElement`重新定位
css2Renderer.domElement.style.position = 'absolute';
css2Renderer.domElement.style.top = '0px';
// 改变canvas画布在网页位置,标签父元素也要重新定位
renderer.domElement.style.marginTop = '200px';
css2Renderer.domElement.style.top = '200px';
### Canvas全屏尺寸变化,CSS2渲染器设置
// 画布跟随窗口变化
window.onresize = function () {
    const width = window.innerWidth;
    const height = window.innerHeight;
    // cnavas画布宽高度重新设置
    renderer.setSize(width,height);
    // HTML标签css2Renderer.domElement尺寸重新设置
    css2Renderer.setSize(width,height);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
};

给CSS2模型对象赋予坐标

mesh.position.set(50,0,50);
// mesh设置一个父对象meshGroup
const meshGroup = new THREE.Group();
meshGroup.add(mesh);
// mesh位置受到父对象局部坐标.positionn影响
meshGroup.position.x = -100;

const tag = new CSS2DObject(div);
const worldPosition = new THREE.Vector3();
// 获取mesh的世界坐标(meshGroup.position和mesh.position累加结果)
mesh.getWorldPosition(worldPosition);
// mesh世界坐标复制给tag
tag.position.copy(worldPosition);

const group = new THREE.Group();
// 最后meshGroup和tag放在同一个父对象中即可
group.add(meshGroup,tag);
### CSS2模型对象作为Mesh子对象
//无论mesh有多少个父对象,CSS2模型对象作为Mesh子对象,可以直接继承mesh的世界坐标,相比通过
//getWorldPosition()`方法获取世界坐标,再设置标签模型位置`CSS2DObject.position`更方便。
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
//标签tag作为mesh子对象,默认受到父对象位置影响
mesh.add(tag);

标注模型几何体的某个顶点

// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
//标签tag作为mesh子对象,默认受到父对象位置影响
mesh.add(tag);

const pos = geometry.attributes.position;
// 获取几何体顶点1的xyz坐标,设置标签局部坐标.position属性
tag.position.set(pos.getX(0),pos.getY(0),pos.getZ(0));

标签位置

### CSS2模型对象标注工厂设备
loader.load("../工厂.glb", function (gltf) {
    const tag = new CSS2DObject(div);
    // const obj = gltf.scene.getObjectByName('设备A');
    const obj = gltf.scene.getObjectByName('设备B');
    //标签tag作为obj子对象,默认标注在工厂设备obj的局部坐标系坐标原点
    obj.add(tag);
})

标签指示线或箭头指向标注点

image.png

image.png

标签位置模型会在mesh模型局部坐标位置的原点位置
const div = document.getElementById('tag');
// id="tag"元素高度322px,默认标签中心与标注点
div.style.top = '-161px'; //平移-161px,指示线端点和标注点重合
// div.style.top = '-140px'; //可以在-161px基础上微调
// div.style.left = 'px';
// ### HTML标签渲染前隐藏
<!-- CSS2渲染器渲染器之前,隐藏标签 -->
<div id="tag" style="display: none;"><>

鼠标选中模型弹出标签(工厂)

addEventListener("click", function (event) {
  const px = event.offsetX;
  const py = event.offsetY;
  //屏幕坐标转标准设备坐标
  const x = (px / window.innerWidth) * 2 - 1;
  const y = -(py / window.innerHeight) * 2 + 1;
  const raycaster = new THREE.Raycaster();
  //.setFromCamera()在点击位置生成raycaster的射线ray
  raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
  const cunchu = model.getObjectByName("存储罐");
  // 射线拾取模型对象(包含多个Mesh)
  // 可以给待选对象的所有子孙后代Mesh,设置一个祖先属性ancestors,值指向祖先(待选对象)
  for (let i = 0; i < cunchu.children.length; i++) {
    const group = cunchu.children[i];
    //递归遍历chooseObj,并给chooseObj的所有子孙后代设置一个ancestors属性指向自己
    group.traverse(function (obj) {
      if (obj.isMesh) {
        obj.ancestors = group;
      }
    });
  }
  // 射线交叉计算拾取模型
  const intersects = raycaster.intersectObjects(cunchu.children);
  console.log("intersects", intersects);
  if (intersects.length > 0) {
    // 通过.ancestors属性判断那个模型对象被选中了
    outlinePass.selectedObjects = [intersects[0].object.ancestors];
    //tag会标注在intersects[0].object.ancestors模型的局部坐标系原点位置
    intersects[0].object.ancestors.add(tag);
    chooseObj = intersects[0].object.ancestors;
    // 获取模型对象对应的标注点
    // console.log('intersects[0].object.ancestors.name', intersects[0].object.ancestors.name);
    // const obj = model.getObjectByName(intersects[0].object.ancestors.name + '标注');
    // //tag会标注在空对象obj对应的位置
    // obj.add(tag);
    // chooseObj = obj;
    span.innerHTML = intersects[0].object.ancestors.name; //修改标签数据
  } else {
    if (chooseObj) {
      //把原来选中模型对应的标签和发光描边隐藏
      outlinePass.selectedObjects = []; //无发光描边
      chooseObj.remove(tag); //从场景移除
    }
  }
});

image.png