vite2+vue3+ts+threejs搭建项目,摸鱼学习vue3和threejs的日子(二)

952 阅读8分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情

一、前言

昨天(雾)我们已经创建好项目了,本文我们先配置一下插件工具,然后再看下vue3如何引入threejs,

二、 配置工具插件

俗话说,工欲善其事必先利其器,优秀的提示可以造就我们良好的代码规范。

所以今天来配置下一些插件工具,一共是eslint、volar、TypeLens。

eslint负责代码规范,vite创建的项目需要额外配置一下,同时要支持ts

eslint步骤略长一些,下面单独拎出来讲。

volar代替vetur给我们提供语法支持(两者不兼容,vue3用volar,vue2用vetur,可打开插件配置进行禁用)

image.png

TypeLens可以显示每个函数的调用次数,在清除旧代码屎山的时候,很有用! 

volar和TypeLens的配置比较简单,就合并一起讲了

1、eslint

安装eslint

npm i -D eslint

初始化配置,接下来按提示选择即可

npx eslint --init

a、选择模式 此处选用第三个严格模式

image.png

b、选择语言模块 此处选用javascript

image.png

c、选择语言框架 此处选用vue.js

image.png

d、使用ts, 选择yes

image.png

e、代码运行范围 空格全选 浏览器browser+node

image.png

f、选择风格 popular就行

image.png

g、风格指南 选standard

image.png

h、配置文件格式 选js

image.png

i、有出现下面就选yes

image.png

j、选npm安装

image.png

安装完成后会在根目录生成.eslintrc.cjs文件,先不管,继续安装

npm i -D vite-plugin-eslint
npm i @babel/eslint-parser

然后配置一下.eslintrc.cjs

// .eslintrc.js 文件
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'standard',
    // 新增这里vue3支持,关键
    'plugin:vue/vue3-recommended'
  ],
  // 新的内容
  parserOptions: {
    ecmaVersion: 6,
    sourceType: 'module',
    ecmaFeatures: {
      modules: true
    },
    requireConfigFile: false
    // parser: '@babel/eslint-parser' // 会报错就不要了
  },
  plugins: [
    'vue'
  ],
  rules: {
    semi: [2, 'never'], // 禁止尾部使用分号“ ; ”
    'no-var': 'error', // 禁止使用 var
    indent: ['error', 2], // 缩进2格
    'no-mixed-spaces-and-tabs': 'error', // 不能空格与tab混用
    quotes: [2, 'single'], // 使用单引号
    'vue/html-closing-bracket-newline': 'off', // 不强制换行
    'vue/singleline-html-element-content-newline': 'off', // 不强制换行
    'vue/max-attributes-per-line': ['error', {
      singleline: { max: 5 },
      multiline: { max: 5 }
    }] // vue template模板元素第一行最多5个属性
    // 其它的规则可以去eslint查看,根据自己需要进行添加
  }
}

到这里就完成eslint的配置了。

2、volar和typeLens

(1)volar

很重要,再提醒一遍,vue3项目,记得先关闭vetur插件(vue2用的)!

很重要,再提醒一遍,vue3项目,记得先关闭vetur插件(vue2用的)!

很重要,再提醒一遍,vue3项目,记得先关闭vetur插件(vue2用的)!

打开vscode扩展 快捷键 ctrl+shift+x

直接搜索volar,这个骷髅头的就是啦

image.png

安装之后可以打开配置项

第一步检查这三个trace是否为off状态,这三个无意义(暂时),而且很占网络,会卡顿

image.png

第二步,配置快捷折叠

在setting.json里配置

// 左侧不折叠的标签,其余标签会折叠
"volar.splitEditors.layout.left": [
	"template",
	"script",
	"scriptSetup",
],
// 右侧不折叠的标签,其余标签会折叠
"volar.splitEditors.layout.right": [
	"styles",
	"customBlocks"
],

然后点击这个小图标就可以自动全部折叠了

image.png

第三步,配置ref自动补全

确保下面这个auto complete refs是勾选状态

image.png

(2)typeLens

直接搜索下载安装即可,这个见仁见智,建议是配置完后再开启

三、 引入静态图片的方式

1、试试import引入

const imgurl1 = import('../assets/demo.png')
// 采用vue2的常规方式,import引入,且为相对路径

