Three.js--》实现3d踢球模型展示

news2024/12/28 5:42:20

目录

项目搭建

初始化three.js基础代码

设置环境纹理加载模型

使用Cannon-es实现物理世界


今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。

项目搭建

本案例还是借助框架书写three项目,借用vite构建工具搭建vue项目,vite这个构建工具如果有不了解的朋友,可以参考我之前对其讲解的文章:vite脚手架的搭建与使用 。搭建完成之后,用编辑器打开该项目,在终端执行 npm i 安装一下依赖,安装完成之后终端在安装 npm i three 即可。

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

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

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

初始化three.js基础代码

three.js开启必须用到的基础代码如下:

导入three库

import * as THREE from 'three'

初始化场景

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)

监听屏幕大小的改变并导入控制器

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()
})

设置渲染函数

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

ok,写完基础代码之后,接下来开始具体的Demo实操。

设置环境纹理加载模型

使用TextureLoader用于加载图片纹理的工具,将图片文件加载为 Three.js 中的纹理对象,并且可以将纹理应用到场景中的任何物体上,从而实现更加生动的渲染效果。

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 // 设置模糊度
})

环境纹理设置完成之后通过GLTFLoader将GLTF格式的3D模型加载到网页上,以及使用DRACOLoader能够有效的压缩3D模型文件的大小,提高加载速度和用户体验。

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)
})

设置聚光灯让球的阴影变得更明显:

// 添加聚光灯
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)

使用Cannon-es实现物理世界

Cannon-es通常被用作物理引擎库,用于模拟三维场景中的物理效果。它可以实现碰撞检测、物体受力作用、模拟重力等功能,从而让3D场景更加真实和有趣。

这里终端先进行下载该相关库,然后进行实例化创建物理世界:

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()

这里通过traverse遍历3D场景图,遍历场景中所有的对象(Object3D)以及它们的子对象,并对它们进行操作或者判断。

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)
})

通过设置点击事件动态让 Vec3(表示三维空间中的位置、方向、速度等值) ,为了显示改变数值大小,接下来定义一个变量来去动态改变其值,这里借用gsap动画库去让其一直重复的在某个范围来回的跳动:

let percentage = ref(30)

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

将动态变量设置在 Vec3 参数里面,如下:

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)
})

接下来通过html和css样式来动态显示数值的变化:

<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>

demo做完,给出本案例的完整代码:(获取素材也可以私信博主) 

<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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/603796.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux4.6LNMP架构

文章目录 计算机系统5G云计算第五章 LINUX LNMP架构一、安装 Nginx 服务1.关闭防火墙&#xff0c;将安装nginx所需软件包传到/opt目录下2.安装依赖包3.创建运行用户、组4.编译安装Nginx5.添加 Nginx 系统服务6.修改nginx服务配置文件 三、编译安装mysqld 服务1.将安装mysql 所需…

