《Three.JS零基础入门教程》----之相机详解

下面介绍three.js相机:

一、简介

为了在二维平面呈现三维效果, 我们需要一个观察者, 在二维平面上绘制出观察者看到的三维世界这个观察者就是相机。

相机用于决定场景中哪些部分能被看到,以及如何呈现它们。它类似于真实摄像机的存在。

相机有很多属性,比如位置、目标(或注视点)、视角(FOV)、近面和远面等。这些属性共同决定了相机如何看待场景中的物体,并形成最终的渲染结果。

为了描述一个观察者, 我们需要考虑这样两个问题

·观察方向: 观察者在哪里, 他沿着哪个方向在观察, 看向什么

·可视范围: 太近, 太远, 太偏的物体都看不到

1) 如何描述观察方向

为了描述观察方向, 有几个概念需要理解

·视点(eye point): 观察点的位置

·目标点(lookat point): 观察目标所在的位置

·观察方向(viewing direction): 从视点出发到目标点的射线, 也叫视线

为了在二维平面上绘制, 还需要确定上方向(up direction)

图片

如果我们调整上方向, 最终看到的效果就不同

图片

2) 如何描述可视范围

那如何描述可视范围呢?

·视角(fov): 可以看到的水平, 垂直的角度

·近裁截面(near): 可以看到的最近距离

·远裁截面(far): 可以看到的最远距离

由视角近面远面构成了可视空间(view volume).

常见的可视空间有两种

·长方体可视空间, 由正射投影(orthographic projection)产生

·四棱锥可视空间, 由透视投影(perspective projection)产生

图片

图片

二、透视相机详解

透视相机主要模拟人的视角, 一般观察物体, 会呈现近大远小的特征

图片

1) 相机参数

在初始化创建相机时, 可以设置4个参数

·fov: 垂直方向的视角

·aspect: 宽高比, 水平方向的视角

·near: 近截面

·far: 远截面

下面通过一个示例, 可视化调整相机的参数帮助大家理解每个参数的作用

const camera = new THREE.PerspectiveCamera(45, 2, 10, 20)

// 设置视点
camera.position.set(0, 0, 5)
// 设置观察点
camera.lookAt(0, 0, 0)

 

使用相机可视化工具动态调整参数, 观察相机的变化

import * as THREE from 'three'
import { BaseGui } from './BaseGui'

function cameraHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateProjectionMatrix()
  parent.update()
}

const cameraConfig = {
  fov: {
    name: '可视角度',
    extend: [0, 180],
    handler: cameraHandler,
  },
  aspect: {
    name: '宽高比',
    extend: [1, 3],
    step: 0.1,
    handler: cameraHandler,
  },
  near: {
    name: '近截面',
    extend: [0.1, 20],
    handler: cameraHandler,
  },
  far: {
    name: '远截面',
    extend: [10, 50],
    handler: cameraHandler,
  },
}

export class CameraGui extends BaseGui {
  constructor(options = {}) {
    if (!options.target.isCamera) {
      console.error('target must be an instance of Camera')
      return
    }
    if (!options.scene.isScene) {
      console.log('scene is required')
      return
    }

    super()
    this.init(options)
  }
  init(options) {
    this.helper = new THREE.CameraHelper(options.target)
    this.scene = options.scene
    this.scene.add(this.helper)

    this.camera = this.helper.camera

    options.camera !== false ? this.initCamera() : ''
  }
  initCamera() {
    this.initGuiFolder(this.camera, this.camera.type, this.helper, cameraConfig)
  }
}

 

import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

import { MeshGui } from '../gui'
import { CameraGui } from '../gui'

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

// 二. 创建渲染相机(上帝视角, 观察整个场景)
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight
)
camera.position.set(200, 200, 200)
camera.lookAt(0, 0, 0)

// 创建透视相机, 借助可视化工具观察
const pCamera = new THREE.PerspectiveCamera(45, 1, 10, 20)
pCamera.position.set(0, 0, 5)
pCamera.updateMatrixWorld()

new CameraGui({
  target: pCamera,
  scene,
})

// 三. 创建物体
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2)
const cubeMaterial = new THREE.MeshNormalMaterial()
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
scene.add(cube)

// 集成Gui
new MeshGui({
  target: cube,
})

// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
// 将渲染的canvas添加到body元素中
document.body.appendChild(renderer.domElement)

// 五. 辅助工具
const control = new OrbitControls(camera, renderer.domElement)

const axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)

const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)

function animation() {
  renderer.render(scene, camera)
}

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()

  renderer.setSize(window.innerWidth, window.innerHeight)
})

 

效果

图片

示例代码

import * as THREE from 'three'
import { BaseGui } from './BaseGui'

function positionHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateMatrixWorld()
  parent.update()
}
const positionConfig = {
  x: {
    handler: positionHandler,
  },
  y: {
    handler: positionHandler,
  },
  z: {
    handler: positionHandler,
  },
}

function cameraHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateProjectionMatrix()
  parent.update()
}

const cameraConfig = {
  fov: {
    name: '可视角度',
    extend: [0, 180],
    handler: cameraHandler,
  },
  aspect: {
    name: '宽高比',
    extend: [1, 3],
    step: 0.1,
    handler: cameraHandler,
  },
  near: {
    name: '近截面',
    extend: [0.1, 20],
    handler: cameraHandler,
  },
  far: {
    name: '远截面',
    extend: [10, 50],
    handler: cameraHandler,
  },
}

export class CameraGui extends BaseGui {
  constructor(options = {}) {
    if (!options.target.isCamera) {
      console.error('target must be an instance of Camera')
      return
    }
    if (!options.scene.isScene) {
      console.log('scene is required')
      return
    }

    super()
    this.init(options)
  }
  init(options) {
    this.helper = new THREE.CameraHelper(options.target)
    this.scene = options.scene
    this.scene.add(this.helper)

    this.camera = this.helper.camera

    this.position = this.camera.position

    options.camera !== false ? this.initCamera() : ''
    options.position !== false ? this.initPosition() : ''
  }
  initCamera() {
    this.initGuiFolder(this.camera, this.camera.type, this.helper, cameraConfig)
  }
  initPosition() {
    this.initGuiFolder(this.position, '相机坐标', this.helper, positionConfig)
  }
}

 

注意

当调整相机参数时, 需要更新相机的投影矩阵, 否则不会生效

图片

2) 调整视点

示例

import * as THREE from 'three'
import { BaseGui } from './BaseGui'

function positionHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateMatrixWorld()
  parent.update()
}
const positionConfig = {
  x: {
    handler: positionHandler,
  },
  y: {
    handler: positionHandler,
  },
  z: {
    handler: positionHandler,
  },
}

function cameraHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateProjectionMatrix()
  parent.update()
}

const cameraConfig = {
  fov: {
    name: '可视角度',
    extend: [0, 180],
    handler: cameraHandler,
  },
  aspect: {
    name: '宽高比',
    extend: [1, 3],
    step: 0.1,
    handler: cameraHandler,
  },
  near: {
    name: '近截面',
    extend: [0.1, 20],
    handler: cameraHandler,
  },
  far: {
    name: '远截面',
    extend: [10, 50],
    handler: cameraHandler,
  },
}

export class CameraGui extends BaseGui {
  constructor(options = {}) {
    if (!options.target.isCamera) {
      console.error('target must be an instance of Camera')
      return
    }
    if (!options.scene.isScene) {
      console.log('scene is required')
      return
    }

    super()
    this.init(options)
  }
  init(options) {
    this.helper = new THREE.CameraHelper(options.target)
    this.scene = options.scene
    this.scene.add(this.helper)

    this.camera = this.helper.camera

    this.position = this.camera.position

    options.camera !== false ? this.initCamera() : ''
    options.position !== false ? this.initPosition() : ''
  }
  initCamera() {
    this.initGuiFolder(this.camera, this.camera.type, this.helper, cameraConfig)
  }
  initPosition() {
    this.initGuiFolder(this.position, '相机坐标', this.helper, positionConfig)
  }
}

 

⚠️ 特别注意

当调整相机在世界中的位置时, 需要更新世界矩阵

图片

效果

图片

3)  调整观察点

示例

import * as THREE from 'three'
import { BaseGui } from './BaseGui'

function positionHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateMatrixWorld()
  parent.update()
}
const positionConfig = {
  x: {
    handler: positionHandler,
  },
  y: {
    handler: positionHandler,
  },
  z: {
    handler: positionHandler,
  },
}

function lookatHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.lookAt(...Object.values(item))
  camera.updateMatrixWorld()
  parent.update()
}

const lookatConfig = {
  x: {
    handler: lookatHandler,
  },
  y: {
    handler: lookatHandler,
  },
  z: {
    handler: lookatHandler,
  },
}

function cameraHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateProjectionMatrix()
  parent.update()
}

const cameraConfig = {
  fov: {
    name: '可视角度',
    extend: [0, 180],
    handler: cameraHandler,
  },
  aspect: {
    name: '宽高比',
    extend: [1, 3],
    step: 0.1,
    handler: cameraHandler,
  },
  near: {
    name: '近截面',
    extend: [0.1, 20],
    handler: cameraHandler,
  },
  far: {
    name: '远截面',
    extend: [10, 50],
    handler: cameraHandler,
  },
}

