從1開始學習THREE.js--場景

概述

本文將會涵蓋如下內容css

  1. Three.js場景中使用的組件
  2. THREE.Scene對象的做用
  3. 幾何網格Geometry 和網格 Mesh 使如何聯繫的

建立場景

場景的意義

THREE.Scene能夠說使場景的代碼化, 它保存了全部圖形場景的必要信息, 好比圖形對象, 光源, 和渲染所須要的其餘對象. 每一個添加到場景的對象, 甚至場景自己都是繼承自 THREE.Object3D對象.web

建立一個簡單場景

// 導出給controller使用
export const scene = new THREE.Scene()
export const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100)
export const renderer = new THREE.WebGLRenderer()
export const planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1)

window.scene = scene
export const init = function() {
  const axes = new THREE.AxesHelper(20)
  scene.add(axes)
  scene.add(camera)


  renderer.setClearColor(new THREE.Color(0x000000))
  renderer.setSize(window.innerWidth, window.innerHeight)
  renderer.shadowMap.enabled = true

  const planeMaterial = new THREE.MeshLambertMaterial({
    color: 'red',
  })
  const plane = new THREE.Mesh(planeGeometry, planeMaterial)
  plane.receiveShadow = true
  plane.rotation.x = -0.5 * Math.PI
  plane.position.x = 0
  plane.position.y = 0
  plane.position.z = 0
  plane.name = 'plane'
  scene.add(plane)
  camera.position.x = -30
  camera.position.y = 40
  camera.position.z = 30
  camera.lookAt(plane.position)
    // 使材質對光產生反應
  const ambientLight = new THREE.AmbientLight(0x3c3c3c)
  scene.add(ambientLight)
    // 建立一個點光源
  const spotLight = new THREE.SpotLight(0xffffff, 1.2, 150, 120)
  spotLight.position.set(-40, 60, -10)
  spotLight.castShadow = true
  scene.add(spotLight)

  document.getElementById('webgl-output').appendChild(renderer.domElement)
  renderer.render(scene, camera)
}
複製代碼

建立一個輔助控制器, 能夠動態往場景裏添加cube並查看有多少個cubeapp

const gui = new dat.GUI()
const addBlock = function() {
  this.numberOfCubes += 1
  const cubeL = Math.random() * 5
  const cubeGeometry = new THREE.BoxGeometry(cubeL, cubeL, cubeL)
  const cubeMaterial = new THREE.MeshLambertMaterial({
    color: Math.random() * 0xffffff,
  })
  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
  cube.castShadow = true
  cube.position.x = camera.position.x + Math.round(Math.random() * planeGeometry.parameters.width)
  cube.position.y = Math.round(Math.random() * 5)
  cube.position.z = -20 + Math.round(Math.random() * planeGeometry.parameters.height)
  cube.name='cube-'+this.numberOfCubes
  scene.add(cube)
  this.numberOfObjects = scene.children.length,

  renderer.render(scene, camera)
}
const removeBlock = function() {
  if (this.numberOfCubes > 0) {
    const lastOne = scene.getObjectByName(`cube-${this.numberOfCubes}`)
    scene.remove(lastOne)
    this.numberOfCubes -= 1
    renderer.render(scene, camera)
    this.numberOfObjects = scene.children.length
  }
}
export const controller = {
  addBlock: () => addBlock.call(controller),
  numberOfCubes: 0,
  numberOfObjects: scene.children.length,
  removeBlock: () => removeBlock.call(controller),
}
// 選擇哪些參數須要添加到控制GUI中
gui.add(controller, 'addBlock')
gui.add(controller, 'numberOfCubes').listen()
gui.add(controller, 'numberOfObjects').listen()
gui.add(controller, 'removeBlock')
複製代碼

運行截圖dom

以上代碼分別使用了scene的如下方法:ide

  • scene.add
  • scene.remove
  • scene.children
  • scene.getObjectByName 這個方法的設計跟DOM的getElementByClass相似.

下面添加一個動畫, 讓添加的cube自動旋轉起來函數