我是在3ddemo.vue文件使用的,所以这个import起始地址为src/components/ image.png 没有报错,但是 image.png

网页元素也是无法正常解析 image.png

2、 试试require引入

const imgurl2 = require('../assets/demo.png')

根据报错提示,安装对应依赖

npm i --save-dev @types/node

控制台不报错了,但是波浪线已经报not found

// 尝试加上default
const imgurl2 = require('../assets/demo.png').default

还是不能正常引入。

后续查了一下,这里的require似乎是webpack提供的api,但我们项目是用的vite构建的,所以不具备。

哈哈骗你们一手。

3、直接当文件引入

import imgurl3 from "../assets/demo.png";

也可以正常引入,src里是路径,不带ip和端口 image.png

4、采用new URL(url, import.meta.url).href (vite引入静态图片推荐方式)

const imgurl = new URL('../assets/demo.png', import.meta.url).href

也是相对路径引入

这次是能够正常解析了,src里是ip端口+路径

image.png

5、贴个简单的代码

<script setup lang="ts">
import { ref } from 'vue'
import imgurl3 from "../assets/demo.png" // 当文件引入
defineProps<{ msg: string }> (
  
)
const imgurl = new URL('../assets/demo.png', import.meta.url).href // vite推荐的静态资源引入方式
const imgurl1 = import('../assets/demo.png') // import引入的另一种方式
// const imgurl2 = require('../assets/demo.png').default // require报错,不推荐

</script>

<template>
  <div>
    threejs3d场景
  </div>
  <div>
    <img :src="imgurl3" alt="">
  </div>
</template>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>

四、 引入threejs并简单加载mode

1、引入

下面是引入的文件和对应用途,不过ts里使用第三方库比较麻烦(是我暂时还没找到方法┭┮﹏┭┮)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- threejs基本库three.min.js -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.min.js"></script>
    <!-- threejs轨道控制器 orbitControls.js -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/controls/OrbitControls.js"></script>
    <!-- 加载器 用于加载 .obj 资源的加载器 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/loaders/OBJLoader.js"></script>
    <!-- 加载器 用于加载 .fbx 资源的加载器 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/loaders/FBXLoader.js"></script>
    <!-- fbx的依赖 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/libs/inflate.min.js"></script>
    <!-- 加载器 用于加载 .gltf 资源的加载器 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/loaders/gltfLoader.js"></script>
    <!-- 加载器 用于加载 .mtl 资源的加载器 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/loaders/MTLLoader.js"></script>
    <!-- 使用 Draco 库压缩的几何图形加载器 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/loaders/DRACOLoader.js"></script>
    <!-- CSS2DRenderer是CSS3DRenderer(CSS 3D渲染器)的简化版本,唯一支持的变换是位移 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/renderers/CSS2DRenderer.js"></script>
    <!-- 效果合成器 用于在three.js中实现后期处理效果 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/postprocessing/EffectComposer.js"></script>
    <!-- RenderPass.js 可以用来在EffectComposer对象上添加渲染通道 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/postprocessing/RenderPass.js"></script>
    <!-- shaderPass.js  用来自定义后期 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/postprocessing/ShaderPass.js"></script>
    <!-- copyShader.js  是threejs的内置着色器包 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/shaders/CopyShader.js"></script>
    <!-- outlinePass.js 后期处理用,物体边界线条高亮处理 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/postprocessing/OutlinePass.js"></script>
    <!-- 主要解决锯齿问题 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/shaders/FXAAShader.js"></script>
    <!-- 镜头炫光效果 -->
    <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/objects/Lensflare.js"></script>
    <title>Vite + Vue + TS</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

所以本文采用npm安装的方式,因为是ts所以不是npm install three了

 npm install --save @types/three
 

下载后,要在src文件夹下找到一个.d.ts结尾的文件,我的项目里叫vite-env.d.ts,添加

 declare module "@types/three";

image.png

然后就可以在项目文件里

   import * as THREE from "three";
   import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" // 轨道控制器
   // 基本就是后缀在上面的html文件里是一样的,按需引用就好
 

报错消失了,功能也能正常引入和使用了。

ts似乎还有个抛出全局变量的功能,等后续我琢磨琢磨

