three.js Raycaster(鼠标点击选中模型)

效果:

代码:

javascript 复制代码
<template>
  <div>
    <el-container>
      <el-main>
        <div class="box-card-left">
          <div id="threejs" style="border: 1px solid red"></div>
          <div class="box-right" style="text-align: left; padding: 10px">
            <div style="text-align: left">标准设备坐标系:</div>
            <div>
              three.js Canvas
              画布具有一个标准设备坐标系,该坐标系的坐标原点是在canvas画布的中间位置,x轴水平向右,y轴竖直向上,标准设备坐标系的坐标值不是绝对值,
              是相对值,范围是[-1,1],
              也就是说canvas画布上任何一个位置的坐标,如果用标准设备坐标取衡量,那么坐标的所有值都在-1
              和 1之间;
            </div>
            <div style="text-align: left; margin-top: 10px">
              屏幕坐标转为 标准设备坐标:
            </div>
            <div style="text-align: left">
              <pre>
                // 坐标转化公式
                addEventListener('click',function(event){
                    const px = event.offsetX;
                    const py = event.offsetY;
                    //屏幕坐标px、py转标准设备坐标x、y
                    //width、height表示canvas画布宽高度
                    const x = (px / width) * 2 - 1;
                    const y = -(py / height) * 2 + 1;
                })
              </pre>
            </div>
          </div>
        </div>
      </el-main>
    </el-container>
  </div>
</template>
<script>
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
// 效果制作器
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
// 渲染通道
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
// 发光描边OutlinePass
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";