MySQL—存储引擎(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️树高千尺&#xff0c;落叶归根人生不易&…

Windows部署WSL开启pwn之旅

不用虚拟机来操作真的太爽了&#xff01;&#xff01;还有和vscode的联动&#xff0c;更是妙绝&#xff01; 目录 前言 一、WSL获取 开启WSL支持 二、各种操作 1.apt 换源 2.更新软件 3.配置SSH服务器&#xff08;对于pwn来说非必要&#xff09; 4.WSL访问windows文件 …

【ChatGPT 】国内无需注册 openai 即可访问 ChatGPT:WeTab 浏览器扩展程序的安装与使用

目录 一、Sider&#xff1a;每天免费 30 查询额度【暂不推荐】 &#xff08;1&#xff09;Edge 浏览器扩展程序&#xff1a;Sider - ChatGPT【暂不推荐】 &#xff08;2&#xff09;iOS 手机 APP&#xff1a;Sider- Al 搭档&#xff08;有需要可以安装&#xff09; 二、 We…

chatgpt赋能python:Python创建画布的教程

Python 创建画布的教程 Python是一个功能强大的编程语言&#xff0c;其中一个重要的应用是数据可视化。在数据科学、机器学习、图像处理和软件开发中&#xff0c;Python的可视化功能非常实用。本教程将介绍Python创建画布的步骤&#xff0c;以及如何使用Matplotlib创建简单的图…

生产案例:消息堆积排查分析

&#x1f4dd;作者简介&#xff1a; 大家好&#xff0c;我是CBeann&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主。 22届校招进入阿里广告部门从事Java开发工程师。 平时有空会帮大家解决问题&#xff0c;模式面试和日常答疑&#xff0c;并且提供免费云服务器使用。 有一…

2023年06月IDE流行度最新排名

点击查看最新IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年06月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多&#xff0c;这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…

Linux4.5Nginx网站服务

文章目录 计算机系统5G云计算第五章 LINUX Nginx网站服务一、编译安装Nginx服务1.关闭防火墙&#xff0c;将安装nginx所需软件包传到/opt目录下2.安装依赖包3.创建运行用户、组4.编译安装Nginx5.检查、启动、重启、停止 nginx服务6.添加 Nginx 系统服务 二、认识Nginx服务的主配…

【数据结构】---几分钟简单几步学会手撕链式二叉树(下)

文章目录 前言&#x1f31f;一、二叉树链式结构的实现&#x1f30f;1.1 二叉树叶子节点个数&#x1f4ab;代码&#xff1a;&#x1f4ab;流程图&#xff1a; &#x1f30f;1.2 二叉树的高度&#x1f4ab;第一种写法(不支持)&#xff1a;&#x1f4d2;代码&#xff1a;&#x1f…

​Memcached 架构

Memcached是一种内存中的键值存储&#xff0c;最初是用Perl编写的&#xff0c;后来重写为C语言。它受到Facebook、Netflix和Wikipedia等公司的欢迎&#xff0c;因为它简单易用。 虽然当谈论到软件描述时&#xff0c;“简单”这个词已经失去了意义&#xff0c;但我认为Memcache…

chatgpt赋能python:用Python制作AI:优化搜索引擎结果的关键

用Python制作AI&#xff1a;优化搜索引擎结果的关键 搜索引擎正成为我们日常生活不可或缺的一部分。无论是寻找答案、娱乐还是购物&#xff0c;大多数人都会先打开搜索引擎。随着越来越多的数据被放入互联网中&#xff0c;如何让搜索引擎结果与用户的搜索意图相符&#xff0c;…

chatgpt赋能python:用Python办公自动化轻松完成繁琐重复的工作

用Python办公自动化轻松完成繁琐重复的工作 随着科技的进步&#xff0c;许多传统工作已经被自动化取代。而在许多职业中&#xff0c;办公自动化通常被认为是节省时间和减少错误的最佳方法。对于那些使用Microsoft Office&#xff0c;例如Excel、Word和PowerPoint等应用程序的用…

chatgpt赋能python:Python如何删除空白

Python 如何删除空白 在SEO优化过程中&#xff0c;我们需要保证我们的网页内容的质量和可读性。其中&#xff0c;一个重要的因素是删除空白。在Python中&#xff0c;我们可以使用多种方法来删除空白&#xff0c;下面我们将介绍一些方法并讨论它们的优缺点。 方法一&#xff1…

操作系统复习4.2.0-磁盘组织和管理

磁盘的结构 磁盘、磁道、扇区 磁盘划分n圈磁道&#xff0c;每条磁道划分为多个扇区 磁盘读写 磁头移动到需要读写的扇区所在的磁道来完成读写 磁盘转起来让目标扇区在磁头下面划过 盘面和柱面 分类 按磁头分类&#xff1a;磁头可伸缩移动、不可伸缩移动(同一盘面上有多个…

chatgpt赋能python:Python加入Path的好处及操作方法

Python加入Path的好处及操作方法 什么是Path&#xff1f; Path&#xff0c;顾名思义就是文件路径的意思。每当我们需要执行某些程序或打开某个文件&#xff0c;电脑都会按照这个文件路径来查找需要的文件或程序。在Windows系统中&#xff0c;文件路径是由一连串的路径名组成的…

DiffRate详解:高效Vision Transformers的可微压缩率

DiffRate详解&#xff1a;高效Vision Transformers的可微压缩率 0. 引言1. 相关内容介绍1.1 Transformer Block1.2 令牌修剪和合并1.3 修剪和合并的统一 2 DiffRate中的创新点2.1 令牌排序2.2 压缩率重参数化2.3 训练目标 3. 算法流程4. 总结 0. 引言 就当前的Vision Transfor…

决策树分类算法

#CSDN AI写作助手创作测评 目录 ID3算法 1.算法原理 2.代码实现 3.ID3算法的优缺点分析 C4.5算法 1.原理 2.优缺点 心得感受 决策树表示方法是应用最广泛的逻辑方法之一&#xff0c;它从一组无次序、无规则的事例中推理出决策树表示形式的分类规则。在决策树的内部…

Vue+springboot医院住院挂号登记收费系统7ui9s

医院信息管理系统的开发过程中&#xff0c;采用B / S架构&#xff0c;主要使用java语言进行开发&#xff0c;结合最新流行的springboot框架。使用Mysql数据库和idea开发环境。该医院信息管理系统包括用户、医生和管理员。其主要功能包括用户管理、医生管理、医生信息管理、预约…

chatgpt赋能python:Python删除非字母的SEO文章

Python删除非字母的SEO文章 Python是一个功能强大的编程语言&#xff0c;广泛应用于各类领域&#xff0c;包括搜索引擎优化&#xff08;SEO&#xff09;。在进行SEO优化时&#xff0c;有时需要从文本中删除非字母字符。这可以用Python快速高效地完成。本文将介绍如何使用Pytho…

基于PyQt5的图形化界面开发——堆栈动画演示

目录 0. 前言1. 了解堆栈2.代码实现3. 演示效果其他PyQt5文章 0. 前言 本文使用 PyQt5制作图形化界面演示数据结构中的堆栈操作 操作系统&#xff1a;Windows10 专业版 开发环境&#xff1a;Pycahrm Comunity 2022.3 Python解释器版本&#xff1a;Python3.8 第三方库&…