一、引入threejs依赖包

1. 终端安装three依赖包

npm i three -S

2. 组件引入threejs

import * as THREE from 'three';

二、创建threejs容器

<template>

  <div>

    <canvas id="three"></canvas>

  </div>

</template>

三、创建threejs应用场景

三要素:scene,render,carmea

1.创建scene

const scene = new THREE.Scene();

scene可以理解为我们将要渲染的环境、背景

scene.background = new THREE.Color("#eee");

2. 获取threejs的容器(render)

const threeDemo = document.getElementById("three");

创建WebGLRenderer,将容器配置参数传入

const renderer = new THREE.WebGLRenderer({canvas: threeDemo, antialias: true});

3.创建threejs的相机(carmea)

常用两种相机实例:

        PerspectiveCamera(透视摄像机): 模拟人眼所看到的景象,物体的大小会受远近距离的影响,它是3D场景的渲染中使用得最普遍的投影模式。

OrthographicCamera(正交投影摄像机): 不具有透视效果,即物体的大小不受远近距离的影响;

  const camera = new THREE.PerspectiveCamera(
    30, 
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  )

PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )具有四个参数:

  • fov — 摄像机视锥体垂直视野角度。可以理解为人类的视野广度。
  • aspect — 摄像机视锥体横纵比。渲染结果的横向尺寸和纵向尺寸的比值,这里使用的是 浏览器窗口的宽高比。
  • near — 摄像机视锥体近端面。一切比近面更近的事物将不被渲染。
  • far — 摄像机视锥体远端面。一切比远面更远的事物将不被渲染,但是设置过大可能会影响性能。

生成的camera默认是放在中心点(0,0,0)的,但这是待会模型要放的位置,因此,我们把摄像机挪个位置:

camera.position.z = 10

4. Three.js 需要一个动画循环函数,Three.js 的每一帧都会执行这个函数。

function annimate(scene, renderer, camera) {
  renderer.render(scene, camera);
  requestAnimationFrame(annimate);
}

现在生成了一个灰色背景没有任何物体的threejs场景,vue代码:

<template>
  <div>
    <canvas id="three"></canvas>
  </div>
</template>

<script setup>
import * as THREE from 'three';
import { onMounted } from "vue";

function initThree() {
  const scene = new THREE.Scene();
  scene.background = new THREE.Color("#eee");

  const threeDemo = document.getElementById("three");
  const renderer = new THREE.WebGLRenderer({canvas: threeDemo, antialias: true});
  const camera = new THREE.PerspectiveCamera(
    30, 
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  )
  camera.position.z = 10;

  function annimate() {
    renderer.render(scene, camera);
    requestAnimationFrame(annimate);
  }
  annimate()
};



onMounted(() => {
  initThree();
})
</script>

<style scoped lang="less">
#three {
  width: 100vw;
  height: 100vh;
  position: absolute;
  top: 0;
  left: 0;
}
</style>

四、引入3D模型

1. 引入threejs加载3d模型的js文件
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

可根据不同的3D模型文件格式引入不同的加载文件

  • fbx - import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
  • obj - import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
  • collada - import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader";

  • gltf - import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

2. 获取3D模型文件

获取sketchfab3D模型文件链接icon-default.png?t=N7T8https://sketchfab.com/

下载好后,解压,放进项目文件的public目录。

3. 声明一个加载器,加载我们下载的模型,并把它添加到场景中
  const gltfLoader = new GLTFLoader();
  gltfLoader.load("/seraphine/scene.gltf", (gltf) => {
    let model = gltf.scene;
    scene.add(model);
  })

场景里有了模糊的黑色的小人,这是因为我们还没有给她添加纹理。

4. 使用threejs给3d模型添加纹理
  gltfLoader.load("/seraphine/scene.gltf", (gltf) => {
    let model = gltf.scene;
    // 添加以下代码
    // 遍历模型
    model.traverse((obj) => {
      // 将图片作为纹理加载
      let imgTexture = new THREE.TextureLoader().load("/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png");
      // 调整纹理图的方向
      imgTexture.flipY = false;
      // 将纹理图生成材质
      const material = new THREE.MeshBasicMaterial({
        map: imgTexture,
      })
      obj.material = material;
    })

    scene.add(model);
  })

此时,小人就带了颜色了