export default {
  data() {
    return {
      name: "",
      scene: null,
      camera: null,
      renderer: null,
      effectComposer: null,
      mesh: null,
      geometry: null,
      group: null,
      material: null,
      texture: null,
      position: null,
      outlinePass: null,
      canvasWidth: 1000,
      canvasHeight: 800,
      color: [],
      meshArr: [],
    };
  },
  created() {},
  mounted() {
    this.name = this.$route.query.name;
    const numbers = Array.from({ length: 255 }, (_, i) => i);
    // const uppercaseLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
    this.color = [...numbers];
    this.init();
  },
  methods: {
    goBack() {
      this.$router.go(-1);
    },
    addEventListenerFn() {
      // canvas画布添加监听事件
      document.getElementById("threejs").addEventListener("click", (event) => {
        // 1,坐标转换
        const point_x = event.offsetX; // 获取x方向当前点击点距离原点的距离;向右为正(原点是当前元素的左上角点)
        const point_y = event.offsetY; // 获取y方向当前点击点距离原点的距离;向下为正(原点是当前元素的左上角点)
        const x = (point_x / this.canvasWidth) * 2 - 1;
        const y = -(point_y / this.canvasHeight) * 2 + 1;
        // 2,创建射线投射器对象,
        const raycaster = new this.$three.Raycaster();
        // 3,射线计算,(参数是鼠标点击位置,相机参数)
        raycaster.setFromCamera(new this.$three.Vector2(x, y), this.camera);
        // 4,射线交叉计算
        // const mesh_arr = []
        // if(!this.scene)return;
        // this.scene.traverse(m => {
        //   console.log('m',m);
        //   if(m.isMesh != undefined && m.isMesh) {
        //     mesh_arr.push(m);
        //   }
        // })
        const intersects = raycaster.intersectObjects(this.meshArr);
        if (intersects.length > 0) {
          if (this.effectComposer) {
            // intersects[0].object.material.color.set(0xff0000);
            this.outlinePass.selectedObjects = [intersects[0].object];
            this.renderFun();
          }
        }
      });
    },
    // 随机颜色
    randomColor() {
      // 要生成min-max之间的随机数,公式为:Math.random()*(max-min+1)+min
      let i = Math.floor(Math.random() * (this.color.length - 0 + 1) + 0);
      let j = Math.floor(Math.random() * (this.color.length - 0 + 1) + 0);
      let k = Math.floor(Math.random() * (this.color.length - 0 + 1) + 0);
      this.position = new this.$three.Vector3(i, j, k);
      return new this.$three.Color(
        "rgb(" +
          this.color[i] +
          ", " +
          this.color[j] +
          ", " +
          this.color[k] +
          ")"
      );
    },
    init() {
      // 创建场景对象
      this.scene = new this.$three.Scene();
      // 创建立方缓存几何体对象
      for (let i = 0; i < 3; i++) {
        this.boxGeometry();
      }
      // 创建辅助坐标轴对象
      const axesHelper = new this.$three.AxesHelper(200);
      this.scene.add(axesHelper);

      // 创建正交投影相机对象
      // this.camera = new this.$three.PerspectiveCamera(60,1,0.01,1000);
      // this.camera.position.set(600,600,600);
      // this.camera.lookAt(0,0,0);

      // 创建透视投影相机对象
      this.camera = new this.$three.OrthographicCamera(
        -500,
        500,
        400,
        -400,
        0,
        1000
      );
      this.camera.position.set(200, 200, 200);
      this.camera.lookAt(0, 0, 0);
      // const helper = new this.$three.CameraHelper( this.camera );
      // this.scene.add( helper );
      // 创建渲染器对象
      this.renderer = new this.$three.WebGLRenderer();
      this.renderer.setSize(this.canvasWidth, this.canvasHeight);
      this.renderer.render(this.scene, this.camera);
      window.document
        .getElementById("threejs")
        .appendChild(this.renderer.domElement);
      // 创建相机空间轨道控制器
      const controls = new OrbitControls(this.camera, this.renderer.domElement);
      controls.addEventListener("change", () => {
        // this.renderer.render(this.scene, this.camera);
        this.effectComposer.render();
      });
      // 调用后处理方法
      this.effectComposerFn();
      // 调用 监听  点击事件的方法
      this.addEventListenerFn();
    },
    // 创建盒模型的方法
    boxGeometry() {
      // 创建网格基础材质对象
      const material = new this.$three.MeshBasicMaterial({
        color: this.randomColor(),
      });
      // 创建立方几何体对象
      const geometry = new this.$three.BoxGeometry(
        this.position.x,
        this.position.y,
        this.position.z
      );
      // 创建网格模型对象
      const mesh = new this.$three.Mesh(geometry, material);
      if (this.position) {
        // 网格模型设置位置
        mesh.position.set(this.position.x, this.position.y, this.position.z);
      }
      this.meshArr.push(mesh);
      this.scene.add(mesh);
    },
    // 后处理方法
    effectComposerFn() {
      // 创建后处理对象
      this.effectComposer = new EffectComposer(this.renderer);
      // 创建后处理渲染器通道对象
      const renderPass = new RenderPass(this.scene, this.camera);
      // 后处理对象 添加渲染器通道
      this.effectComposer.addPass(renderPass);
      // 创建发光描边对象
      this.outlinePass = new OutlinePass(
        new this.$three.Vector2(this.canvasWidth, this.canvasHeight),
        this.scene,
        this.camera
      );
      // 设置发光描边颜色
      this.outlinePass.visibleEdgeColor.set(0xE0F409);
      // 设置发光描边厚度
      this.outlinePass.edgeThickness = 5;
      // 描边亮度
      this.outlinePass.edgeStrength = 20;
      // 设置发光描边频率
      this.outlinePass.pulsePeriod = 2;
      
      this.effectComposer.addPass(this.outlinePass);
    },
    renderFun() {
      // 调用后处理对象的render方法进行渲染,
      this.effectComposer.render();
      window.requestAnimationFrame(this.renderFun);
    },
  },
};
</script>
//
<style lang="less" scoped>
.box-card-left {
  display: flex;
  align-items: flex-start;
  flex-direction: row;

  width: 100%;

  .box-right {
  }
}
</style>
相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax