Напишите небольшую сцену с помощью three.js

WebGL three.js
Напишите небольшую сцену с помощью three.js

В прошлый раз мы написали один из трех.jsанимация дождя, в основном с использованием частиц. На этот раз я создал небольшую сцену с помощью three.js.

адрес проектаеще:GitHub.com/Alxa О Лала/Он…, здесь размещены следующие тренировочные демонстрации three.js.

В качестве небольшого проекта для практики можно отрепетировать эту небольшую сцену:

  1. Использование различной геометрии и текстур
  2. Импорт внешних моделей
  3. Сцена и взаимодействие с пользователем
  4. простая анимация
  5. Отладка проекта three.js

Это просто, но очень полезно записать его целиком.

трава сцена

Построение сцены повторно использует шаблон сцены в анимации дождя.Template.

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

const groundGeometry = new PlaneGeometry( 20000, 20000 )  //草地平面几何体

const groundTexture = new TextureLoader().load('/images/room/grass.jpg')  //加载草地材质
groundTexture.wrapS = groundTexture.wrapT = RepeatWrapping   //设置重复贴图
groundTexture.repeat.set( 50, 50 )
groundTexture.anisotropy = 16
const groundMaterial = new MeshLambertMaterial({   //生成贴图的材质
  map: groundTexture 
})

const ground = new Mesh( groundGeometry, groundMaterial )   //生成草地

image.pngЧтобы не выглядеть ненавязчиво, в качестве цвета фона холста используем цвет неба, а затем добавляем эффект тумана вдали.

this.rendererColor = new Color(0xcce0ff)   //设置画布的背景色
//renderer.setClearColor(this.rendererColor)

this.scene.fog = new Fog( 0xcce0ff, 2500, 10000)   //加上雾化的效果

image.pngВ этом процессе необходимо постоянно регулировать положение и поле зрения камеры до подходящего поля зрения. Наш окончательный выбор местоположения, ближнего и дальнего расстояния:

this.PCamera.far = 10000
this.PCamera.near = 1
this.cameraPostion = new Vector3(1000, 600, 1500)

Построить дом

Наш дом построен вокруг начала координат, что облегчает настройку координат.

Для облегчения отладки добавляем в сценуAxesHelper, который используется для простого моделирования трех осей в сцене. Красный — ось X, зеленый — ось Y, синий — ось Z.

image.png

const axesHelper = new AxesHelper( 700 )    //创建AxesHelper,700是三条线的长度
this.scene.add( axesHelper )   //将AxesHelper加入到场景中

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

  • Сначала создайте землю, такую ​​же, как трава выше,PlaneGeometryи наклейки.
const floorGeometry = new PlaneGeometry( 800, 1000 )

const floorTexture = new TextureLoader().load('/images/room/floor.png')
floorTexture.wrapS = floorTexture.wrapT = RepeatWrapping
floorTexture.repeat.set( 25, 25 )
floorTexture.anisotropy = 16
const floorMaterial = new MeshLambertMaterial({ 
  map: floorTexture 
})

const floor = new Mesh( floorGeometry, floorMaterial )

image.png

  • Дальше стена. Стена в основном делится на: переднюю стенку, заднюю стенку и боковую стенку в зависимости от формы. Задняя стенка самая простая, представляющая собой обычный куб, боковая стенка представляет собой неправильный куб, а передняя стенка представляет собой куб с двумя отверстиями для дверей и окон.

Используем непосредственно заднюю стенкуBoxGeometry.

const boxGeometry = new BoxGeometry( ...arguments )
const boxMaterial = new MeshLambertMaterial({ 
  color: 0xe5d890 
})
const box = new Mesh( boxGeometry, boxMaterial )

Для боковых и фасадных стенExtrudeGeometry.ExtrudeGeometryЧтобы создать трехмерную графику из двухмерной графики, мы можем сначала нарисовать двухмерную фигуру,ExtrudeGeometryЭта 2D-форма постоянно «утолщается», в результате чего получается цилиндр. Аналогия идет от плоского круга к цилиндру.

Взяв в качестве примера боковую стенку, мы сначала нарисуем форму, подобную следующей, а затем «утолщаем» ее:image.png

function drawShape () {
  const shape = new Shape()   //用Shape类绘制二维形状
  shape.moveTo(-400, 0)       //绘制方法类似canvas中的绘制方法
  shape.lineTo(400, 0)
  shape.lineTo(400,400)
  shape.lineTo(0,500)
  shape.lineTo(-400,400)

  const extrudeSettings = {  //Extrude配置,具体可以修改参数调试各种效果
    amount: 8,  
    bevelSegments: 2, 
    steps: 2, 
    bevelSize: 1, 
    bevelThickness: 1 
  }
  //根据二维形状和Extrude配置生成ExtrudeGeometry
  const geometry = new ExtrudeGeometry( shape, extrudeSettings ) 
}

const wallGeometry = drawShape()
const wallMaterial = new MeshLambertMaterial({ 
  color: 0xe5d890 
})
const wall = new Mesh( wallGeometry, wallMaterial )

Чертеж передней стены аналогичен чертежу боковой стены, за исключением того, что «выкопаны» два отверстия для двери и окна. По сути, это еще и «копание» на двумерной графике.

drawShape () {
  const shape = new Shape()   //绘制整体形状
  shape.moveTo(-500, 0)
  shape.lineTo(500, 0)
  shape.lineTo(500,400)
  shape.lineTo(-500,400)

  const window = new Path()   //用Path类绘制窗户形状
  window.moveTo(100,100)
  window.lineTo(100,250)
  window.lineTo(300,250)
  window.lineTo(300,100)
  shape.holes.push(window)   //将窗户形状加入到shape.holes数组,就会从当前形状减去窗户形状。

  const door = new Path()   //用Path类绘制门的形状
  door.moveTo(-330,30)
  door.lineTo(-330, 250)
  door.lineTo(-210, 250)
  door.lineTo(-210, 30)
  shape.holes.push(door)    //将门的形状加入到shape.holes数组

  const extrudeSettings = { 
    amount: 8, 
    bevelSegments: 2, 
    steps: 2, 
    bevelSize: 1, 
    bevelThickness: 5 
  }

  const geometry = new ExtrudeGeometry( shape, extrudeSettings )
  return geometry
}

Таким образом нарисованы четыре стены,ExtrudeGeometryМогут быть реализованы полые цилиндры различной формы, а также на их основе реализованы рамы задних дверей и оконные рамы.

image.png

  • Наконец, крыша. Крыша состоит из двухBoxGeometry, установите соответствующее положение и угол поворота для достижения каждогоBoxGeometryИспользуйте карту для одной из сторон и сплошной цвет для остальных пяти сторон.
const roofGeometry = new BoxGeometry( 500, 1300, 10 )   //创建几何体

const roofTexture = new TextureLoader().load('/images/room/roof.png')  //导入贴图
roofTexture.wrapS = roofTexture.wrapT = RepeatWrapping
roofTexture.repeat.set( 2, 2 )
		
const materials = []    //创建一个6项的材质数组,three.js会自动将每一项贴一个面
const colorMaterial = new MeshLambertMaterial({ color: 'grey' })
const textureMaterial = new MeshLambertMaterial({ map: roofTexture })
for(let i=0; i<6; i++){
  materials.push(colorMaterial)   
}
materials[5] = textureMaterial  //将其中一个面的设为图片材质,而其他五个面是纯色材质

const roof = new Mesh( roofGeometry, materials )

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

image.png

Добавьте двери и окна

Двери делятся на дверные полотна и дверные коробки, которые различны по форме и материалу, но также представляют собой единое целое. То же самое касается окон и створок.

В three.js мы используем класс Group для управления группой объектов.

const group = new Group()  //创建Group
group.add( this.frame )    //往Group加入门框
group.add( this.door )     //往Group加入门板

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

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

initFrame () {
    const frameGeometry = this.drawShape()   //门框的形状是用`ExtrudeGeometry`实现的
    const frameMaterial = new MeshLambertMaterial({  //门框材质
      color: 0x8d7159
    })
    const frame = new Mesh( frameGeometry, frameMaterial )
    this.frame = frame
}

initDoor () {
    const doorGeometry = new BoxGeometry(100,210,4)  //门的形状
    const doorTexture = new TextureLoader().load('/images/room/wood.jpg')
    const doorMaterial = new MeshLambertMaterial({ map: doorTexture })  //门的材质
    const door = new Mesh(doorGeometry, doorMaterial)    //BoxGeometry的材质不是数组时,每个面都会贴这个材质

    this.param = {
      positionX : 60,
      positionZ: 0,
      rotationY: 0
    }
    door.position.set(this.param.positionX, 105, this.param.positionZ)  //门相对于group的位移和旋转,开关门动画会用到。
    door.rotation.y = this.param.rotationY

    this.door = door
    this.status = 'closed'
}

анимация дверного переключателя

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

window.addEventListener('click', onMouseDown)  //给window绑定点击事件
function onMouseDown (event) { 
    let vector = new Vector3(   // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)

      (event.clientX / window.innerWidth) * 2 - 1,
      -(event.clientY / window.innerHeight) * 2 + 1,
      0.5
    )
    vector = vector.unproject(this.camera)
    const raycaster = new Raycaster(    // 通过摄像机和鼠标位置更新射线
      this.camera.position,
      vector.sub(this.camera.position).normalize()
    )
    
    // 计算物体和射线的交点
    const intersects = raycaster.intersectObjects([this.doorSet.door])
    if(intersects.length > 0){
      this.doorSet.animate()
    }
}