// controller.js
gui.add(controller, 'rotationSpeed', 0, 0.5)
export const controller = {
    // ...
  rotationSpeed: 0,
}
// animation.js
export const animationRender = function render() {
  scene.traverse((obj) => {
    if (obj.name.includes('cube')) {
      obj.rotation.x += controller.rotationSpeed
      obj.rotation.y += controller.rotationSpeed
      obj.rotation.Z += controller.rotationSpeed
    }
  })
  requestAnimationFrame(animationRender)
  renderer.render(scene, camera)
}
複製代碼

這裏又使用了一個新的scene的方法, traverse, 遍歷全部的子對象樹, 不過與scene.children.forEach的差異在於: 若是子對象自己還有子對象, 最會在該子對象上執行traverse方法. 使用requestAnimationFrame使動畫連貫. 遞歸執行動畫

爲場景添加霧化效果

fog表示場景的霧化屬性, 霧化的效果使: 場景中的物體離得越遠越模糊, 跟拍照同樣.webgl

添加的方法使:ui

// 添加白色霧化效果, near和far值
scene.fog = new THREE.Fog(0xffffff, 0.015, 100)
// 指數增加的霧化濃度
scene.fog = new THREE.FogExp(0xffffff, 0.01)
複製代碼

線性漸變截圖this

指數漸變截圖

使用overrideMaterial屬性

可使用這一屬性批量修改在場景中的全部物體的材質, 方便進行統一的控制, 即便是物體自己設置了屬性.

scene.overrideMaterial = new THREE.MeshLambertMaterial({
    color: 0xfffffff
})
複製代碼

覆蓋截圖:

上述內容便是關於場景的全部經常使用屬性的描述

幾何體和網格

幾何體和網格的意義

用於填充場景, 表現具體的物體內容.

幾何體的屬性和方法

大多數幾何體均是三維空間的點集(vertex, 頂點)和將這些點鏈接起來的面.以立方體爲例

  • 立方體有8個角
  • 立方體有6個面, 但在THREE裏面3個點構成一個面,那麼就是12個面
// customeCube.js
const vertices = [
  new THREE.Vector3(1, 3, 1),
  new THREE.Vector3(1, 3, -1),
  new THREE.Vector3(1, -1, 1),
  new THREE.Vector3(1, -1, -1),
  new THREE.Vector3(-1, 3, -1),
  new THREE.Vector3(-1, 3, 1),
  new THREE.Vector3(-1, -1, -1),
  new THREE.Vector3(-1, -1, 1),
]
const faces = [
  new THREE.Face3(0, 2, 1),
  new THREE.Face3(2, 3, 1),
  new THREE.Face3(4, 6, 5),
  new THREE.Face3(6, 7, 5),
  new THREE.Face3(4, 5, 1),
  new THREE.Face3(5, 0, 1),
  new THREE.Face3(7, 6, 2),
  new THREE.Face3(6, 3, 2),
  new THREE.Face3(5, 7, 0),
  new THREE.Face3(7, 2, 0),
  new THREE.Face3(1, 3, 4),
  new THREE.Face3(3, 6, 4),
]

const gem = new THREE.Geometry()
gem.vertices = vertices
gem.faces = faces
gem.computeFaceNormals()
const mat = [
  new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}),
  new THREE.MeshLambertMaterial({opacity: 0.6, color: 0x44ff44, transparent: true}),
]
const group = new THREE.Group()

for ( let i = 0, l = mat.length; i < l; i ++ ) {
  group.add( new THREE.Mesh( gem, mat[i] ) )
}
// 遍歷全部mesh
group.children.forEach((mesh) => mesh.castShadow = true)
group.name = 'customCube'

export default group
複製代碼

最終展現的效果:

注意在建立faces的時候, 若是須要讓這個面, 面向攝像機, 那麼3個點須要按照順時針方向添加,反之爲逆時針

最後一個clone方法

const clone = function() {
  const geo = scene.getObjectByName('customCube').children[0].geometry
  const mats = [
    new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}),
    new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}),
  ]
  const group = new THREE.Group()
  for (let i = 0, l=mats.length; i < l; i ++) {
    group.add(new THREE.Mesh(geo, mats[i]))
  }
  group.translateX(10 * (Math.random() + 1))
  group.translateY(3)
  group.name = 'clone'
  scene.remove(scene.getObjectByName('clone'))
  scene.add(group)
}
複製代碼

