Published on

THREE.js 知识: 实用技巧

Authors
  • avatar
    Name
    王凯
    Twitter

 

1. 投射阴影

renderer.shadowMap.enabled = true

const light = new THREE.PointLight(0xffffff, 40000)
light.castShadow = true
light.shadow.camera.near = 1
light.shadow.camera.far = 160
scene.add(light)
scene.add(new THREE.CameraHelper(light.shadow.camera))

const box = new THREE.Mesh(
  new THREE.BoxGeometry(40, 40, 40), 
  new THREE.MeshNormalMaterial()
)
box.castShadow = true
scene.add(box)

const plane = new THREE.Mesh(
  new THREE.PlaneGeometry(300, 300),
  new THREE.MeshPhysicalMaterial({ color: 'white', side: THREE.DoubleSide })
)
plane.receiveShadow = true
scene.add(plane)

 

2. 粒子系统

技能点描述来源
粒子系统three.quarks3QuarksAngryBird.tsx
粒子系统的特殊行为three.quarks ParticleSystem.addBehavior3QuarksAngryBird.tsx
粒子系统的切图uTileCount vTileCount3QuarksFishBubble.tsx
粒子系统的 EmitterGridEmitter ConeEmitter PointEmitter-
粒子系统的粒子重组TextureSequencer ApplySequences3QuarksRegroup.tsx

3. 标注

技能点描述来源
CSS3DRenderer 渲染将 DOM 节点挂载到 Canvas 元素上3dComputer.tsx
CSS2DRenderer 渲染将 DOM 节点挂载到 Canvas 元素上cameraHelper.tsx
SpriteText 渲染SpriteTextbarChart.tsx
Sprite 渲染THREE.Sprite createCanvas THREE.CanvasTexturecameraHelper.tsx
snowSprite.tsx
Canvas 渲染THREE.CanvasTexturemusicPlayer.tsx

4. 模型相关

技能点描述来源
获取/裁模型尺寸THREE.Box3 setFromObject getCenter<br/>expandByVector expandByObject3QuarksAngryBird.tsx
gameAvoidCar.tsx
碰撞检测THREE.Raycaster3QuarksAngryBird.tsx
后期处理EffectComposer RenderPass3QuarksAngryBird.tsx
raycaster.tsx
颜色渐变THREE.BufferAttributehistogram.tsx
贴花THREE.Eulerbase.tsx
向量夹角点积vectorA.dot(vectorB)vectorDotProduct.tsx

6. 动画

技能点描述来源
GLTF 模型的骨骼动画THREE.AnimationMixer3QuarksFishBubble.tsx
自定义骨骼动画THREE.Bone THREE.SkeletonHelperskeletonAnimation.tsx
Tween 过渡动画Tween tweenA.chain(tweenB)3QuarksFishBubble.tsx
混合动画THREE.KeyframeTrack THREE.AnimationClipdistortedAnimation.tsx

7. Cannon 引擎

技能点描述来源
Cannon 引擎模拟规则物体CANNON.Worldcannon.tsx
Cannon 引擎模拟不规则物体CANNON.ConvexPolyhedroncannon.tsx
未完成-
已完成Cannon 引擎模拟规则物体、Cannon 引擎模拟不规则物体
const world = new CANNON.World()
world.gravity.set(0, -200, 0)

/**
 * 平面
 */
const plane = new THREE.Mesh(
  new THREE.PlaneGeometry(300, 300),
  new THREE.MeshPhysicalMaterial({ color: 'white', side: THREE.DoubleSide })
)
plane.rotateX(-Math.PI / 2)
scene.add(plane)
// cannon 模拟
const planeCannonMaterial = new CANNON.Material()
const planeCannonBody = new CANNON.Body({
  mass: 0, // 创建一个无质量(mass=0)的物理平面
  material: planeCannonMaterial,
  shape: new CANNON.Plane(),
})
planeCannonBody.position.set(0, 0, 0)
planeCannonBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
world.addBody(planeCannonBody)

/**
 * 规则球体
 */
const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(16, 16, 16),
  new THREE.MeshNormalMaterial()
)
sphere.position.set(0, 100, 0)
scene.add(sphere)
// cannon 模拟
const sphereCannonMaterial = new CANNON.Material()
const sphereCannonBody = new CANNON.Body({
  mass: 1, // 有质量(mass=1),可参与运动模拟
  shape: new CANNON.Sphere(16),
  material: sphereCannonMaterial,
})
sphereCannonBody.position.set(0, 100, 0)
world.addBody(sphereCannonBody)
world.addContactMaterial(
  new CANNON.ContactMaterial(planeCannonMaterial, sphereCannonMaterial, {
    friction: 0.2, // 摩擦力
    restitution: 0.66, // 弹性
  })
)

/**
 * 不规则球体
 */
const xSphere = new THREE.Mesh(
  new THREE.SphereGeometry(16, 5),
  new THREE.MeshNormalMaterial()
)
xSphere.position.set(0, 100, 40)
scene.add(xSphere)
// cannon 模拟
const vertices: CANNON.Vec3[] = []
const faces: number[][] = []
// position 用来描述几何体中每一个顶点的三维位置, 提供物理体顶点
const ps = xSphere.geometry.attributes.position
for (let i = 0; i < ps.count; i++) {
  vertices.push(new CANNON.Vec3(ps.getX(i), ps.getY(i), ps.getZ(i)))
}
// index 是一个索引数组,告诉 WebGL 如何把顶点组成三角面, 提供三角面结构
const index = xSphere.geometry.index! as unknown as number[]
for (let i = 0; i < index.length; i++) {
  faces.push([index[i], index[i + 1], index[i + 2]])
}
const xSphereCannonMaterial = new CANNON.Material()
const xSphereCannonBody = new CANNON.Body({
  mass: 1,
  shape: new CANNON.ConvexPolyhedron({ vertices, faces }),
  material: xSphereCannonMaterial,
})
xSphereCannonBody.position.set(0, 100, 40)
world.addBody(xSphereCannonBody)
world.addContactMaterial(
  new CANNON.ContactMaterial(planeCannonMaterial, xSphereCannonMaterial, {
    friction: 0.2, // 摩擦力
    restitution: 0.66, // 弹性
  })
)

function r() {
  world.fixedStep()
  if (sphere) {
    sphere.position.copy(sphereCannonBody.position)
    sphere.position.copy(sphereCannonBody.position)
    sphere.quaternion.copy(sphereCannonBody.quaternion)
  }
  if (xSphere) {
    xSphere.position.copy(xSphereCannonBody.position)
    xSphere.position.copy(xSphereCannonBody.position)
    xSphere.quaternion.copy(xSphereCannonBody.quaternion)
  }

  renderer.render(scene, camera)
  requestAnimationFrame(r)
}
requestAnimationFrame(r)

 

8. 浏览器相关

未完成-
已完成页面截屏、页面录屏、帧率检测
const downloadBlob = (blob, filename) => {
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
}

const renderer = new THREE.WebGLRenderer({
  antialias: true,
  // 在每一帧渲染完成之后,保留绘图缓冲区的内容
  preserveDrawingBuffer: true,
})

const stats = new Stats()
stats.dom.style.position = 'absolute'
c.appendChild(stats.dom)

const orbitControls = new OrbitControls(camera, renderer.domElement)

const x = {
  downloadImage() {
    renderer.domElement.toBlob((blob) => {
      if (blob) {
        downloadBlob(blob, 'browser.png')
      }
    }, 'image/png')
  },
  onDownloadVideo() {
    const stream = renderer.domElement.captureStream(60)
    const mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' })
    orbitControls.autoRotate = true
    orbitControls.autoRotateSpeed = 8
    mediaRecorder.start()
    mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        downloadBlob(event.data, 'browser.webm')
      }
    }
    setTimeout(() => {
      orbitControls.autoRotate = false
      orbitControls.reset()
      mediaRecorder.stop()
    }, 5000)
  },
}

const gui = new GUI()
gui.add(x, 'downloadImage').name('截屏')
gui.add(x, 'onDownloadVideo').name('录屏')
gui.domElement.style.position = 'absolute'
c.appendChild(gui.domElement)

function r() {
  stats.update()
  orbitControls.update()
  renderer.render(scene, camera)
  requestAnimationFrame(r)
}
requestAnimationFrame(r)

 

9. 音乐数据处理

技能点描述来源
加载音乐AudioListenerdancerBattle.tsx
音乐频谱数据处理逻辑THREE.AudioAnalyser.getFrequencyDatamusicPlayer.tsx

10. 第三方库

技能点描述来源
噪音SimplexNoisefireworm.tsx
噪音控制点云simplex-noiserandomMountain.tsx
JSX 风格@react-three/fiberr3f.tsx

11. helper

技能点描述来源
CameraHelpercameraHelper.tsx
SpotLightHelperdancerBattle.tsx
PointLightHelper
Box3Helper包括渲染、位置更新gameAvoidCar.tsx
SkeletonHelperskeletonAnimation.tsx
ArrowHelpervectorDotProduct.tsx