当前位置:   article > 正文

three.js: 点击交互事件 含解决点击的物体与看到的不一致问题(非全屏/多边形偏移)_threejs 点击事件

threejs 点击事件

在 three.js 中,可以通过添加事件监听器来实现点击交互事件。具体步骤如下:

1. 获取场景中的所有物体,并为每个物体添加一个点击事件监听器。

javascript 
scene.traverse(function(object) { 
  if (object instanceof THREE.Mesh) { 
    object.addEventListener('click', function() { 
      // 处理点击事件 
    }); 
  } 
}); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2. 在点击事件处理函数中,可以获取到被点击的物体对象,并进行相应的操作。


function handleClick(event) { 
  var mouse = new THREE.Vector2(); 
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1; 
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; 
 
  var raycaster = new THREE.Raycaster(); 
  raycaster.setFromCamera(mouse, camera); 
 
  var intersects = raycaster.intersectObjects(scene.children, true); 
 
  if (intersects.length > 0) { 
    var clickedObject = intersects[0].object; 
    // 处理被点击的物体对象 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

需要注意的是,有时候点击的物体与看到的物体不一致,这可能是由于非全屏或多边形偏移等问题导致的。解决方法如下:

1. 非全屏问题:在创建渲染器时,将 canvas 的宽高设置为窗口的宽高。

var renderer = new THREE.WebGLRenderer({ canvas: canvas }); 
renderer.setSize(window.innerWidth, window.innerHeight); 
  • 1
  • 2

2. 多边形偏移问题:在创建材质时,设置 polygonOffset 属性。

var material = new THREE.MeshBasicMaterial({ 
  color: 0xffffff, 
  polygonOffset: true, 
  polygonOffsetFactor: 1, 
  polygonOffsetUnits: 1 
}); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过以上方法,可以实现 three.js 中的点击交互事件,并解决点击的物体与看到的不一致问题。

示例

<template>
  <div class="hello">
    <div class="tip">
      <canvas id="three"></canvas>
    </div>
    
    <Mabtn @cocorbtn="cocorbtn" @caizhibtn="caizhibtn" @btnopen="btnopen"/>
  </div>
</template>

<script>
import Mabtn from "../components/mabtn.vue";
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
// 添加轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer"
import {RenderPass} from "three/examples/jsm/postprocessing/RenderPass"
import {OutlinePass} from "three/examples/jsm/postprocessing/OutlinePass"
import {ShaderPass} from "three/examples/jsm/postprocessing/ShaderPass"
import {FXAAShader} from "three/examples/jsm/shaders/FXAAShader"

import {setcolor} from '../../public/setColor'
var OBJ = ''
export default {
  name: 'HelloWorld',
  components:{
    Mabtn
  },
  data(){
    return{

      renderer:null,
      nameNode:null,
      composer:null,
      outlinePass:null,
      renderPass:null,
      scene:null,
      camera:null,
      gltfscene:null,
      bujian:'',
      faguang:false,
    }
  },
  mounted() {
    this.initThree()
    // this.render()
  },
  
  methods: {
    btnopen(){
      // document.querySelector('#three').style.height='100%'
      
    },
    cocorbtn(b){
      console.log(b);
      console.log(this.faguang);
      if (!this.faguang) {
        if (this.nameNode.children.length>0) { //点击颜色
          let img =  setcolor(b)
           // 更换纹理贴图
          var texture = new THREE.TextureLoader().load(img + '.png');
          OBJ.material.map = texture
          for (let i = 0; i < this.nameNode.children.length; i++) {
            this.nameNode.children[i].material.color.set(b)
          } 
        }else{
          this.nameNode.material.color.set(b)
        }
      }
     
    },
    caizhibtn(a){//点击部件
      console.log(a);
      this.bujian=a
      this.nameNode=this.gltfscene.getObjectByName(a)
      if (this.nameNode.children.length>0) { //点击颜色
        this.outlineObj([this.nameNode.children[0]])
        OBJ = this.nameNode.children[0]
        console.log(this.nameNode.children[0]);
      }else{
        this.outlineObj([this.nameNode])
        OBJ = this.nameNode
      }
      this.faguang=!this.faguang
    },
    
    initThree() {
      let that = this
      this.scene = new THREE.Scene() // 创建一个scene 
      this.scene.background = new THREE.Color('#eee') // 背景颜色

      const canvas = document.querySelector('#three')
      // 渲染器锯齿属性.antialias
      that.renderer = new THREE.WebGLRenderer({ canvas, antialias: true }) // 创建一个WebGLRenderer,将canvas和配置参数传入
   
      // 引入3D模型 gltf放置public目录 
      const gltfLoader = new GLTFLoader()
      gltfLoader.load('/ShoeOne/ShoeOne.gltf', (gltf) => {
        let model = gltf.scene
        // 递归遍历所有模型节点批量修改材质
        gltf.scene.traverse(function(obj) {
            if (obj.isMesh) {//判断是否是网格模型
                // console.log('模型节点',obj);
                // console.log('模型节点名字',obj.name);
                // console.log('gltf默认材质',obj.material); 
            }
        });
        console.log(gltf.scene)
        this.gltfscene = gltf.scene
        // 设置模型离中心点的位置
        // gltf.scene.scale.set(40,40,40)
        // gltf.scene.position.x = 0
        // gltf.scene.position.y = -80
        // gltf.scene.position.z = 0
        //  console.log(nameNode);
        //  for (let i = 0; i < nameNode.children.length; i++) {
        //   nameNode.children[i].material.color.set(0xff0000)
        //   nameNode.children[i].position.x = 0
        //   nameNode.children[i].position.y = -10
        //   nameNode.children[i].position.z = 0
        //  } 
        // const nameNode = gltf.scene.getObjectByName("内里");
        // nameNode.material.color.set(0xff0000);//改变Mesh材质颜色
       
        this.scene.add(model)
      })
      that.renderer.domElement.addEventListener('click', that.modelMouseClick, false) //设置点击方法
      that.renderer.setSize(window.innerWidth, window.innerHeight)

      const hemLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 1)
      hemLight.position.set(0, 48, 0)
      this.scene.add(hemLight)

      //平行光 (这里可以用点光源PointLight和环境光AmbientLight没有特定方向,整体改变场景的光照明暗)
      const dirLight = new THREE.DirectionalLight(0xffffff, 1)
      //光源等位置
      dirLight.position.set(-10, 8, -5)
  
      //使用PerspectiveCamera(透视摄像机):透视相机用来模拟人眼所看到的景象,物体的大小会受远近距离的影响,它是3D场景的渲染中使用得最普遍的投影模式。
      this.camera = new THREE.PerspectiveCamera(
        50,
        window.innerWidth / window.innerHeight,
        1,
        1000
      )
      // camera.position.z = 10 // 正方位
      this.camera.position.set(3, 4, 3);

      const controls = new OrbitControls(this.camera, that.renderer.domElement)
      // 阻尼感
      controls.enableDamping = true
        // 动画循环函数
      function animate() {
        controls.update()
        that.renderer.render(that.scene, that.camera)
        requestAnimationFrame(animate)

        if (resizeRendererToDisplaySize(that.renderer)) {
          const canvas = that.renderer.domElement
          that.camera.aspect = canvas.clientWidth / canvas.clientHeight
          that.camera.updateProjectionMatrix()
        }
        if (that.composer) {
          that.composer.render()
        }
      }
      animate()
      // 物理像素分辨率与CSS像素分辨率
      function resizeRendererToDisplaySize(renderer) {
        const canvas = renderer.domElement
        var width = window.innerWidth
        var height = window.innerHeight
        var canvasPixelWidth = canvas.width / window.devicePixelRatio
        var canvasPixelHeight = canvas.height / window.devicePixelRatio

        const needResize =
          canvasPixelWidth !== width || canvasPixelHeight !== height
        if (needResize) {
          renderer.setSize(width, height, false)
          
        }
        return needResize
      }
    },
        // 窗口监听函数
    onWindowResize() {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    },
 
    
    // 模型的点击事件
    modelMouseClick( event ) {
      let raycaster = new THREE.Raycaster();
      let mouse = new THREE.Vector2();
      // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
      mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
      mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 0.8;


      raycaster.setFromCamera(mouse, this.camera);

      const intersects = raycaster.intersectObjects(this.scene.children);

      // if (intersects[0].object) {
        if (this.nameNode==intersects[0].object) {
          console.log('jin1');
          this.faguang=!this.faguang
        }else{
          console.log('jin2');
          this.faguang=false
        }
      // }
     
      console.log(this.faguang,'001');
      this.nameNode= intersects[0].object
      // 根据它来判断点击的什么,length为0即没有点击到模型
      console.log(intersects.length ? intersects[0].object.name : intersects, 'intersects----->>>')
       // 获取选中最近的 Mesh 对象

       if (intersects.length != 0 && intersects[0].object instanceof THREE.Mesh) {
        this.outlineObj([intersects[0].object] )
      }
      // if(intersects.length){

      // }
    },
    //高亮显示模型(呼吸灯)
    outlineObj (selectedObjects) {
      console.log(selectedObjects);
      // let that = this
       // 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。
       this.composer = new EffectComposer(this.renderer)
      // 新建一个场景通道  为了覆盖到原理来的场景上
      this.renderPass = new RenderPass(this.scene, this.camera)
      this.composer.addPass(this.renderPass);
      // 物体边缘发光通道
      this.outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), this.scene, this.camera, selectedObjects)
      console.log(this.faguang,'002');
      if (!this.faguang) {
        this.outlinePass.selectedObjects = selectedObjects
      }else{
        this.outlinePass.selectedObjects =[]
      }
      console.log('显示',this.faguang);
      this.outlinePass.edgeStrength = 15.0 // 边框的亮度
      this.outlinePass.edgeGlow = 2// 光晕[0,1]
      this.outlinePass.usePatternTexture = false // 是否使用父级的材质
      this.outlinePass.edgeThickness = 1.0 // 边框宽度
      this.outlinePass.downSampleRatio = 1 // 边框弯曲度
      this.outlinePass.pulsePeriod = 0 // 呼吸闪烁的速度
      this.outlinePass.visibleEdgeColor.set(parseInt(0x0000FF)) // 呼吸显示的颜色
      this.outlinePass.hiddenEdgeColor = new THREE.Color(0x0000FF) // 呼吸消失的颜色
      this.outlinePass.clear = true
      this.composer.addPass(this.outlinePass)
      // 自定义的着色器通道 作为参数
      let effectFXAA = new ShaderPass(FXAAShader)
      effectFXAA.uniforms.resolution.value.set(1 / window.innerWidth, 1 / window.innerHeight)
      effectFXAA.renderToScreen = true
      this.composer.addPass(effectFXAA)
      
    },

 
  }
}
</script>

<style scoped>
.tip{
  width: 100%;
  height: 80%;
  position: fixed;
  top: 0;
  left: 0;
}
#three {
  width: 100% !important;
  height: 100% !important;

}
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/398571?site
推荐阅读
相关标签
  

闽ICP备14008679号