Three.js--"realize 3d kicking model display

Table of contents

Project build

Initialize the basic code of three.js

Set environment texture loading model

Realize the physical world with Cannon-es


Today, simply implement a small demo of three.js to strengthen your grasp and learning of three knowledge. Only in the project can you flexibly apply the knowledge you have learned, so let’s start directly without further ado.

Project build

In this case, the three project is written with the help of the framework, and the vue project is built with the vite construction tool. If you don’t know the vite construction tool, you can refer to the article I explained before: the construction and use of vite scaffolding . After the build is complete, open the project with an editor, execute npm i on the terminal to install dependencies, and after the installation is complete, install npm i three on the terminal.

<template>
  <!-- 踢球游戏 -->
  <kickballGame></kickballGame>
</template>

<script setup>
import kickballGame from './components/kickballGame.vue';
</script>

<style lang="less">
  *{
    margin: 0;
    padding: 0;
  }
</style>

Initialize the basic code of three.js

The basic code that must be used to start three.js is as follows:

Import the three library :

import * as THREE from 'three'

Initialize the scene :

const scene = new THREE.Scene()

Initialize the camera :

// 初始化相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.set(4,2,0)
camera.updateProjectionMatrix()

Initialize the renderer :

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true, // 设置抗锯齿
  logarithmicDepthBuffer: true, // 使用对数缓存
})
renderer.setSize(window.innerWidth,window.innerHeight)
// 设置色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
// 设置阴影
renderer.shadowMap.enabled = true
// 设置阴影类型,实现更加平滑和真实的阴影效果
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.body.appendChild(renderer.domElement)

Listen for screen size changes and import the controller :

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

// 初始化控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.enableDamping = true

// 监听屏幕大小变化
window.addEventListener("resize",()=>{ 
  renderer.setSize(window.innerWidth,window.innerHeight)
  camera.aspect = window.innerWidth/window.innerHeight
  camera.updateProjectionMatrix()
})

Set the render function :

const render = () =>{ 
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}

ok, after writing the basic code, the next step is to start the specific demo operation.

Set environment texture loading model

Use TextureLoader to load image textures, load image files as texture objects in Three.js, and apply textures to any object in the scene to achieve more vivid rendering effects.

const texture = new THREE.TextureLoader()
texture.load("./textures/outdoor.jpg", (texture)=>{
  texture.mapping = THREE.EquirectangularReflectionMapping
  // 设置环境纹理
  scene.background = texture
  scene.environment = texture
  scene.backgroundBlurriness = 0.3 // 设置模糊度
})

After the environment texture is set, load the 3D model in GLTF format to the web page through GLTFLoader, and use DRACOLoader to effectively compress the size of the 3D model file, improving the loading speed and user experience.

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";

// 解压模型
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./draco/')
// 加载模型
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('./model/playground02.glb',(glb) => {
  const model = glb.scene
  scene.add(model)
})

Set the spotlight to make the ball's shadow more visible:

// 添加聚光灯
const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(10,50,0)
spotLight.castShadow = true
spotLight.shadow.mapSize.width = 2048
spotLight.shadow.mapSize.height = 2048
spotLight.shadow.camera.near = 0.5
spotLight.shadow.camera.far = 500
spotLight.shadow.camera.fov = 30
spotLight.shadow.bias = -0.00008
spotLight.intensity = 0.1
scene.add(spotLight)

Realize the physical world with Cannon-es

Cannon-es is often used as a physics engine library for simulating physical effects in 3D scenes. It can realize functions such as collision detection, force acting on objects, and simulated gravity, so as to make 3D scenes more realistic and interesting.

Here, the terminal first downloads the relevant library, and then instantiates it to create the physical world:

import * as CANNON from 'cannon-es'

// 初始化物理世界
const world = new CANNON.World()
world.gravity.set(0,-9.82,0)

let clock = new THREE.Clock()
// 设置渲染函数
const render = () =>{ 
  let delta = clock.getDelta()
  world.step(delta) // 更新物理世界
  if(ball && ballBody) {
    ball.position.copy(ballBody.position)
    ball.quaternion.copy(ballBody.quaternion)
  }
  controls.update()
  requestAnimationFrame(render)
  renderer.render(scene,camera)
}
render()