2、 使用threejs

最近接触了一些类的写法,今天就用这种方式来带大家调用一下

首先是在vue文件里创建好对应元素设置好宽高,本文是宽1000px,高800px,一般是窗口宽高的,看个人需求吧

<div id="three3d" style="height: 800px;width: 1000px;" />

然后引入我们打算撰写的three.ts文件,这里包含具体初始化和加载逻辑,如果是vue文件需要补充.vue后缀,如果是ts则不用。

import ThreeJsClass from './three'

之后是在onMounted周期里,先创建该类,然后将dom元素的id传递过去,并执行初始化函数

onMounted(() => {
  const theejs = new ThreeJsClass()
  theejs.setDomId('three3d')
  theejs.init()
})

贴个完整的代码

<script setup lang="ts">
import { onMounted } from 'vue'
import ThreeJsClass from './three'
const props = defineProps({
  msg: {
    type: String,
    default: () => { return '测试信息' }
  }
})
onMounted(() => {
  const theejs = new ThreeJsClass()
  theejs.setDomId('three3d')
  theejs.init()
})
console.log(props.msg)
</script>

<template>
  <!-- threejs要绑定的元素,宽1000px,高800px -->
  <div id="three3d" style="height: 800px;width: 1000px;" />
</template>

<style scoped lang="scss">

</style>

3、加载

先看看最终效果,没报错,成功引入图片作为纹理贴到物品上 image.png

下面,首先我已经将model放置到public文件夹

image.png

然后在同级目录,创建three.ts文件,用来放具体的逻辑

基本就是新建场景scene,新建相机camera,创建渲染器render,
相机和渲染器都需要知道尺寸(本文是宽1000px,高800px),
渲染器还需要知道元素dom,才能绑定到一起(这就是前面我额外传递元素id的原因),
创建灯光并添加到场景,不然黑漆漆的也不知道成功没有,
接下来是引入轨道控制器,方便旋转,
最后才是生成模型和调用requestAnimationFrame实时刷新。

直接放源码吧,基本写法没多大差别

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
export default class ThreeJsClass {
  scene: THREE.Scene | null = null;
  camera: THREE.PerspectiveCamera | null = null;
  renderer: THREE.WebGLRenderer | null = null;
  ambientLight: THREE.AmbientLight | null = null;
  mesh: THREE.Mesh | null = null;
  domId: '' | string = '';
  // 需要提前写好变量,才能用this指向到
  constructor() {
    // this.init();
    // 默认执行的,但由于元素id此时未绑定,渲染会出问题,故不用此
  }
  setDomId(id:string):void {
    this.domId = id
    console.log(this.domId)
  }
  init(): void {
    // 第一步新建一个场景
    this.scene = new THREE.Scene()
    this.setCamera() // 设置相机
    this.setLight() // 设置灯光
    this.setRenderer() // 设置渲染器
    this.setControls() // 设置轨道控制器
    this.animate() // 刷新
    this.setCube() // 生成物品
  }

  // 新建透视相机
  setCamera(): void {
    // 第二参数就是 长度和宽度比 我的元素是w:1000px  h:800px  返回以像素为单位的窗口的内部宽度和高度
    this.camera = new THREE.PerspectiveCamera(
      75,
      1000 / 800,
      0.1,
      1000
    );
    this.camera.position.set(2, 1, 3)
    this.camera.lookAt(10, 100, 10)
    this.scene.add(this.camera)
  }

  // 设置渲染器
  setRenderer(): void {
    this.renderer = new THREE.WebGLRenderer();
    // 设置画布的大小
    this.renderer.setSize(1000, 800);
    //这里 其实就是canvas 画布  renderer.domElement
    // document.body.appendChild(this.renderer.domElement);
    if(this.domId) {
      document.getElementById(this.domId).appendChild(this.renderer.domElement)
    }
  }

