Напишите анимацию дождя с помощью three.js

WebGL three.js
Напишите анимацию дождя с помощью three.js

Недавно я прочитал «Руководство по разработке Three.js» и глубоко осознал, что просто смотреть и не практиковаться — это почти то же самое, что не смотреть, поэтому я попрактиковался и написал эту небольшую анимацию.

адрес проекта:GitHub.com/Alxa О Лала/Он…

Предварительное знание

WebGL позволяет нам разрабатывать 3D-приложения в браузере, однако программировать напрямую с помощью WebGL довольно сложно, поэтому разработчикам необходимо знать основные детали WebGL и изучать сложные языки затенения, чтобы получить большинство функций WebGL. Three.js предоставляет ряд очень простых API-интерфейсов JavaScript, связанных с функциями WebGL, чтобы разработчики могли легко создавать красивую трехмерную графику. На официальном сайте Three.js есть многоКлассный 3D эффект.

Использование Three.js для разработки 3D-приложения обычно включает средство визуализации, сцену, камеру, а также объекты и источники света, которые вы создаете в сцене.

Представьте себе ситуацию фотографирования, нам нужна сцена (Scene), в которой размещается объект для фотографирования, задается среда освещения, положение и ориентация камеры (Camera), после чего изображение может быть взятый. Рендерер (Renderer) может быть больше похож на фотографа, отвечающего за отдачу приказов о съемке и создание изображений (фотографий).

Скопируйте и запустите следующий код, чтобы получить очень простую 3D-сцену.

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>room</title>
</head>
<body>
  <div id="webgl-output"></div>
  <script src="https://unpkg.com/three@0.119.0/build/three.js"></script>
  <script>
    function init () {
      const scene = new THREE.Scene()

      const camera = new THREE.PerspectiveCamera(45, 
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      )
      camera.position.set(-30, 40, 30)
      camera.lookAt(0,0,0)
      scene.add(camera) 

      const planeGeometry = new THREE.PlaneGeometry(60,20)
      const planeMaterial = new THREE.MeshLambertMaterial({
        color: 0xAAAAAA
      })  
      const plane = new THREE.Mesh(planeGeometry, planeMaterial)
      plane.rotation.x = -Math.PI / 2
      plane.position.set(15, 0, 0)
      scene.add(plane)

      const sphereGeometry = new THREE.SphereGeometry(4, 20, 20)
      const sphereMaterial = new THREE.MeshLambertMaterial({
        color: 0xffff00
      })
      const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
      sphere.position.set(20, 4, 2)
      scene.add(sphere)

      const spotLight = new THREE.SpotLight(0xffffff)
      spotLight.position.set(-20, 30, -15)
      scene.add(spotLight)

      const renderer = new THREE.WebGLRenderer()
      renderer.setClearColor(new THREE.Color(0x000000))
      renderer.setSize(window.innerWidth, window.innerHeight)
      document.getElementById('webgl-output').appendChild(renderer.domElement)

      renderer.render(scene, camera)
    }

    init()
</script>
</body>
</html>

Место действия

Объект THREE.Scene является контейнером для всех разных объектов, но сам этот объект не имеет особо сложных операций.Обычно мы создаем экземпляр сцены в начале программы, а затем добавляем в сцену камеры, объекты и источники света.

const scene = new THREE.Scene()
scene.add(camera)        //添加照相机
scene.add(plane)         //添加灰色平面
scene.add(sphere)        //添加黄色球体
scene.add(spotLight)     //添加光源

Камера

Библиотека Three.js предоставляет две разные камеры: камеры перспективной проекции и камеры ортогональной проекции.

透视投影照相机的效果类似人眼在真实世界中看到的场景,有 "近大远小" 的效果,垂直视平面的平行线在远方会相交。

正交投影照相机的效果类似我们在数学几何学课上老师教我们画的效果,在三维空间内平行的线,在屏幕上永远不会相交。

Здесь мы используем перспективную проекционную камеру, и мы в основном обсудим ее, а ортогональную проекционную камеру будем использовать позже.

const camera = new THREE.PerspectiveCamera(
  45, 
  window.innerWidth / window.innerHeight,
  0.1,
  1000
)
camera.position.set(-30, 40, 30)
camera.lookAt(0,0,0)
scene.add(camera) 

Настройка камеры состоит из трех шагов:Определить поле зрения, определить координаты камеры, определить точку фокусировки камеры.

image.pngмы вnew THREE.PerspectiveCameraПри определении поля зрения камеры, соответствующей приведенному выше рисунку, принимается 45 fov, представляющее собой угол между верхним и нижним краями поля зрения.window.innerWidth / window.innerHeight- отношение длины горизонтального направления к вертикальному направлению поля зрения. 0,1 (ближний) и 1000 (дальний) - ближнее и дальнее расстояние от камеры до поля зрения соответственно. Эти параметры определяют диапазон отображаемого трехмерного пространства, то есть верхняя серая область на рисунке.

