three.js基础概念

58 阅读4分钟

基础概念

  • 图元 指的是3D的形状,比如盒子(BoxGeometry),圆柱(CylinderGeometry)等。除了这些可以直接使用的形状外,还可以使用BufferGeometry自定义形状
    const objects = [];

   // 一球多用
   const radius = 1;
   const widthSegments = 6;
   const heightSegments = 6;
   const sphereGeometry = new THREE.SphereGeometry(
     radius,
     widthSegments,
     heightSegments
   );
  • 材质 前面物体的材质,有很多种;MeshBasicMaterial 不受光照的影响。MeshLambertMaterial 只在顶点计算光照,而 MeshPhongMaterial 则在每个像素计算光照。MeshPhongMaterial 还支持镜面高光....
       const sunMaterial = new THREE.MeshPhongMaterial({ emissive: 0xffff00 });
    
  • 层次 将形状和材质进行结合
        const mesh = new THREE.Mesh(sphereGeometry, sumMaterial)
    
  • 场景 3D引擎中一个图中节点的层次结构,代表一个局部空间 const scene = new THREE.Scene() scene.add(sunMesh);
  • 摄像机 PerspectiveCamera,提供一个近大远小的3d视觉效果,near视锥的前端,far视锥的后端,for视野角度,aspect视锥前端和后端宽度,

image.png

const camera2 = new THREE.PerspectiveCamera(
  60,  // fov
  2,   // aspect
  0.1, // near
  500, // far
);
camera2.position.set(40, 10, 30);
camera2.lookAt(0, 5, 0);
 
  • 渲染目标 THREE.WebGLRenderer,需要传入dom等参数
const renderer = new THREE.WebGLRenderer({ 
  canvas: canvas, 
  antialias: true, // 开启抗锯齿
  alpha: true      // 开启背景透明
})

renderer.render(scene, camera)
// window.requestAnimationFrame(tick)来实现循环渲染,const tick = () => {renderer.render(scene, camera)}
  • 轨道控制器,使得可以通过鼠标移动3d图形
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true // 开启阻尼感(平滑旋转)
controls.dampingFactor = 0.05
// 限制缩放范围
controls.minDistance = 2
controls.maxDistance = 10
  • 坐标 在页面上显示x y z轴
    • 红色:X轴(向右)
    • 绿色:Y轴(向上)
    • 蓝色:Z轴(向前/向屏幕外)
// 添加坐标轴辅助工具
const axesHelper = new THREE.AxesHelper(5); // 参数表示坐标轴长度
scene.add(axesHelper);

// 或者指定不同长度
const axesHelper = new THREE.AxesHelper(1);
scene.add(axesHelper);
  • 光照 增加一些环境光,半球光等
   const color = 0xFFFFFF;
   const intensity = 1;
   const light = new THREE.AmbientLight(color, intensity);
   scene.add(light);
  • 加载器
    • 图元加载器,各种图元模型gltf模型或者glb(glb是gltf的二进制文件)
      import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
      const gltfLoader = new GLTFLoader()
      // 加载甜甜圈模型
      gltfLoader.load(require('xxx'))
      
    • 材料加载器,比如贴片图片,创建材料等纹理节点
    const material = new TextureLoad(require('XXX'))
    
  • 设置渲染大小为整个屏幕
const getCanvasSize = () => {
 const parent = canvas.parentElement
 return {
   width: parent?.clientWidth || window.innerWidth,
   height: parent?.clientHeight || window.innerHeight
 }
}
let sizes = getCanvasSize()
render.setSize(siezs.width, sizes.height)
// 设置像素比,防止在高分屏上模糊
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
  • gui 允许在页面上直接调参的工具
  • 压缩加载器,draco glb是gltf压缩后的模型,那要如何解压呢
        const dracoLoader = new DRACOLoader()
        dracoLoader.setDecoderPath('./draco/') // 这里通常将dracoLoader这个库和模型放一起
        const gltfLoader = new GLTFLoader()
        gltfLoader.setDRACOLoader(dracoLoader)
        glefLoader.load('模型',(gltf) => {
            const model = gltf.scene
            scene.add(model)
        })
    
  • 纹理加载
   let rgbeLoader = new RGBELoader();
   rgbeLoader.load('./XXX/sky.hdr', (texture) => {
       texture.mapping = THREE.EquiretangularReflectionMapping; //设置这个纹理为球形结构
       scene.background = texture;
       scene.enviroment = texture;
   })

写一个完整的例子

'use client'

import { useEffect } from 'react'
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import GUI from 'lil-gui'

