在three.js中最常用的摄像机并且之前我们一直用的摄像机是透视摄像机 PerspectiveCamera,它可以提供一个近大远小的3D视觉效果.
阴影贴图的工作方式就是具有投射阴影的光能对所有能被投射阴影的物体从光源渲染阴影。
一、学习视频
二、摄像机相关内容
2.1 摄像机(Camera)
摄像机的抽象基类。在构建新摄像机时,应始终继承此类。
- .isCamera : Boolean
Read-only flag to check if a given object is of type Camera. - .layers : Layers
摄像机是一个layers的成员. 这是一个从Object3D继承而来的属性。
当摄像机的视点被渲染的时候,物体必须和当前被看到的摄像机共享至少一个层。 - .matrixWorldInverse : Matrix4
这是matrixWorld矩阵的逆矩阵。 MatrixWorld包含了相机的世界变换矩阵。 - .projectionMatrix : Matrix4
这是投影变换矩阵。 - .projectionMatrixInverse : Matrix4
这是投影变换矩阵的逆矩阵。 - .clone ( ) : Camera
返回一个具有和当前相机的属性一样的新的相机。 - .copy ( source : Camera, recursive : Boolean ) : this
将源摄像机的属性复制到新摄像机中。 - .getWorldDirection ( target : Vector3 ) : Vector3
target --- 调用该函数的结果将复制给该Vector3对象。
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
export default () => {
const canvas = document.querySelector('#demo')
const domeView1 = document.querySelector('#view1')
const domeView2 = document.querySelector('#view2')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
const fov = 45
const aspect = 2 // the canvas default
const near = 5
const far = 100
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 10, 20)
const cameraHelp = new THREE.CameraHelper(camera)
interface Helper {
[key: string]: any
}
class MinMaxGUIHelper {
obj: Helper
minProp: any
maxProp: any
minDif: number
constructor(obj: Helper, minProp: any, maxProp: any, minDif: number) {
this.obj = obj
this.minProp = minProp
this.maxProp = maxProp
this.minDif = minDif
}
get min() {
return this.obj[this.minProp]
}
set min(v) {
this.obj[this.minProp] = v
this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif)
}
get max() {
return this.obj[this.maxProp]
}
set max(v) {
this.obj[this.maxProp] = v
this.min = this.min // this will call the min setter
}
}
const gui = new GUI()
gui.add(camera, 'fov', 1, 180)
const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1)
gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near')
gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far')
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 5, 0)
controls.update()
const camera2 = new THREE.PerspectiveCamera(
60, // fov
2, // aspect
0.1, // near
500, // far
)
camera2.position.set(40, 10, 30)
camera2.lookAt(0, 5, 0)
const controls2 = new OrbitControls(camera2, domeView2)
controls2.target.set(0, 5, 0)
controls2.update()
const scene = new THREE.Scene()
scene.background = new THREE.Color('black')
scene.add(cameraHelp)
{
const planeSize = 40
const loader = new THREE.TextureLoader()
const texture = loader.load('https://threejs.org/manual/examples/resources/images/checker.png')
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.magFilter = THREE.NearestFilter
texture.colorSpace = THREE.SRGBColorSpace
const repeats = planeSize / 2
texture.repeat.set(repeats, repeats)
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
})
const mesh = new THREE.Mesh(planeGeo, planeMat)
mesh.rotation.x = Math.PI * -0.5
scene.add(mesh)
}
{
const cubeSize = 2
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
const mesh = new THREE.Mesh(cubeGeo, cubeMat)
mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
scene.add(mesh)
}
{
const sphereRadius = 1
const sphereWidthDivisions = 32
const sphereHeightDivisions = 16
const sphereGeo = new THREE.SphereGeometry(
sphereRadius,
sphereWidthDivisions,
sphereHeightDivisions,
)
const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
const mesh = new THREE.Mesh(sphereGeo, sphereMat)
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
scene.add(mesh)
}
{
const color = 0xffffff
const intensity = 3
const light = new THREE.DirectionalLight(color, intensity)
light.position.set(0, 10, 0)
light.target.position.set(-5, 0, 0)
scene.add(light)
scene.add(light.target)
}
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function setScissorForElement(elem: Element) {
if (!canvas) {
return
}
const canvasRect = canvas.getBoundingClientRect()
const elemRect = elem.getBoundingClientRect()
// compute a canvas relative rectangle
const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left
const left = Math.max(0, elemRect.left - canvasRect.left)
const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top
const top = Math.max(0, elemRect.top - canvasRect.top)
const width = Math.min(canvasRect.width, right - left)
const height = Math.min(canvasRect.height, bottom - top)
// setup the scissor to only render to that part of the canvas
const positiveYUpBottom = canvasRect.height - bottom
renderer.setScissor(left, positiveYUpBottom, width, height)
renderer.setViewport(left, positiveYUpBottom, width, height)
// return the aspect
return width / height
}
function render() {
resizeRendererToDisplaySize(renderer)
// turn on the scissor
renderer.setScissorTest(true)
if (domeView1) {
const aspect = setScissorForElement(domeView1)
// adjust the camera for this aspect
camera.aspect = aspect
camera.updateProjectionMatrix()
cameraHelp.update()
// don't draw the camera helper in the original view
cameraHelp.visible = false
scene.background.set(0x000000)
// render
renderer.render(scene, camera)
}
// render from the 2nd camera
if (domeView2) {
const aspect = setScissorForElement(domeView2)
// adjust the camera for this aspect
camera2.aspect = aspect
camera2.updateProjectionMatrix()
// draw the camera helper in the 2nd view
cameraHelp.visible = true
scene.background.set(0x000040)
renderer.render(scene, camera2)
}
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}
2.2 摄像机阵列(ArrayCamera)
ArrayCamera 用于更加高效地使用一组已经预定义的摄像机来渲染一个场景。这将能够更好地提升VR场景的渲染性能。
一个 ArrayCamera 的实例中总是包含着一组子摄像机,应当为每一个子摄像机定义viewport(视口)这个属性,这一属性决定了由该子摄像机所渲染的视口区域的大小。
- .cameras : Array
摄像机数组。 - .isArrayCamera : Boolean
Read-only flag to check if a given object is of type ArrayCamera.
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
const ASPECT_RATIO = window.innerWidth / window.innerHeight
const WIDTH = (window.innerWidth / 2) * window.devicePixelRatio
const HEIGHT = (window.innerHeight / 2) * window.devicePixelRatio
const cameras = []
for (let y = 0; y < 2; y++) {
for (let x = 0; x < 2; x++) {
const subcamera = new THREE.PerspectiveCamera(
40,
window.innerWidth / window.innerHeight,
0.1,
10,
)
subcamera.viewport = new THREE.Vector4(
Math.floor(x * WIDTH),
Math.floor(y * HEIGHT),
Math.ceil(WIDTH),
Math.ceil(HEIGHT),
)
subcamera.position.x = x / 2 - 0.5
subcamera.position.y = 0.5 - y / 2
subcamera.position.z = 1.5
subcamera.position.multiplyScalar(2)
subcamera.lookAt(0, 0, 0)
subcamera.updateMatrixWorld()
cameras.push(subcamera)
const controls = new OrbitControls(subcamera, canvas)
controls.target.set(0, 5, 0)
controls.update()
}
}
const camera = new THREE.ArrayCamera(cameras)
camera.position.z = 3
const scene = new THREE.Scene()
scene.background = new THREE.Color('black')
const color = 0xffffff
const intensity = 1
scene.add(new THREE.AmbientLight(color, intensity))
const light = new THREE.DirectionalLight(0xffffff, 3)
light.position.set(0.5, 0.5, 1)
light.castShadow = true
light.shadow.camera.zoom = 4 // tighter shadow map
scene.add(light)
// const video = document.getElementById('video')
// const texture = new THREE.VideoTexture(video)
// const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, map: texture })
// const cubes: THREE.Mesh[] = [] // just an array we can use to rotate the cubes
// const cube = new THREE.Mesh(geometry, material)
{
const planeSize = 40
const loader = new THREE.TextureLoader()
const texture = loader.load('https://threejs.org/manual/examples/resources/images/checker.png')
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.magFilter = THREE.NearestFilter
texture.colorSpace = THREE.SRGBColorSpace
const repeats = planeSize / 2
texture.repeat.set(repeats, repeats)
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
})
const mesh = new THREE.Mesh(planeGeo, planeMat)
mesh.rotation.x = Math.PI * -0.5
scene.add(mesh)
}
// cubes.push(mesh)
{
const cubeSize = 2
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
const mesh = new THREE.Mesh(cubeGeo, cubeMat)
mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
scene.add(mesh)
}
{
const sphereRadius = 1
const sphereWidthDivisions = 32
const sphereHeightDivisions = 16
const sphereGeo = new THREE.SphereGeometry(
sphereRadius,
sphereWidthDivisions,
sphereHeightDivisions,
)
const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
const mesh = new THREE.Mesh(sphereGeo, sphereMat)
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
scene.add(mesh)
}
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
renderer.render(scene, camera)
}
2.3 立方相机(CubeCamera)
创建6个渲染到WebGLCubeRenderTarget的摄像机。
- near -- 近剪切面的距离
- far -- 远剪切面的距离
- renderTarget -- The destination cube render target.
- .renderTarget : WebGLCubeRenderTarget
The destination cube render target. - .update ( renderer : WebGLRenderer, scene : Scene ) : undefined
- renderer -- 当前的WebGL渲染器
- scene -- 当前的场景
这个方法用来更新renderTarget(渲染目标对象)。
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
const scene = new THREE.Scene()
scene.background = new THREE.Color('black')
scene.rotation.y = 0.5 // avoid flying objects occluding the sun
const fov = 45
const aspect = 2 // the canvas default
const near = 5
const far = 100
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 10, 20)
// Create cube render target
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(128, {
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter,
})
cubeRenderTarget.texture.type = THREE.HalfFloatType
// Create cube camera
const cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget)
scene.add(camera)
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 5, 0)
controls.update()
const material2 = new THREE.MeshStandardMaterial({
roughness: 0.1,
metalness: 0,
})
const cube = new THREE.Mesh(new THREE.BoxGeometry(15, 15, 15), material2)
scene.add(cube)
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
cubeCamera.update(renderer, scene)
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
const color = 0xffffff
const intensity = 1
const light = new THREE.AmbientLight(color, intensity)
scene.add(light)
renderer.render(scene, camera)
}
2.4 正交相机(OrthographicCamera)
这一摄像机使用orthographic projection(正交投影)来进行投影。
在这种投影模式下,无论物体距离相机距离远或者近,在最终渲染的图片中物体的大小都保持不变。
这对于渲染2D场景或者UI元素是非常有用的。
- left --- 摄像机视锥体左侧面。
- right --- 摄像机视锥体右侧面。
- top --- 摄像机视锥体上侧面。
- bottom --- 摄像机视锥体下侧面。
- near --- 摄像机视锥体近端面。
- far --- 摄像机视锥体远端面。
属性:
- .bottom : Float
摄像机视锥体下侧面。 - .far : Float
摄像机视锥体远端面,其默认值为2000。
该值必须大于near plane(摄像机视锥体近端面)的值。 - .isOrthographicCamera : Boolean
Read-only flag to check if a given object is of type OrthographicCamera. - .left : Float
摄像机视锥体左侧面。 - .near : Float
摄像机视锥体近端面。其默认值为0.1.
其值的有效范围介于0和far(摄像机视锥体远端面)之间。
请注意,和PerspectiveCamera不同,0对于OrthographicCamera的近端面来说是一个有效值。 - .right : Float
摄像机视锥体右侧面。 - .top : Float
摄像机视锥体上侧面。 - .view : Object
这个值是由setViewOffset来设置的,其默认值为null。 - .zoom : number
获取或者设置摄像机的缩放倍数,其默认值为1。
方法:
- .setViewOffset ( fullWidth : Float, fullHeight : Float, x : Float, y : Float, width : Float, height : Float ) : undefined
fullWidth --- 多视图的全宽设置
fullHeight --- 多视图的全高设置
x --- 副摄像机的水平偏移
y --- 副摄像机的垂直偏移
width --- 副摄像机的宽度
height --- 副摄像机的高度
在较大的viewing frustum(视锥体)中设置偏移量,对于多窗口或者多显示器的设置是很有用的。 对于如何使用它,请查看PerspectiveCamera中的示例。 - .clearViewOffset () : undefined
清除任何由.setViewOffset设置的偏移量。 - .updateProjectionMatrix () : undefined
更新摄像机投影矩阵。在任何参数被改变以后必须被调用。 - .toJSON (meta : Object) : Object
meta -- 包含有元数据的对象,例如对象后代中的纹理或图像
将摄像机转换为 three.js JSON Object/Scene format(three.js JSON 物体/场景格式)。
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
const scene = new THREE.Scene()
scene.background = new THREE.Color('black')
scene.rotation.y = 0.5 // avoid flying objects occluding the sun
const camera = new THREE.OrthographicCamera(
window.innerWidth / -2,
window.innerWidth / 2,
window.innerHeight / 2,
window.innerHeight / -2,
1,
1000,
)
camera.position.set(0, 10, 20)
// Create cube render target
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(128, {
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter,
})
cubeRenderTarget.texture.type = THREE.HalfFloatType
// Create cube camera
const cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget)
scene.add(camera)
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 5, 0)
controls.update()
{
const planeSize = 40
const loader = new THREE.TextureLoader()
const texture = loader.load('https://threejs.org/manual/examples/resources/images/checker.png')
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.magFilter = THREE.NearestFilter
texture.colorSpace = THREE.SRGBColorSpace
const repeats = planeSize / 2
texture.repeat.set(repeats, repeats)
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
})
const mesh = new THREE.Mesh(planeGeo, planeMat)
mesh.rotation.x = Math.PI * -0.5
scene.add(mesh)
}
{
const cubeSize = 2
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
const mesh = new THREE.Mesh(cubeGeo, cubeMat)
mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
scene.add(mesh)
}
{
const sphereRadius = 1
const sphereWidthDivisions = 32
const sphereHeightDivisions = 16
const sphereGeo = new THREE.SphereGeometry(
sphereRadius,
sphereWidthDivisions,
sphereHeightDivisions,
)
const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
const mesh = new THREE.Mesh(sphereGeo, sphereMat)
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
scene.add(mesh)
}
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
cubeCamera.update(renderer, scene)
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
const color = 0xffffff
const intensity = 1
const light = new THREE.AmbientLight(color, intensity)
scene.add(light)
renderer.render(scene, camera)
}
2.5 透视相机(PerspectiveCamera)
这一摄像机使用perspective projection(透视投影)来进行投影。
这一投影模式被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。
- fov --- 摄像机视锥体垂直视野角度
- aspect --- 摄像机视锥体长宽比
- near --- 摄像机视锥体近端面
- far --- 摄像机视锥体远端面
属性:
- .aspect : Float
摄像机视锥体的长宽比,通常是使用画布的宽/画布的高。默认值是1(正方形画布)。 - .far : Float
摄像机的远端面,默认值是2000。
该值必须大于near plane(摄像机视锥体近端面)的值。 - .filmGauge : Float
胶片尺寸,其默认值为35(毫米)。 这个参数不会影响摄像机的投影矩阵,除非.filmOffset被设置为了一个非零的值。 - .filmOffset : Float
水平偏离中心偏移量,和.filmGauge单位相同。默认值为0。 - .focus : Float
用于立体视觉和景深效果的物体的距离。 这个参数不会影响摄像机的投影矩阵,除非使用了StereoCamera。 默认值是10。 - .fov : Float
摄像机视锥体垂直视野角度,从视图的底部到顶部,以角度来表示。默认值是50。 - .isPerspectiveCamera : Boolean
Read-only flag to check if a given object is of type PerspectiveCamera. - .near : Float
摄像机的近端面,默认值是0.1。
其有效值范围是0到当前摄像机far plane(远端面)的值之间。 请注意,和OrthographicCamera不同,0对于PerspectiveCamera的近端面来说不是一个有效值。 - .view : Object
Frustum window specification or null. 这个值使用.setViewOffset方法来进行设置,使用.clearViewOffset方法来进行清除。 - .zoom : number
获取或者设置摄像机的缩放倍数,其默认值为1。
方法: - .clearViewOffset () : undefined
清除任何由.setViewOffset设置的偏移量。 - .getEffectiveFOV () : Float
结合.zoom(缩放倍数),以角度返回当前垂直视野角度。 - .getFilmHeight () : Float
返回当前胶片上图像的高,如果.aspect小于或等于1(肖像格式、纵向构图),则结果等于.filmGauge。 - .getFilmWidth () : Float
返回当前胶片上图像的宽,如果.aspect大于或等于1(景观格式、横向构图),则结果等于.filmGauge。 - .getFocalLength () : Float
返回当前.fov(视野角度)相对于.filmGauge(胶片尺寸)的焦距。 - .setFocalLength ( focalLength : Float ) : undefined
通过相对于当前.filmGauge的焦距,设置FOV。
默认情况下,焦距是为35mm(全画幅)摄像机而指定的。 - .setViewOffset ( fullWidth : Float, fullHeight : Float, x : Float, y : Float, width : Float, height : Float ) : undefined
fullWidth --- 多视图的全宽设置
fullHeight --- 多视图的全高设置
x --- 副摄像机的水平偏移
y --- 副摄像机的垂直偏移
width --- 副摄像机的宽度
height --- 副摄像机的高度
在较大的viewing frustum(视锥体)中设置偏移量,对于多窗口或者多显示器的设置是很有用的。
例如,如果你有一个3x2的显示器阵列,每个显示器分辨率都是1920x1080,且这些显示器排列成像这样的网格:
±--±--±--+
| A | B | C |
±--±--±--+
| D | E | F |
±--±--±--+
那对于每个显示器,你可以这样来设置、调用:
javascript
const w = 1920;
const h = 1080;
const fullWidth = w * 3;
const fullHeight = h * 2;
// A
camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
// B
camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
// C
camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
// D
camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
// E
camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
// F
camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );请注意,显示器的不必具有相同的大小,或者不必在网格中。
- .updateProjectionMatrix () : undefined
更新摄像机投影矩阵。在任何参数被改变以后必须被调用。 - .toJSON (meta : Object) : Object
meta -- 包含有元数据的对象,例如对象后代中的纹理或图像将摄像机转换为 three.js JSON Object/Scene format(three.js JSON 物体/场景格式)。
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
const fov = 45
const aspect = 2 // canvas 的默认宽高 300:150
const near = 0.1
const far = 100
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 10, 20)
const scene = new THREE.Scene()
scene.background = new THREE.Color('black')
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 5, 0)
controls.update()
// const video = document.getElementById('video')
// const texture = new THREE.VideoTexture(video)
// const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, map: texture })
// const cubes: THREE.Mesh[] = [] // just an array we can use to rotate the cubes
// const cube = new THREE.Mesh(geometry, material)
{
const planeSize = 40
const loader = new THREE.TextureLoader()
const texture = loader.load('https://threejs.org/manual/examples/resources/images/checker.png')
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.magFilter = THREE.NearestFilter
texture.colorSpace = THREE.SRGBColorSpace
const repeats = planeSize / 2
texture.repeat.set(repeats, repeats)
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
})
const mesh = new THREE.Mesh(planeGeo, planeMat)
mesh.rotation.x = Math.PI * -0.5
scene.add(mesh)
}
// cubes.push(mesh)
{
const cubeSize = 2
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
const mesh = new THREE.Mesh(cubeGeo, cubeMat)
mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
scene.add(mesh)
}
{
const sphereRadius = 1
const sphereWidthDivisions = 32
const sphereHeightDivisions = 16
const sphereGeo = new THREE.SphereGeometry(
sphereRadius,
sphereWidthDivisions,
sphereHeightDivisions,
)
const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
const mesh = new THREE.Mesh(sphereGeo, sphereMat)
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
scene.add(mesh)
}
const color = 0xffffff
const intensity = 150
const light = new THREE.SpotLight(color, intensity)
light.position.set(0, 10, 0)
light.target.position.set(-5, -10, 0)
scene.add(light)
scene.add(light.target)
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}
2.6 立体相机(StereoCamera)
透视摄像机(立体相机)常被用于创建3D Anaglyph(3D立体影像) 或者Parallax Barrier(视差屏障)。
属性:
- .aspect : Float
默认值是1. - .eyeSep : Float
默认值是0.064. - .cameraL : PerspectiveCamera
左摄像机,它被加入到了layer 1中 ------ 需要被左摄像机渲染的物体也应当要加入到这一层中。 - .cameraR : PerspectiveCamera
右摄像机,它被加入到了layer 2中 ------ 需要被右摄像机渲染的物体也应当要加入到这一层中。
方法 - .update ( camera : PerspectiveCamera ) : undefined
基于摄像机通过场景,更新立体摄像机。
javascript
import * as THREE from 'three'
import { StereoEffect } from 'three/addons/effects/StereoEffect.js'
export default () => {
const spheres: THREE.Mesh[] = []
let mouseX = 0,
mouseY = 0
let windowHalfX = window.innerWidth / 2
let windowHalfY = window.innerHeight / 2
function onDocumentMouseMove(event: MouseEvent) {
mouseX = (event.clientX - windowHalfX) * 10
mouseY = (event.clientY - windowHalfY) * 10
}
document.addEventListener('mousemove', onDocumentMouseMove)
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000)
camera.position.z = 3200
const scene = new THREE.Scene()
scene.background = new THREE.Color('black')
const geometry = new THREE.SphereGeometry(100, 32, 16)
const material = new THREE.MeshBasicMaterial({
color: 0xff00dd,
refractionRatio: 0.95,
})
for (let i = 0; i < 500; i++) {
const mesh = new THREE.Mesh(geometry, material)
mesh.position.x = Math.random() * 10000 - 5000
mesh.position.y = Math.random() * 10000 - 5000
mesh.position.z = Math.random() * 10000 - 5000
mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1
scene.add(mesh)
spheres.push(mesh)
}
const effect = new StereoEffect(renderer)
effect.setSize(window.innerWidth, window.innerHeight)
const color = 0xffffff
const intensity = 150
const light = new THREE.SpotLight(color, intensity)
light.position.set(0, 10, 0)
light.target.position.set(-5, -10, 0)
scene.add(light)
scene.add(light.target)
function render() {
const timer = 0.0001 * Date.now()
camera.position.x += (mouseX - camera.position.x) * 0.05
camera.position.y += (-mouseY - camera.position.y) * 0.05
camera.lookAt(scene.position)
for (let i = 0, il = spheres.length; i < il; i++) {
const sphere = spheres[i]
sphere.position.x = 5000 * Math.cos(timer + i)
sphere.position.y = 5000 * Math.sin(timer + i * 1.1)
}
effect.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}
三、阴影相关内容
阴影贴图的工作方式就是具有投射阴影的光能对所有能被投射阴影的物体从光源渲染阴影。
3.1 LightShadow该类作为其他阴影类的基类来使用。
创建一个新的LightShadow。这不能直接调用的 - 它由其他阴影用作基类。
属性:
- .autoUpdate : Boolean
Enables automatic updates of the light's shadow. Default is true. If you do not require dynamic lighting / shadows, you may set this to false. - .camera : Camera
光的世界里。这用于生成场景的深度图;从光的角度来看,其他物体背后的物体将处于阴影中。 - .bias : Float
阴影贴图偏差,在确定曲面是否在阴影中时,从标准化深度添加或减去多少。
默认值为0.此处非常小的调整(大约0.0001)可能有助于减少阴影中的伪影 - .blurSamples : Integer
The amount of samples to use when blurring a VSM shadow map. - .map : WebGLRenderTarget
使用内置摄像头生成的深度图;超出像素深度的位置在阴影中。在渲染期间内部计算。 - .mapPass : WebGLRenderTarget
The distribution map generated using the internal camera; an occlusion is calculated based on the distribution of depths. Computed internally during rendering. - .mapSize : Vector2
一个Vector2定义阴影贴图的宽度和高度。
较高的值会以计算时间为代价提供更好的阴影质量。值必须是2的幂,直到给定设备的WebGLRenderer.capabilities.maxTextureSize, 虽然宽度和高度不必相同(例如,(512,1024)有效)。 默认值为(512,512)。 - .matrix : Matrix4
模拟阴影相机空间,计算阴影贴图中的位置和深度。存储在Matrix4中。这是在渲染期间内部计算的。 - .needsUpdate : Boolean
When set to true, shadow maps will be updated in the next render call. Default is false. If you have set .autoUpdate to false, you will need to set this property to true and then make a render call to update the light's shadow. - .normalBias : Float
Defines how much the position used to query the shadow map is offset along the object normal. The default is 0. Increasing this value can be used to reduce shadow acne especially in large scenes where light shines onto geometry at a shallow angle. The cost is that shadows may appear distorted. - .radius : Float
将此值设置为大于1的值将模糊阴影的边缘。
较高的值会在阴影中产生不必要的条带效果 - 更大的mapSize将允许在这些效果变得可见之前使用更高的值。
If WebGLRenderer.shadowMap.type is set to PCFSoftShadowMap, radius has no effect and it is recommended to increase softness by decreasing mapSize instead.
请注意,如果[page:WebGLRenderer.shadowMap.type]设置为BasicShadowMap,将会无效。
方法:
- .getFrameExtents () : Vector2
Used internally by the renderer to extend the shadow map to contain all viewports - .updateMatrices ( light : Light ) : undefined
Update the matrices for the camera and shadow, used internally by the renderer.
light -- the light for which the shadow is being rendered. - .getFrustum () : Frustum
Gets the shadow cameras frustum. Used internally by the renderer to cull objects. - .getViewportCount () : number
Used internally by the renderer to get the number of viewports that need to be rendered for this shadow. - .copy ( source : LightShadow ) : this
将source中的所有属性的值复制到该Light。 - .clone () : LightShadow
克隆与此相同属性的新LightShadow。 - .toJSON () : Object
序列化这个LightShadow。
javascript
import * as THREE from 'three'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
const fov = 45
const aspect = 2 // the canvas default
const near = 0.1
const far = 100
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 10, 20)
camera.lookAt(0, 0, 0)
const scene = new THREE.Scene()
scene.background = new THREE.Color('#fff')
const loader = new THREE.TextureLoader()
{
const planeSize = 40
const texture = loader.load('https://threejs.org/manual/examples/resources/images/checker.png')
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.magFilter = THREE.NearestFilter
texture.colorSpace = THREE.SRGBColorSpace
const repeats = planeSize / 2
texture.repeat.set(repeats, repeats)
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
const planeMat = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
})
planeMat.color.setRGB(1.5, 1.5, 1.5)
const mesh = new THREE.Mesh(planeGeo, planeMat)
mesh.rotation.x = Math.PI * -1.5
scene.add(mesh)
}
const shadowTexture = loader.load(
'https://threejs.org/manual/examples/resources/images/roundshadow.png',
)
interface SphereShadow {
base: THREE.Object3D
sphereMesh: THREE.Mesh
shadowMesh: THREE.Mesh
y: number
}
const sphereShadowBases: SphereShadow[] = []
{
const sphereRadius = 1
const sphereWidthDivisions = 32
const sphereHeightDivisions = 16
const sphereGeo = new THREE.SphereGeometry(
sphereRadius,
sphereWidthDivisions,
sphereHeightDivisions,
)
const planeSize = 1
const shadowGeo = new THREE.PlaneGeometry(planeSize, planeSize)
const numSpheres = 15
for (let i = 0; i < numSpheres; ++i) {
// make a base for the shadow and the sphere.
// so they move together.
const base = new THREE.Object3D()
scene.add(base)
// add the shadow to the base
// note: we make a new material for each sphere
// so we can set that sphere's material transparency
// separately.
const shadowMat = new THREE.MeshBasicMaterial({
map: shadowTexture,
transparent: true, // so we can see the ground
depthWrite: false, // so we don't have to sort
})
const shadowMesh = new THREE.Mesh(shadowGeo, shadowMat)
shadowMesh.position.y = 0.001 // so we're above the ground slightly
shadowMesh.rotation.x = Math.PI * -0.5
const shadowSize = sphereRadius * 4
shadowMesh.scale.set(shadowSize, shadowSize, shadowSize)
base.add(shadowMesh)
// add the sphere to the base
const u = i / numSpheres
const sphereMat = new THREE.MeshPhongMaterial()
sphereMat.color.setHSL(u, 1, 0.75)
const sphereMesh = new THREE.Mesh(sphereGeo, sphereMat)
sphereMesh.position.set(0, sphereRadius + 2, 0)
base.add(sphereMesh)
// remember all 3 plus the y position
sphereShadowBases.push({ base, sphereMesh, shadowMesh, y: sphereMesh.position.y })
}
}
{
const skyColor = 0xb1e1ff // light blue
const groundColor = 0xb97a20 // brownish orange
const intensity = 0.75
const light = new THREE.HemisphereLight(skyColor, groundColor, intensity)
scene.add(light)
}
{
const color = 0xffffff
const intensity = 2.5
const light = new THREE.DirectionalLight(color, intensity)
light.position.set(0, 10, 5)
light.target.position.set(-5, 0, 0)
scene.add(light)
scene.add(light.target)
}
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render(time: number) {
time *= 0.001 // convert to seconds
resizeRendererToDisplaySize(renderer)
{
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
sphereShadowBases.forEach((sphereShadowBase, ndx) => {
const { base, sphereMesh, shadowMesh, y } = sphereShadowBase
// u is a value that goes from 0 to 1 as we iterate the spheres
const u = ndx / sphereShadowBases.length
// compute a position for there base. This will move
// both the sphere and its shadow
const speed = time * 0.2
const angle = speed + u * Math.PI * 2 * (ndx % 1 ? 1 : -1)
const radius = Math.sin(speed - ndx) * 10
base.position.set(Math.cos(angle) * radius, 0, Math.sin(angle) * radius)
// yOff is a value that goes from 0 to 1
const yOff = Math.abs(Math.sin(time * 2 + ndx))
// move the sphere up and down
sphereMesh.position.y = y + THREE.MathUtils.lerp(-2, 2, yOff)
// fade the shadow as the sphere goes up
shadowMesh.material.opacity = THREE.MathUtils.lerp(1, 0.25, yOff)
})
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}
3.2 PointLightShadow该类在内部由PointLights所使用,以用于计算阴影。
- .isPointLightShadow : Boolean
Read-only flag to check if a given object is of type PointLightShadow. - .updateMatrices ( light : Light, viewportIndex : number) : undefined
Update the matrices for the camera and shadow, used internally by the renderer.
light -- the light for which the shadow is being rendered.
viewportIndex -- calculates the matrix for this viewport
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap // default THREE.PCFShadowMap
const fov = 45
const aspect = 2 // the canvas default
const near = 0.1
const far = 100
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 10, 20)
camera.lookAt(0, 0, 0)
const scene = new THREE.Scene()
// scene.background = new THREE.Color('#fff')
//Create a PointLight and turn on shadows for the light
const light = new THREE.PointLight(0xffffff, 1, 100)
light.position.set(0, 10, 4)
light.castShadow = true // default false
scene.add(light)
//Set up shadow properties for the light
light.shadow.mapSize.width = 512 // default
light.shadow.mapSize.height = 512 // default
light.shadow.camera.near = 0.5 // default
light.shadow.camera.far = 500 // default
//Create a sphere that cast shadows (but does not receive them)
const sphereGeometry = new THREE.SphereGeometry(5, 32, 32)
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
sphere.castShadow = true //default is false
sphere.receiveShadow = false //default
scene.add(sphere)
//Create a plane that receives shadows (but does not cast them)
const planeGeometry = new THREE.PlaneGeometry(20, 20, 32, 32)
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.receiveShadow = true
scene.add(plane)
//Create a helper for the shadow camera (optional)
const helper = new THREE.CameraHelper(light.shadow.camera)
scene.add(helper)
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render(time: number) {
time *= 0.001 // convert to seconds
resizeRendererToDisplaySize(renderer)
{
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
renderer.render(scene, camera)
requestAnimationFrame(render)
}
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 5, 0)
controls.update()
requestAnimationFrame(render)
}
3.3 DirectionalLightShadow这是用于在DirectionalLights内部计算阴影
与其他阴影类不同,它是使用OrthographicCamera来计算阴影,而不是PerspectiveCamera。这是因为来自DirectionalLight的光线是平行的。
- .camera : Camera
在光的世界里。这用于生成场景的深度图;从光的角度来看,其他物体背后的物体将处于阴影中。 - .isDirectionalLightShadow : Boolean
Read-only flag to check if a given object is of type DirectionalLightShadow.
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap // default THREE.PCFShadowMap
const fov = 45
const aspect = 2 // the canvas default
const near = 0.1
const far = 100
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 10, 20)
camera.lookAt(0, 0, 0)
const scene = new THREE.Scene()
// scene.background = new THREE.Color('#fff')
//Create a DirectionalLight and turn on shadows for the light
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(0, 1, 0) //default; light shining from top
light.castShadow = true // default false
scene.add(light)
//Set up shadow properties for the light
light.shadow.mapSize.width = 512 // default
light.shadow.mapSize.height = 512 // default
light.shadow.camera.near = 0.5 // default
light.shadow.camera.far = 500 // default
//Create a sphere that cast shadows (but does not receive them)
const sphereGeometry = new THREE.SphereGeometry(5, 32, 32)
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
sphere.castShadow = true //default is false
sphere.receiveShadow = false //default
scene.add(sphere)
//Create a plane that receives shadows (but does not cast them)
const planeGeometry = new THREE.PlaneGeometry(20, 20, 32, 32)
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.receiveShadow = true
scene.add(plane)
//Create a helper for the shadow camera (optional)
const helper = new THREE.CameraHelper(light.shadow.camera)
scene.add(helper)
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render(time: number) {
time *= 0.001 // convert to seconds
resizeRendererToDisplaySize(renderer)
{
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
renderer.render(scene, camera)
requestAnimationFrame(render)
}
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 5, 0)
controls.update()
requestAnimationFrame(render)
}
3.4 SpotLightShadow 这在SpotLights内部用于计算阴影。
- .camera : Camera
在光的世界里。这用于生成场景的深度图;从光的角度来看,其他物体背后的物体将处于阴影中。
默认值为PerspectiveCamera,近剪裁平面为0.5。 fov将通过更新方法跟踪拥有SpotLight的角度属性。同样,aspect属性将跟踪mapSize的方面。如果设置了灯光的距离属性,则远剪裁平面将跟踪该值,否则默认为500。 - .focus : Number
Used to focus the shadow camera. The camera's field of view is set as a percentage of the spotlight's field-of-view. Range is [0, 1]. Default is 1.0. - .isSpotLightShadow : Boolean
Read-only flag to check if a given object is of type SpotLightShadow.
javascript
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.querySelector('#demo')
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas,
})
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap // default THREE.PCFShadowMap
const fov = 45
const aspect = 2 // the canvas default
const near = 0.1
const far = 100
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 10, 20)
camera.lookAt(0, 0, 0)
const scene = new THREE.Scene()
scene.background = new THREE.Color('#fff')
//Create a SpotLight and turn on shadows for the light
const light = new THREE.SpotLight(0xffffff)
light.castShadow = true // default false
scene.add(light)
//Set up shadow properties for the light
light.shadow.mapSize.width = 512 // default
light.shadow.mapSize.height = 512 // default
light.shadow.camera.near = 0.5 // default
light.shadow.camera.far = 500 // default
light.shadow.focus = 1 // default
//Create a sphere that cast shadows (but does not receive them)
const sphereGeometry = new THREE.SphereGeometry(5, 32, 32)
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
sphere.castShadow = true //default is false
sphere.receiveShadow = false //default
scene.add(sphere)
//Create a plane that receives shadows (but does not cast them)
const planeGeometry = new THREE.PlaneGeometry(20, 20, 32, 32)
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.receiveShadow = true
scene.add(plane)
//Create a helper for the shadow camera (optional)
const helper = new THREE.CameraHelper(light.shadow.camera)
scene.add(helper)
function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
function render(time: number) {
time *= 0.001 // convert to seconds
resizeRendererToDisplaySize(renderer)
{
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
renderer.render(scene, camera)
requestAnimationFrame(render)
}
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 5, 0)
controls.update()
requestAnimationFrame(render)
}