  // 设置环境光
  setLight(): void {
    if (this.scene) {
      this.ambientLight = new THREE.AmbientLight(0xffffff); // 环境光
      this.scene.add(this.ambientLight);
      // 聚光灯光源
      const dirLight = new THREE.SpotLight(0xffffff)
      dirLight.position.set(0, 0, 100)
      dirLight.intensity = 1.5 // 强度
      dirLight.castShadow = true
      dirLight.shadow.mapSize.width = 2048 // 暗影贴图宽度设置为2048像素
      dirLight.shadow.mapSize.height = 2048 // 暗影贴图高度设置为2048像素
      this.scene.add(dirLight)
      const spotLightHelper = new THREE.SpotLightHelper(dirLight)
      this.scene.add(spotLightHelper)
      // 聚光灯光源
      const dirLight1 = new THREE.SpotLight(0xffffff)
      dirLight1.position.set(100, 0, 100)
      dirLight1.intensity = 1.5 // 强度
      dirLight1.castShadow = true
      dirLight1.shadow.mapSize.width = 2048 // 暗影贴图宽度设置为2048像素
      dirLight1.shadow.mapSize.height = 2048 // 暗影贴图高度设置为2048像素
      this.scene.add(dirLight1)
      // 聚光灯光源
      const dirLight2 = new THREE.SpotLight(0xffffff)
      dirLight2.position.set(-100, 0, 100)
      dirLight2.intensity = 1.5 // 强度
      dirLight2.castShadow = true
      dirLight2.shadow.mapSize.width = 2048 // 暗影贴图宽度设置为2048像素
      dirLight2.shadow.mapSize.height = 2048 // 暗影贴图高度设置为2048像素
      this.scene.add(dirLight2)
      // 聚光灯光源
      const dirLight3 = new THREE.SpotLight(0xffffff)
      dirLight3.position.set(-100, 100, -100)
      dirLight3.intensity = 1.5 // 强度
      dirLight3.castShadow = true
      dirLight3.shadow.mapSize.width = 2048 // 暗影贴图宽度设置为2048像素
      dirLight3.shadow.mapSize.height = 2048 // 暗影贴图高度设置为2048像素
      this.scene.add(dirLight3)
      // 环境光
      const aLight = new THREE.AmbientLight(0xffffff)
      // aLight.position.set(0, 0, 100)
      aLight.intensity = 1.2 // 强度
      aLight.castShadow = true
      aLight.name = 'AmbientLight'
      this.scene.add(aLight)
      // 平行光光
      const DirectionalLight = new THREE.DirectionalLight(0xffffff, 1)
      DirectionalLight.position.set(-10, -10, -21)
      DirectionalLight.intensity = 3 // 强度
      DirectionalLight.castShadow = true
      DirectionalLight.shadow.mapSize.width = 2048 // 暗影贴图宽度设置为2048像素
      DirectionalLight.shadow.mapSize.height = 2048 // 暗影贴图高度设置为2048像素
      this.scene.add(DirectionalLight)
    }
  }

  // 创建网格模型
  setCube(): void {
    if (this.scene) {
      const geometry = new THREE.BoxGeometry(); //创建一个立方体几何对象Geometry
      // const material = new THREE.MeshBasicMaterial({ color: 0xff3200 }); //材质对象Material
      // 默认路径是public,所以下面纹理图片的路径是public/assets/logo.png
      const texture = new THREE.TextureLoader().load(
        "/assets/logo.png"
      ); //首先,获取到纹理
      const material = new THREE.MeshBasicMaterial({ map: texture }); //然后创建一个phong材质来处理着色,并传递给纹理映射
      this.mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
      this.scene.add(this.mesh); //网格模型添加到场景中
      this.render();
    }
  }
  setControls():void {
    const controls = new OrbitControls(this.camera, this.renderer.domElement)
  }

  // 渲染
  render(): void {
    if (this.renderer && this.scene && this.camera) {
      this.renderer.render(this.scene, this.camera);
    }
  }

  // 动画
  animate(): void {
    // console.log(this.scene)
    requestAnimationFrame(this.animate.bind(this));
    this.render();
    if (this.mesh) {
      this.mesh.rotation.x += 0.01;
      this.mesh.rotation.y += 0.01;
    }
  }
}

到此我们能正常生成物品,并引入图片给物品贴图啦!可喜可贺可喜可贺。

看我写了这么多,就给个赞吧,至于加载模型的,等下期我再更新。

我是地霊殿__三無,一个又菜又摸鱼的前端。

ps: 这一篇写了好久,断断续续的,求个赞不过分吧

Snipaste_2022-07-19_15-30-26.jpg