camera.position.set(-30, 40, 30)Определяются координаты камеры в пространстве.

camera.lookAt(0,0,0)Определяется точка фокусировки камеры, а линия, соединяющая эту точку и координаты камеры, является направлением съемки.

Эффект отображения серой области на приведенном выше рисунке на экране, то есть проекция координат трехмерного пространства на двухмерные координаты экрана, осуществляется webgl, нам нужно только позаботиться о координаты трехмерного пространства.

Система координат

с тем, что мы сказали раньше3D система координат для CSSОтличие состоит в том, что система координат webgl является правосторонней системой координат, ось X направлена ​​вправо, ось Y направлена ​​вверх, а ось Z указывает на «я».

伸出右手,让拇指和食指成"L"形,大拇指向右,食指向上。其余的手指指向自己,这样就建立了一个右手坐标系。

其中,拇指、食指和其余手指分别代表x,y,z轴的正方向

Позиционирование и перемещение в пространстве понять легче.Давайте рассмотрим здесь вращение.

Иногда мы устанавливаем вращение объекта следующим образом:object.rotation.x = -Math.PI / 2, что соответствует повороту на -90 градусов вокруг оси X. Конкретно как крутить, надо сравнивать вышеуказанную систему координат,Разверните правую руку, большой палец указывает на положительное направление оси X, и направление изгиба оставшихся пальцев является положительным направлением вращения; большой палец указывает на отрицательное направление оси X и направление изгиба Из остальных пальцев - отрицательное направление вращения. Суждение направления вращения оси Y и ось Z одинаково.

объект

В three.js для создания объекта требуются два параметра: Geometry и Material. Говоря простым языком, геометрия определяет форму объекта, а материал определяет цвет поверхности, текстурную карту, реакцию на освещение и так далее.

//创建一个平面几何体,参数是沿X方向的Width和沿Y方向的height
const planeGeometry = new THREE.PlaneGeometry(60,20)  

//创建一种材质,MeshLambertMaterial是一种考虑漫反射而不考虑镜面反射的材质
const planeMaterial = new THREE.MeshLambertMaterial({
  color: 0xAAAAAA
})  

//根据几何形状和材质创建物体
const plane = new THREE.Mesh(planeGeometry, planeMaterial)

//设置物体的位置和旋转,并将物体加到场景(scene)中
plane.rotation.x = -Math.PI / 2
plane.position.set(15, 0, 0)
scene.add(plane)

освещение

Без источников света отрендеренная сцена не будет видна (если только вы не используете базовые или каркасные материалы, которые, конечно, редко используются при создании 3D-приложений).

WebGL изначально не поддерживает источники света. Если вы не используете Three.js, вам нужно написать собственный шейдер WebGL для имитации источника света. Three.js упрощает работу с источниками света.

const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(0, 0, 100)
scene.add(spotLight)

Как показано выше, нам просто нужно создать источник света и добавить его в сцену. three.js рассчитает эффект отображения каждого объекта в сцене в соответствии с типом и положением источника света.

Наиболее часто используемые источники света: AmbientLight, PointLight, SpotLight, DirectionalLight.image.png

Рендерер

Когда камера, объекты, источники света и т. д. в сцене готовы, пришло время вступить в игру рендереру.

В небольшом примере выше мы используем средство визуализации следующим образом:

//new 一个渲染器
const renderer = new THREE.WebGLRenderer()

//设置画布背景色,也就是画布中没有物体的地方的显示颜色
renderer.setClearColor(new THREE.Color(0x000000))

//设置画布大小
renderer.setSize(window.innerWidth, window.innerHeight)

//将画布元素(即renderer.domElement,它是一个canvas元素)挂载到一个dom节点
document.getElementById('webgl-output').appendChild(renderer.domElement)

//执行渲染操作,参数是上面定义的场景(scene)和照相机(camera)
renderer.render(scene, camera)

Видно, что при использовании Three.js для разработки 3D-приложений нам нужно заботиться только о расположении объектов, камер, освещения и т. д. в сцене в трехмерном пространстве, а также о движении и способах его рендеринга. выполняется Three.js. Конечно, лучше понимать некоторые базовые принципы webgl, ведь некоторые приложения будут настолько сложными, что API three.js не сможет соответствовать требованиям.

Реализовать анимацию дождя

Инициализировать сцену

Поскольку инициализация каждого 3D-приложения включает в себя сцену, камеру и рендеринг, мы инкапсулируем инициализацию этих трех в шаблон класса, а последующая инициализация приложения может наследовать этот класс через подклассы для быстрого создания каркаса.