const Page = () => {
 const init = () => {
   // 1. 获取 Canvas 元素
   const canvas = document.querySelector('canvas.webgl') as HTMLCanvasElement | null
   if (!canvas) return

   // 2. 创建场景 (Scene)
   const scene = new THREE.Scene()

   // 2.1 初始化 GUI
   const gui = new GUI()
   gui.close() // 默认关闭
   const debugObject = {
     wireframe: true
   }

   // 3. 模型加载器 (GLTFLoader)
   const gltfLoader = new GLTFLoader()
   let donut: THREE.Object3D | null = null

   // 加载甜甜圈模型
   // 注意:这里使用 /api/static 代理路径来绕过 Next.js 对 .gltf 文件的模块导入限制
   gltfLoader.setPath('/api/static/assets/donut/')
   gltfLoader.load(
     'scene.gltf',
     (gltf) => {
       donut = gltf.scene as THREE.Object3D
       
       // 更新模型线框模式的函数
       const updateWireframe = () => {
         if (!donut) return
         donut.traverse((child) => {
           if ((child as THREE.Mesh).isMesh) {
             const mesh = child as THREE.Mesh
             const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
             materials.forEach((m: THREE.Material) => {
               if ('wireframe' in m) {
                 ;(m as THREE.MeshStandardMaterial).wireframe = debugObject.wireframe
               }
             })
           }
         })
       }

       // 初始化线框状态
       updateWireframe()

       // 添加 GUI 控制
       gui.add(debugObject, 'wireframe')
         .name('显示线性')
         .onChange(updateWireframe)

       const radius = 8.5
       // 设置初始位置
       donut.position.x = 0
       // 设置模型缩放倍数
       donut.scale.set(radius, radius, radius)
       scene.add(donut)

     },
     undefined,
     (error) => { console.error('模型加载失败:', error) }
   )

   // 4. 灯光设置 (Lights)
   // 环境光:提供基础亮度,确保暗部不会完全变黑
   const ambientLight = new THREE.AmbientLight(0xffffff, 1.2)
   scene.add(ambientLight)

   // 平行光:模拟太阳光,产生阴影和立体感
   const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
   directionalLight.position.set(2, 4, 1)
   directionalLight.castShadow = true // 开启阴影投影
   scene.add(directionalLight)

   // 5. 视口大小设置
   const getCanvasSize = () => {
     const parent = canvas.parentElement
     return {
       width: parent?.clientWidth || window.innerWidth,
       height: parent?.clientHeight || window.innerHeight
     }
   }
   let sizes = getCanvasSize()

   // 6. 相机设置 (Camera)
   // 使用透视相机 (PerspectiveCamera)
   const camera = new THREE.PerspectiveCamera(35, sizes.width / sizes.height, 0.1, 1000)
   camera.position.z = 5 // 相机后移,以便看清场景
   camera.lookAt(0, 0, 0)
   scene.add(camera)

   // 7. 渲染器设置 (Renderer)
   const renderer = new THREE.WebGLRenderer({ 
     canvas: canvas, 
     antialias: true, // 开启抗锯齿
     alpha: true      // 开启背景透明
   })
   renderer.shadowMap.enabled = true // 开启阴影渲染
   renderer.shadowMap.type = THREE.PCFSoftShadowMap // 设置柔和阴影
   renderer.setSize(sizes.width, sizes.height)
   // 设置像素比,防止在高分屏上模糊
   renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

   // 7.1 轨道控制器 (OrbitControls)
   const controls = new OrbitControls(camera, canvas)
   controls.enableDamping = true // 开启阻尼感(平滑旋转)
   controls.dampingFactor = 0.05
   // 限制缩放范围
   controls.minDistance = 2
   controls.maxDistance = 10

   // 8. 动画循环 (Animation Loop)
   const clock = new THREE.Clock()
   const tick = () => {
     const elapsedTime = clock.getElapsedTime()

     if (donut) {
       // 设定基础倾斜角度,使其正面朝向屏幕并有立体感
       const baseRotationX = 0.8
       const baseRotationY = 0.8
       
       // 动态计算:基础角度 + 正弦波晃动,实现轻微的浮动效果
       donut.rotation.y = baseRotationY + Math.sin(elapsedTime * 0.4) * 0.1
       donut.rotation.x = baseRotationX + Math.sin(elapsedTime * 0.2) * 0.05
       // Y 轴位置动态浮动
       donut.position.y = Math.sin(elapsedTime * 0.5) * 0.1
     }

     // 更新控制器(如果开启了 enableDamping,必须在每一帧调用)
     controls.update()

     // 执行渲染
     renderer.render(scene, camera)
     // 请求下一帧动画
     window.requestAnimationFrame(tick)
   }

   const handleResize = () => {
     sizes = getCanvasSize()
     
     // 更新相机
     camera.aspect = sizes.width / sizes.height
     camera.updateProjectionMatrix()

     // 更新渲染器
     renderer.setSize(sizes.width, sizes.height)
     renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
   }

   window.addEventListener('resize', handleResize)
   tick()

   // 返回清理函数,销毁 GUI 和事件监听
   return () => {
     gui.destroy()
     window.removeEventListener('resize', handleResize)
   }
 }

 useEffect(() => {
   const cleanup = init()
   return () => {
     if (cleanup) cleanup()
   }
 }, [])

 return (
   <div className="min-h-screen w-full flex flex-col lg:flex-row bg-[#fdf2f8]">
       <canvas className="webgl w-full h-full block"></canvas>
   </div>
 )
}

export default Page