После щелчка по двери определите, открыта она или закрыта, и установите положение и поворот (относительно группы) следующего состояния в соответствии с состоянием.

animate () {
    if(this.status === 'closed'){  
      this.param.positionX = 10
      this.param.positionZ = 50
      this.param.rotationY = -Math.PI/2
      this.status= 'open'
    }else{
      this.param.positionX = 60
      this.param.positionZ = 0
      this.param.rotationY = 0
      this.status= 'closed'
    }
    this.onUpdate(this.param)
}

onUpdate (param) {
    this.door.position.x = param.positionX
    this.door.position.z = param.positionZ
    this.door.rotation.y = param.rotationY
}

door.gif

рисовать окна

Чертеж оконной рамы такой же, как и чертеж передней стены.ExtrudeGeometry.

окна используютсяBoxGeometry, материалMeshPhysicalMaterial, установите определенную прозрачность, чтобы имитировать эффект стекла.

const windowGeometry = new BoxGeometry( 150, 200, 4 )
const windowMaterial = new MeshPhysicalMaterial( {
      map: null,
      color: 0xcfcfcf,
      metalness: 0,
      roughness: 0,
      opacity: 0.45,
      transparent: true,
      envMapIntensity: 10,
      premultipliedAlpha: true
} )
const window = new Mesh( windowGeometry, windowMaterial )

Подобно дверям, окна и створки добавляются кgroupсередина.

image.png

импортный стол и цветы

Стол и цветок являются импортными внешними моделями. Для сложных моделей очень проблематично строить напрямую с помощью three.js Мы можем моделировать с помощью специального программного обеспечения для моделирования, а затем экспортировать модель.

three.js поддерживает импорт различных 3d моделей, здесь мы используем OBJ.

OBJ и MTL — это два формата, которые дополняют друг друга и часто используются вместе. Файлы OBJ определяют геометрию, а файлы MTL определяют используемые материалы. Их импорт осуществляется с помощью отзывчивого загрузчика. В качестве примера возьмем импорт таблицы:

import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader"  //引入OBJLoader
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader"  //引入MTLLoader

function addTable (scene) {
  const mtlLoader = new MTLLoader()
  const objLoader = new OBJLoader()

  mtlLoader.load( '../../images/room/table/table.mtl', ( material ) => {  //导入材质
    objLoader.setMaterials( material )                //为objLoader设置材质
    objLoader.load( '../../images/room/table/table.obj', ( object ) => {  //导入形状
      object.position.set(600,0,0)    //设置形状的位置
      scene.add( object )             //将形状加入到场景中
    } );
  })
}

addTable(this.scene)  

orbitControls

Three.js предоставляет некоторые элементы управления камерой, с помощью которых вы можете управлять камерами в сцене. Ниже приведены некоторые из наиболее часто используемых элементов управления.image.pngЗдесь мы используем контроллер дорожки (OrbitControls), который можно использовать для управления вращением и перемещением объектов в сцене вокруг центра сцены. Использование очень простое:

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

function addOrbitControls (camera, el) {  
  const controls = new OrbitControls( camera, el )  //参数是将要被控制的相机和用于事件监听的HTML元素(通常是renderer.domElement)
  controls.maxPolarAngle = Math.PI * 0.45  //垂直旋转的角度的上限
  controls.enablePan = false    //禁止平移
}

addOrbitControls(this.camera, this.renderer.domElement)

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

change.gif

dat.gui

dat.gui может легко создавать компоненты интерфейса, которые могут изменять переменные кода, что может упростить отладку three.js.В официальном случае three мы часто можем видеть использование dat.gui.

Как использовать можно обратиться к этой статье,Научите, как использовать dat.gui, очень подробно.

Мы используем его здесь для управления отображением и скрытием оси и крыши.

dat.gif

import { GUI } from 'dat.gui'

export function Gui () {    //初始化GUI,添加要控制的变量
  const controls = new function () {
    this.showAxes = false
    this.showRoof = true
  }

  const gui = new GUI()

  gui.add(controls, 'showAxes')
  gui.add(controls, 'showRoof')
  
  return controls
}
//director.js
import { Gui } from "../tools/dat.gui"
this.Controls = Gui()

//在循环渲染中,根据当前Controls中的值判断是否显示axesHelper和roof
animate () {  
    if(this.Controls.showAxes){
      this.scene.add( this.axesHelper )
    }else{
      this.scene.remove( this.axesHelper )
    }

    if(this.Controls.showRoof){
      this.scene.add( this.roof_1 )
      this.scene.add( this.roof_2 )
    }else{
      this.scene.remove( this.roof_1 )
      this.scene.remove( this.roof_2 )
    }

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

На сегодня это все. Проект threejs-tutorial на github продолжит обновлять различные три кейса. Добро пожаловать на внимание~~GitHub.com/магия СОСО/Он…