export class CameraGui extends BaseGui {
  constructor(options = {}) {
    if (!options.target.isCamera) {
      console.error('target must be an instance of Camera')
      return
    }
    if (!options.scene.isScene) {
      console.log('scene is required')
      return
    }

    super()
    this.init(options)
  }
  init(options) {
    this.helper = new THREE.CameraHelper(options.target)
    this.scene = options.scene
    this.scene.add(this.helper)

    this.camera = this.helper.camera

    this.position = this.camera.position

    options.camera !== false ? this.initCamera() : ''
    options.position !== false ? this.initPosition() : ''
    options.lookAt !== false ? this.initLookAt() : ''
  }
  initCamera() {
    this.initGuiFolder(this.camera, this.camera.type, this.helper, cameraConfig)
  }
  initPosition() {
    this.initGuiFolder(this.position, '相机坐标', this.helper, positionConfig)
  }
  initLookAt() {
    const lookAt = {
      x: 0,
      y: 0,
      z: 0,
    }
    this.initGuiFolder(lookAt, '观察点坐标', this.helper, lookatConfig)
  }
}

 

注意

每次调整观察点时, 需要重新调用lookAt()方法更新

效果

图片

4) 实时渲染

为了能看到添加到场景中的透视相机拍出的图片, 我们可以使用透视相机进行拍照并渲染

效果

图片

我们将渲染的画布分成两个部分

·左边: 实时显示透视相机的渲染结果

·右边: 调整相机参数

示例

import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

import { MeshGui } from '../gui'
import { CameraGui } from '../gui'

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

// 二. 创建渲染相机(上帝视角, 观察整个场景)
const camera = new THREE.PerspectiveCamera(
  45,
  (window.innerWidth / window.innerHeight) * 0.5
)
camera.position.set(20, 20, 20)
camera.lookAt(0, 0, 0)

// 创建透视相机, 借助可视化工具观察
const pCamera = new THREE.PerspectiveCamera(
  45,
  (window.innerWidth / window.innerHeight) * 0.5,
  10,
  20
)
pCamera.position.set(0, 0, 5)
pCamera.updateMatrixWorld()

new CameraGui({
  target: pCamera,
  scene,
})

// 三. 创建物体
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2)
const cubeMaterial = new THREE.MeshNormalMaterial()
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
scene.add(cube)

// 集成Gui
new MeshGui({
  target: cube,
})

// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
renderer.autoClear = false

// 将渲染的canvas添加到body元素中
document.body.appendChild(renderer.domElement)

// 五. 辅助工具
const control = new OrbitControls(camera, renderer.domElement)

const axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)

const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)

function animation() {
  renderer.clear()
  renderer.setViewport(0, 0, window.innerWidth / 2, window.innerHeight)
  renderer.render(scene, pCamera)

  renderer.setViewport(
    window.innerWidth / 2,
    0,
    window.innerWidth / 2,
    window.innerHeight
  )
  renderer.render(scene, camera)
}

window.addEventListener('resize', () => {
  camera.aspect = (window.innerWidth / window.innerHeight) * 0.5
  camera.updateProjectionMatrix()

  pCamera.aspect = (window.innerWidth / window.innerHeight) * 0.5
  pCamera.updateProjectionMatrix()

  renderer.setSize(window.innerWidth, window.innerHeight)
})

三、正交相机详解

正交相机使用正视投影, 不管从什么距离, 哪个角度渲染的结果都是一样的. 这种相机主要被用于游戏中

图片

1) 相机参数

在初始化创建相机时, 可以设置4个参数

·left: 左面

·right: 右面

·top: 上面

·bottom: 下面

·near: 近面

·far: 远面

下面通过一个示例, 可视化调整相机的参数帮助大家理解每个参数的作用

效果

图片

示例

import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

import { MeshGui } from '../gui'
import { CameraGui } from '../gui'

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

// 二. 创建渲染相机(上帝视角, 观察整个场景)
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight
)
camera.position.set(20, 20, 20)
camera.lookAt(0, 0, 0)

// 创建正交相机, 借助可视化工具观察
const oCamera = new THREE.OrthographicCamera(-5, 5, 5, -5, 5, 15)
oCamera.position.set(0, 0, 5)
oCamera.updateMatrixWorld()

new CameraGui({
  target: oCamera,
  scene,
})

// 三. 创建物体
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2)
const cubeMaterial = new THREE.MeshNormalMaterial()
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
scene.add(cube)

// 集成Gui
new MeshGui({
  target: cube,
})

// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
// 将渲染的canvas添加到body元素中
document.body.appendChild(renderer.domElement)

// 五. 辅助工具
const control = new OrbitControls(camera, renderer.domElement)

const axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)

const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)

function animation() {
  renderer.render(scene, camera)
}

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()

  renderer.setSize(window.innerWidth, window.innerHeight)
})

 

import * as THREE from 'three'
import { BaseGui } from './BaseGui'

function positionHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateMatrixWorld()
  parent.update()
}
const positionConfig = {
  x: {
    handler: positionHandler,
  },
  y: {
    handler: positionHandler,
  },
  z: {
    handler: positionHandler,
  },
}

function lookatHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.lookAt(...Object.values(item))
  camera.updateMatrixWorld()
  parent.update()
}

const lookatConfig = {
  x: {
    handler: lookatHandler,
  },
  y: {
    handler: lookatHandler,
  },
  z: {
    handler: lookatHandler,
  },
}

function cameraHandler(item, key, value, parent) {
  const camera = parent.camera

  item[key] = value
  camera.updateProjectionMatrix()
  parent.update()
}

const cameraMapping = {
  PerspectiveCamera: {
    fov: {
      name: '可视角度',
      extend: [0, 180],
      handler: cameraHandler,
    },
    aspect: {
      name: '宽高比',
      extend: [1, 3],
      step: 0.1,
      handler: cameraHandler,
    },
    near: {
      name: '近截面',
      extend: [0.1, 20],
      handler: cameraHandler,
    },
    far: {
      name: '远截面',
      extend: [10, 50],
      handler: cameraHandler,
    },
  },
  OrthographicCamera: {
    left: {
      name: '左面',
      extend: [-10, 10],
      handler: cameraHandler,
    },
    right: {
      name: '右面',
      extend: [-10, 10],
      handler: cameraHandler,
    },
    top: {
      name: '上面',
      extend: [-10, 10],
      handler: cameraHandler,
    },
    bottom: {
      name: '底面',
      extend: [-10, 10],
      handler: cameraHandler,
    },
    near: {
      name: '近面',
      extend: [1, 10],
      handler: cameraHandler,
    },
    far: {
      name: '近面',
      extend: [10, 20],
      handler: cameraHandler,
    },
  },
}

export class CameraGui extends BaseGui {
  constructor(options = {}) {
    if (!options.target.isCamera) {
      console.error('target must be an instance of Camera')
      return
    }
    if (!options.scene.isScene) {
      console.log('scene is required')
      return
    }

    super()
    this.init(options)
  }
  init(options) {
    this.helper = new THREE.CameraHelper(options.target)
    this.scene = options.scene
    this.scene.add(this.helper)
    console.log(this.scene)

    this.camera = this.helper.camera

    this.position = this.camera.position

    options.camera !== false ? this.initCamera() : ''
    options.position !== false ? this.initPosition() : ''
    options.lookAt !== false ? this.initLookAt() : ''
  }
  initCamera() {
    this.initGuiFolder(
      this.camera,
      this.camera.type,
      this.helper,
      cameraMapping[this.camera.type]
    )
  }
  initPosition() {
    this.initGuiFolder(this.position, '相机坐标', this.helper, positionConfig)
  }
  initLookAt() {
    const lookAt = {
      x: 0,
      y: 0,
      z: 0,
    }
    this.initGuiFolder(lookAt, '观察点坐标', this.helper, lookatConfig)
  }
}

 

2) 实时渲染

import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

import { MeshGui } from '../gui'
import { CameraGui } from '../gui'

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

// 二. 创建渲染相机(上帝视角, 观察整个场景)
const camera = new THREE.PerspectiveCamera(
  45,
  (window.innerWidth / window.innerHeight) * 0.5
)
camera.position.set(20, 20, 20)
camera.lookAt(0, 0, 0)

// 创建透视相机, 借助可视化工具观察
// 创建正交相机, 借助可视化工具观察
const oCamera = new THREE.OrthographicCamera(-5, 5, 5, -5, 5, 15)
oCamera.position.set(0, 0, 5)
oCamera.updateMatrixWorld()

new CameraGui({
  target: oCamera,
  scene,
})

// 三. 创建物体
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2)
const cubeMaterial = new THREE.MeshNormalMaterial()
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
scene.add(cube)

// 集成Gui
new MeshGui({
  target: cube,
})

// 四. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(animation)
renderer.autoClear = false

// 将渲染的canvas添加到body元素中
document.body.appendChild(renderer.domElement)

// 五. 辅助工具
const control = new OrbitControls(camera, renderer.domElement)

const axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)

const gridHelper = new THREE.GridHelper(20, 20, 0xffffff, 0xffffff)
gridHelper.material.transparent = true
gridHelper.material.opacity = 0.5
scene.add(gridHelper)

function animation() {
  renderer.clear()
  renderer.setViewport(0, 0, window.innerWidth / 2, window.innerHeight)
  renderer.render(scene, oCamera)

  renderer.setViewport(
    window.innerWidth / 2,
    0,
    window.innerWidth / 2,
    window.innerHeight
  )
  renderer.render(scene, camera)
}

window.addEventListener('resize', () => {
  camera.aspect = (window.innerWidth / window.innerHeight) * 0.5
  camera.updateProjectionMatrix()

  renderer.setSize(window.innerWidth, window.innerHeight)
})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值