clone能夠克隆整個物體, 也能夠只複製geometry, 再從新給material.

網格對象的屬性和方法

網格的建立須要一個幾何體以及一個或多個材質, 建立完成以後, 添加到場景中進行渲染. 如下屬性用於改變網格在場景中的位置和顯示的效果

  • position: 相對於父對象的位置
  • rotation: 設置繞每一個軸的旋轉弧度, 還能夠單獨設置, 好比rotateX/Y/Z, 注意amount, 要使用弧度, 換算方式爲degree * (Math.PI/180)
    • rotation.x = amount
    • rotation.set(x,y,z)
    • rotation = new THREE.Vector3(x,y,z)
  • scale: 沿X,Y,Z放大縮小
  • translateX/Y/Z(amount): 水平移動amount的距離, 不改變絕對位置(可參考css的transform屬性)
  • visible: 若是爲false, 不會渲染到場景中

須要注意的是dat.GUI的用法, 如下是設置controller

const controls = new function () {
      this.scaleX = 1;
      this.scaleY = 1;
      this.scaleZ = 1;

      this.positionX = 0;
      this.positionY = 4;
      this.positionZ = 0;

      this.rotationX = 0;
      this.rotationY = 0;
      this.rotationZ = 0;
      this.scale = 1;

      this.translateX = 0;
      this.translateY = 0;
      this.translateZ = 0;

      this.visible = true;
      // 點擊時調用函數
      this.translate = function () {
        
          cube.translateX(controls.translateX);
          cube.translateY(controls.translateY);
          cube.translateZ(controls.translateZ);

          controls.positionX = cube.position.x;
          controls.positionY = cube.position.y;
          controls.positionZ = cube.position.z;
      }
  };
複製代碼

按類別添加到dat.GUI中, 並監聽變化進行響應

const gui = new dat.GUI();
 // addFolder, 添加一個文件夾的層級
 guiScale = gui.addFolder('scale');
 guiScale.add(controls, 'scaleX', 0, 5);
 guiScale.add(controls, 'scaleY', 0, 5);
 guiScale.add(controls, 'scaleZ', 0, 5);

 guiPosition = gui.addFolder('position');
 const contX = guiPosition.add(controls, 'positionX', -10, 10);
 const contY = guiPosition.add(controls, 'positionY', -4, 20);
 const contZ = guiPosition.add(controls, 'positionZ', -10, 10);
 // 監聽器
 contX.listen();
 // 響應, 每次更改時觸發
 contX.onChange(function (value) {
     cube.position.x = controls.positionX;
 });

 contY.listen();
 contY.onChange(function (value) {
     cube.position.y = controls.positionY;
 });

 contZ.listen();
 contZ.onChange(function (value) {
     cube.position.z = controls.positionZ;
 });
複製代碼

攝像機

一個場景的渲染若是沒有攝像機就沒法被展現出來, 一共有兩種攝像機

  • 正交投影: 提供僞3D效果
  • 透視投影: 提供近大遠小的真實效果

建立透視投影的參數有:

  • fov: 視場
  • near: 進場
  • far: 遠場
  • aspect: 長寬比
  • zoom: 縮放比

建立正交投影的參數爲, 很像一個長方體:

  • left: 左邊界
  • right: 右邊界
  • top: 上邊界
  • bottom: 下邊界
  • near: 近場
  • fat: 遠場
  • zoom: 縮放比

攝像機都有一個方法: lookAt(new THREE.Vector3(x,y,z)) 表示攝像機應該指向的地方, 能夠指向一個點, 也能夠指向一個具體的物體, 好比plane, 下一部分會詳細說明攝像機的具體用法, 此處先帶過

總結

上述部分講述了THREE.Scene的全部屬性和方法, THREE.Geometry對象或使用內置幾何體建立幾何體, 最後是兩種攝像機的簡介.相信大家能夠製做一些簡單的場景和幾何體了.

相關文章
相關標籤/搜索