Here traverse the 3D scene graph through traverse, traverse all objects (Object3D) and their sub-objects in the scene, and operate or judge them.

model.traverse((child)=>{
  if(child.isMesh && child.name.search(/Solid/ == -1)) {
    child.castShadow = true
    child.receiveShadow = true
    // 创建trimesh类型
    const trimesh = new CANNON.Trimesh(
      child.geometry.attributes.position.array,
      child.geometry.index.array
    )
    // 创建刚体
    const trimeshBody = new CANNON.Body({
      mass: 0,
      shape: trimesh
    })
    // 获取世界位置和旋转给到物理世界
    trimeshBody.position.copy(child.getWorldPosition(new THREE.Vector3()))
    trimeshBody.quaternion.copy(
      child.getWorldQuaternion(new THREE.Quaternion())
    )
    // 添加刚体到物理世界
    world.addBody(trimeshBody)

    if(child.name === "door"){
      child.material = new THREE.MeshBasicMaterial({
        color: 0x000000,
        opacity:0,
        transparent:true
      })
    }
  }
  if(child.name === 'Soccer_Ball'){
    ball = child
    // 创建球体
    const ballShape = new CANNON.Sphere(0.15)
    // 创建刚体
    ballBody = new CANNON.Body({
      mass: 1,
      position: new CANNON.Vec3(0,5,0),
      shape: ballShape
    })
    // 添加刚体到物理世界
    world.addBody(ballBody)
  }
  setTimeout(()=>{
    ballBody.position.set(0,15,0)
    ballBody.velocity.set(0,0,0)
    ballBody.angularVelocity.set(0,0,0)
  },2000)
})

By setting the click event to dynamically let Vec3 (representing the position, direction, speed, etc. in three-dimensional space), in order to display and change the value, then define a variable to dynamically change its value. Here we borrow the gsap animation library to make it repeat The jumping back and forth in a certain range:

let percentage = ref(30)

gsap.to(percentage, {
  duration: 1,
  value: 100,
  ease: "linear",
  repeat: -1,
  onUpdate: () =>{
    percentage.value = Math.floor(percentage.value)
  }
})

Set the dynamic variable in the Vec3 parameter, as follows:

let isClick = false
// 设置点击事件
window.addEventListener('click',()=>{
  if(isClick) return
  isClick = true
  ballBody.applyForce(
    new CANNON.Vec3(
      -1*percentage.value,
      6*percentage.value,
      (Math.random() - 0.5)*percentage.value
    ),ballBody.position)
  setTimeout(()=>{
    isClick = false
    ballBody.position.set(0,15,0)
    ballBody.velocity.set(0,0,0)
    ballBody.angularVelocity.set(0,0,0)
  },4000)
})

Next, dynamically display the change of the value through html and css styles:

<template>
  <div class="">
    <h1>{
   
   { percentage }}</h1>
  </div>
</template>

<style lang="less">
canvas {
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
}
h1{
  position: fixed;
  left: 0;
  top: 0;
  z-index: 10;
  color: #fff;
}
</style>

After the demo is finished, give the complete code of this case: (you can also private message the blogger to get the material) 

<template>
  <div class="">
    <h1>{
   
   { percentage }}</h1>
  </div>
</template>

<script setup>
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { ref } from 'vue'
import gsap from 'gsap'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";

let percentage = ref(30)

gsap.to(percentage, {
  duration: 1,
  value: 100,
  ease: "linear",
  repeat: -1,
  onUpdate: () =>{
    percentage.value = Math.floor(percentage.value)
  }
})

// 初始化场景
const scene = new THREE.Scene()
// 初始化相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.set(4,2,0)
camera.updateProjectionMatrix()

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true, // 设置抗锯齿
  logarithmicDepthBuffer: true, // 使用对数缓存
})
renderer.setSize(window.innerWidth,window.innerHeight)
// 设置色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
// 设置阴影
renderer.shadowMap.enabled = true
// 设置阴影类型,实现更加平滑和真实的阴影效果
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.body.appendChild(renderer.domElement)