import {
  Scene,
  PerspectiveCamera,
  WebGLRenderer,
  Vector3,
  Color
} from 'three'

export default class Template {
  constructor () {               //各种默认选项
    this.el = document.body
    this.PCamera = {
      fov: 45,
      aspect: window.innerWidth / window.innerHeight,
      near: 1,
      far: 1000
    }
    this.cameraPostion = new Vector3(0, 0, 1)
    this.cameraLookAt = new Vector3(0,0,0)
    this.rendererColor = new Color(0x000000)
    this.rendererWidth = window.innerWidth
    this.rendererHeight = window.innerHeight
  }

  initPerspectiveCamera () {     //初始化相机,这里是透视相机
    const camera = new PerspectiveCamera(
      this.PCamera.fov,
      this.PCamera.aspect,
      this.PCamera.near,
      this.PCamera.far,
    )
    camera.position.copy(this.cameraPostion)
    camera.lookAt(this.cameraLookAt)
    this.camera = camera
    this.scene.add(camera)
  }

  initScene () {                //初始化场景
    this.scene = new Scene() 
  }

  initRenderer () {             //初始化渲染器
    const renderer = new WebGLRenderer()
    renderer.setClearColor(this.rendererColor)
    renderer.setSize(this.rendererWidth, this.rendererHeight)
    this.el.appendChild(renderer.domElement)
    this.renderer = renderer
  }

  init () {
    this.initScene()
    this.initPerspectiveCamera()
    this.initRenderer()
  }
}

В нашей анимации дождя создайте класс Director для управления анимацией, которая наследуется от класса Template. Видно, что то, что он должен сделать, предельно ясно: инициализировать фреймворк, изменить стандартную конфигурацию родительского класса, добавить объекты (облака и капли дождя), добавить освещение (молния тоже формируется освещением), добавить эффекты тумана, и цикл рендеринга.

//director.js
export default class Director extends Template{
  constructor () {
    super()

    //set params
    //camera
    this.PCamera.fov = 60       //修改照相机的默认视场fov

    //init camera/scene/render
    this.init()
    this.camera.rotation.x = 1.16   //设置照相机的旋转角度(望向天空)
    this.camera.rotation.y = -0.12
    this.camera.rotation.z = 0.27

    //add object
    this.addCloud()                  //添加云层和雨滴
    this.addRainDrop()

    //add light
    this.initLight()                //添加光照,用PointLight模拟闪电
    this.addLightning()
    
    //add fog
    this.addFog()                   //添加雾,在相机附近视野清晰,距离相机越远,雾的浓度越高

    //animate
    this.animate()                 //requestAnimationFrame实现动画
  }
}

Создавайте постоянно меняющиеся слои облаков

Сначала создаем плоскость, используем в качестве материала небольшое облачко и получаем облачный объект. Затем сложите много облачных объектов, чтобы получить облако.

image.png

//Cloud.js
const texture = new TextureLoader().load('/images/smoke.png')  //加载云朵素材
const cloudGeo = new PlaneBufferGeometry(564, 300)   //创建平面几何体
const cloudMaterial = new MeshLambertMaterial({   //图像作为纹理贴图,生成材质
  map: texture,
  transparent: true
})
export default class Cloud {
  constructor () {      
    const cloud = new Mesh(cloudGeo, cloudMaterial)   //生成云朵物体
    cloud.material.opacity = 0.6
    this.instance = cloud
  }

  setPosition (x,y,z) {
    this.instance.position.set(x,y,z)
  }

  setRotation (x,y,z) {
    this.instance.rotation.x = x
    this.instance.rotation.y = y
    this.instance.rotation.z = z
  }

  animate () {
    this.instance.rotation.z -= 0.003            //云朵的运动是不断绕着z轴旋转
  }
}

В классе Director генерируются 30 облачных объектов, а их положения и повороты задаются случайным образом для формирования эффекта растекания и каскадирования. Вызовите метод анимации облачного объекта во время цикла рендеринга.

//director.js
addCloud () {
  this.clouds = []
  for(let i = 0; i < 30; i++){
    const cloud = new Cloud()
    this.clouds.push(cloud)
    cloud.setPosition(Math.random() * 1000 - 460, 600, Math.random() * 500 - 400)
    cloud.setRotation(1.16, -0.12, Math.random() * 360)
    this.scene.add(cloud.instance)
  }
}
animate () {
    //cloud move
    this.clouds.forEach((cloud) => {  //调用每个云朵物体的animate方法,形成整个云层的不断变换效果
      cloud.animate()
    })
    ...
    this.renderer.render(this.scene, this.camera)
    requestAnimationFrame(this.animate.bind(this))
  }

Окружающий свет и молния

В то же время AmbientLight и DirectionalLight используются как стабильные источники света для всей сцены, чтобы улучшить моделирование реалистичных сцен.