模糊原因:原因是设备的物理像素分辨率与CSS像素分辨率的比值的问题,我们的canvas绘制出来后图片因为高清屏设备的影响,导致图片变大,然而我们在浏览器的渲染窗口并没有变大,因此图片会挤压缩放使得canvas画布会变得模糊。

修改它我们要用到devicePixelRatio这个属性(此属性返回当前显示设备的物理像素分辨率与CSS像素分辨率的比值。该值也可以被解释为像素大小的比例:即一个CSS像素的大小相对于一个物理像素的大小的比值。)

添加函数:

  function resizeDevicePixel(renderer) {
    const canvas = renderer.domElement
        let width = window.innerWidth
        let height = window.innerHeight
        let devicePixelWidth = canvas.width / window.devicePixelRatio
        let devicePixelHeight = canvas.height / window.devicePixelRatio

        const needResize = devicePixelWidth !== width || devicePixelHeight !== height
        if (needResize) {
          renderer.setSize(width, height, false)
        }
        return needResize
  }

在animate函数内调用它:

  function annimate() {
    renderer.render(scene, camera);
    requestAnimationFrame(annimate);

    // 添加以下代码
    if(resizeDevicePixel(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }
  }

5. 添加轨道控制器 让模型动起来

引入轨道控制器

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

创建轨道控制变量

 const controls = new OrbitControls(camera, renderer.domElement);

在annimate函数调用(要写在最前面)

    controls.update()

现在拉近可以看见近脸

五、添加光与影子

先加个地板,Three.js里物体(一般叫网格Mesh)由两部分构成,一是它的形状,二是它的材质,我们给地板创建它们:

  let floorGeometry = new THREE.PlaneGeometry(3000, 3000)
  let floorMaterial = new THREE.MeshPhongMaterial({ color: "#7e7ab0" })

平面几何体,PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer)

  • width — 平面沿着X轴的宽度。默认值是1。
  • height — 平面沿着Y轴的高度。默认值是1。
  • widthSegments — (可选)平面的宽度分段数,默认值是1。
  • heightSegments — (可选)平面的高度分段数,默认值是1。

Phong网格材质(MeshPhongMaterial):是一种用于具有镜面高光的光泽表面的材质。

生成Mesh,并添加到场景中:

  let floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
  floorMesh.rotation.x = -0.5 * Math.PI;
  floorMesh.receiveShadow = true;
  floorMesh.position.y = -0.001;
  scene.add(floorMesh);

现在还是黑的,还需要加光

添加平行光:

  const dirLight = new THREE.DirectionalLight(0xffffff, 0.6)
  //光源等位置
  dirLight.position.set(-10, 8, -5)
  //可以产生阴影
  dirLight.castShadow = true
  dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024)
  scene.add(dirLight)

平行光一般用来模拟太阳光,DirectionalLight( color : Integer, intensity : Float )

  • color - (可选参数) 16进制表示光的颜色。 缺省值为 0xffffff (白色)。
  • intensity - (可选参数) 光照的强度。缺省值为1。

添加半球光光源:

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

半球光光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。

  • skyColor - (可选参数) 天空中发出光线的颜色。 缺省值 0xffffff。
  • groundColor - (可选参数) 地面发出光线的颜色。 缺省值 0xffffff。
  • intensity - (可选参数) 光照强度。 缺省值 1。

想要产生影子,还需要在renderer下添加:

  const renderer = new THREE.WebGLRenderer({ canvas: threeDemo, antialias: true });
  // ++++++
  renderer.shadowMap.enabled = true;

以及:

    model.traverse((obj) => {
      // 将图片作为纹理加载
      let imgTexture = new THREE.TextureLoader().load("/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png");
      // 调整纹理图的方向
      imgTexture.flipY = false;
      // 将纹理图生成材质
      const material = new THREE.MeshBasicMaterial({
        map: imgTexture,
      })
      obj.material = material;

      //加这句,让模型等每个部分都能产生阴影
      if (obj.isMesh) {
        obj.castShadow = true
        obj.receiveShadow = true
      }
    })

给场景添加雾化效果:

  const scene = new THREE.Scene();
  scene.background = new THREE.Color("#eee");
  // +++++
  scene.fog = new THREE.Fog('#eee', 20, 100)

最终效果:

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