// 初始化控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.enableDamping = true

// 监听屏幕大小变化
window.addEventListener("resize",()=>{ 
  renderer.setSize(window.innerWidth,window.innerHeight)
  camera.aspect = window.innerWidth/window.innerHeight
  camera.updateProjectionMatrix()
})

// 设置环境纹理
const texture = new THREE.TextureLoader()
texture.load("./textures/outdoor.jpg", (texture)=>{
  texture.mapping = THREE.EquirectangularReflectionMapping
  // 设置环境纹理
  scene.background = texture
  scene.environment = texture
  scene.backgroundBlurriness = 0.3 // 设置模糊度
})

let ball,ballBody

// 解压模型
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./draco/')
// 加载模型
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('./model/playground02.glb',(glb) => {
  const model = glb.scene
  model.traverse((child)=>{
    if(child.isMesh && child.name.search(/Solid/ == -1)) {
      child.castShadow = true
      child.receiveShadow = true
      // 创建trimesh类型
      const trimesh = new CANNON.Trimesh(
        child.geometry.attributes.position.array,
        child.geometry.index.array
      )
      // 创建刚体
      const trimeshBody = new CANNON.Body({
        mass: 0,
        shape: trimesh
      })
      // 获取世界位置和旋转给到物理世界
      trimeshBody.position.copy(child.getWorldPosition(new THREE.Vector3()))
      trimeshBody.quaternion.copy(
        child.getWorldQuaternion(new THREE.Quaternion())
      )
      // 添加刚体到物理世界
      world.addBody(trimeshBody)

      if(child.name === "door"){
        child.material = new THREE.MeshBasicMaterial({
          color: 0x000000,
          opacity:0,
          transparent:true
        })
      }
    }
    if(child.name === 'Soccer_Ball'){
      ball = child
      // 创建球体
      const ballShape = new CANNON.Sphere(0.15)
      // 创建刚体
      ballBody = new CANNON.Body({
        mass: 1,
        position: new CANNON.Vec3(0,5,0),
        shape: ballShape
      })
      // 添加刚体到物理世界
      world.addBody(ballBody)
    }
    setTimeout(()=>{
      ballBody.position.set(0,15,0)
      ballBody.velocity.set(0,0,0)
      ballBody.angularVelocity.set(0,0,0)
    },2000)
  })
  scene.add(model)
})

// 添加聚光灯
const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(10,50,0)
spotLight.castShadow = true
spotLight.shadow.mapSize.width = 2048
spotLight.shadow.mapSize.height = 2048
spotLight.shadow.camera.near = 0.5
spotLight.shadow.camera.far = 500
spotLight.shadow.camera.fov = 30
spotLight.shadow.bias = -0.00008
spotLight.intensity = 0.1
scene.add(spotLight)

// 初始化物理世界
const world = new CANNON.World()
world.gravity.set(0,-9.82,0)

let clock = new THREE.Clock()
// 设置渲染函数
const render = () =>{ 
  let delta = clock.getDelta()
  world.step(delta) // 更新物理世界
  if(ball && ballBody) {
    ball.position.copy(ballBody.position)
    ball.quaternion.copy(ballBody.quaternion)
  }
  controls.update()
  requestAnimationFrame(render)
  renderer.render(scene,camera)
}
render()

let isClick = false
// 设置点击事件
window.addEventListener('click',()=>{
  if(isClick) return
  isClick = true
  ballBody.applyForce(
    new CANNON.Vec3(
      -1*percentage.value,
      6*percentage.value,
      (Math.random() - 0.5)*percentage.value
    ),ballBody.position)
  setTimeout(()=>{
    isClick = false
    ballBody.position.set(0,15,0)
    ballBody.velocity.set(0,0,0)
    ballBody.angularVelocity.set(0,0,0)
  },4000)
})
</script>

<style lang="less">
canvas {
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
}
h1{
  position: fixed;
  left: 0;
  top: 0;
  z-index: 10;
  color: #fff;
}
</style>

おすすめ

転載: blog.csdn.net/qq_53123067/article/details/131011453