//director.js
initLight () {
  const ambientLight = new AmbientLight(0x555555)
  this.scene.add(ambientLight)

  const directionLight = new DirectionalLight(0xffeedd)
  directionLight.position.set(0,0,1)
  this.scene.add(directionLight)
}

Чтобы моделировать молнию с помощью точечного света, первый является начальным нотакой.

//director.js
addLightning () {
  const lightning = new PointLight(0x062d89, 30, 500, 1.7)
  lightning.position.set(200, 300, 100)
  this.lightning = lightning
  this.scene.add(lightning)
}

При циклическом рендеринге интенсивность (мощность) точечного источника света PointLight непрерывно и случайным образом изменяется для формирования эффекта мерцания.При небольшой интенсивности, то есть при затемнении света, положение точечного источника света равно " тихо" изменено, так что молния может быть изменена случайным образом без резкого. Земля появляется в различных положениях облачного слоя.

//director.js
animate () {
  ...
  //lightning
  if(Math.random() > 0.93 || this.lightning.power > 100){
    if(this.lightning.power < 100){
      this.lightning.position.set(
        Math.random() * 400,
        300 + Math.random() * 200,
        100
      )
    }
    this.lightning.power = 50 + Math.random() * 500
  }

  this.renderer.render(this.scene, this.camera)
  requestAnimationFrame(this.animate.bind(this))
}

Создать капли дождя

Эффект частиц, используемый для создания капель дождя. Чтобы создать группу частиц, интуитивно понятно создать объект частицы, а затем скопировать N, определить их положения и повороты соответственно.

Это прекрасно работает, когда вы работаете с небольшим количеством объектов, но когда вы хотите использовать большое количество объектов THREE.Sprite, вы быстро столкнетесь с проблемами производительности, поскольку Three.js должен управлять каждым объектом отдельно. .

Three.js предоставляет еще один способ обработки большого количества частиц, который требует использования THREE.Points. С THREE.Points Three.js больше не нужно управлять большим количеством отдельных объектов THREE.Sprite, а только экземплярами THREE.Points.

Используя THREE.Points, очень легко создавать множество крошечных объектов для имитации капель дождя, снежинок, дыма и других интересных эффектов.

Основная идея THREE.Points заключается в том, чтобы сначала объявить геометрию geom, а затем определить положение каждой вершины геометрии, которая будет позицией каждой частицы. Определяем материал вершины через PointsMaterial, затем новые Points(geom, material), генерируем систему частиц на основе входящей геометрии и материала вершины.

Движение частиц: координаты положения частиц определяются набором чиселconst positions = this.geom.attributes.position.array, этот набор чисел, каждые три числа определяют координатную точку (x\y\z), поэтому, чтобы изменить координату X частицы, изменитеpositions[ 3n ](n — номер частицы), так же координата Y соответствуетpositions[ 3n+1 ], координата Z соответствуетpositions[ 3n+2 ].

//RainDrop.js
export default class RainDrop {
  constructor () {
    const texture = new TextureLoader().load('/images/rain-drop.png')
    const material = new PointsMaterial({    //用图片初始化顶点材质
      size: 0.8,
      map: texture,
      transparent: true
    })
    
    const positions = []

    this.drops = 8000
    this.geom = new BufferGeometry()
    this.velocityY = []

    for(let i = 0; i < this.drops; i++){
      positions.push( Math.random() * 400 - 200 )
      positions.push( Math.random() * 500 - 250 )
      positions.push( Math.random() * 400 - 200 )
      this.velocityY.push(0.5 + Math.random() / 2)  //初始化每个粒子的坐标和粒子在Y方向的速度
    }
    
    //确定各个顶点的位置坐标
    this.geom.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) )  
    this.instance = new Points(this.geom, material)  //初始化粒子系统
  }

  animate () {
    const positions = this.geom.attributes.position.array;
    
    for(let i=0; i<this.drops * 3; i+=3){    //改变Y坐标,加速运动
      this.velocityY[i/3] += Math.random() * 0.05
      positions[ i + 1 ] -=  this.velocityY[i/3]
      if(positions[ i + 1 ] < -200){
        positions[ i + 1 ] =  200
        this.velocityY[i/3] = 0.5 + Math.random() / 2
      } 									
    }
    this.instance.rotation.y += 0.002    
    this.geom.attributes.position.needsUpdate = true
  }
}

Добавьте в сцену частицы капель дождя и при зацикливании вызовите метод анимации RainDrop:

//director.js
addRainDrop () {
  this.rainDrop = new RainDrop()
  this.scene.add(this.rainDrop.instance)
}
animate () {
  //rain drop move
  this.rainDrop.animate() 
  ...
  this.renderer.render(this.scene, this.camera)
  requestAnimationFrame(this.animate.bind(this))
}