ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

three.js学习笔记(五)——Shadows阴影

2021-12-03 23:32:16  阅读:338  来源: 互联网

标签:Shadows const THREE three camera new shadow js 阴影


阴影一直是实时三维渲染的挑战,开发人员必须在合理的情况下找到显示真实阴影的技巧。
Three.js 有一个内置的解决方案,虽然其并不完美,但用起来很方便。

阴影是怎么工作的?

当你进行一次渲染时,Three.js将对每个支持阴影的光线进行渲染,那些渲染会像摄像机那样模拟光线所看到的内容,而在这些灯光渲染下,网格材质将被深度网格材质MeshDepthMaterial所替代。
灯光渲染将像纹理一样被存储起来,称为阴影贴图,之后它们会被用于每个支持接收阴影的材质并投射到几何体上。

激活阴影

1.想要激活并使用阴影,就得先在渲染器renderer.shadowMap.enabled属性中设置开启,允许在场景中使用阴影贴图

renderer.shadowMap.enabled = true

2.检查每个对象,确定它是否可以使用castshadow投射阴影,以及是否可以使用receiveshadow接收阴影。
现在我们的场景里有一个球体和一块平面,光源有环境光和平行光。
在这里插入图片描述
设置球体可以投射阴影,平面可以接收阴影

sphere.castShadow = true
plane.receiveShadow = true

然后使用castShadow激活灯光上的阴影

directionalLight.castShadow = true

注意:只有平行光、点光源和聚光灯支持阴影
在这里插入图片描述

优化阴影贴图

优化渲染尺寸

我们可以在每个灯光的阴影属性中访问阴影贴图

console.log(directionalLight.shadow);

在这里插入图片描述
可以看到,默认的贴图尺寸是512x512,我们可以设置其为2的n次幂,因为这涉及到mip映射。之后会发现当数值越高,阴影拥有越清晰的细节,数值越低,阴影越模糊

directionalLight.shadow.mapSize.width = 1024
directionalLight.shadow.mapSize.height = 1024

近与远

上面说到Three.js使用灯光摄像机进行阴影贴图渲染。这些相机具有相同的属性,像nearfar
为了方便调试,我们可以往场景中添加摄像机辅助对象(摄像机助手),要做的就是把平行光用于渲染阴影的灯光摄像机directionalLight.shadow.camera给添加到摄像机助手中

const directionalLightCameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera)
scene.add(directionalLightCameraHelper)

可以看到红线交叉处是我们的平行光光源,正方形矩形部分是near值,而far处于非常远的地方,这就是性能需要优化的地方
在这里插入图片描述
接下来改变平行光渲染阴影的灯光摄像机可视范围的远近值

directionalLight.shadow.camera.near = 2
directionalLight.shadow.camera.far = 6

虽然阴影没有什么变化,但至少减少了些性能消耗
在这里插入图片描述

振幅amplitude

通过观察上图,使用相机助手后我们可以发现灯光相机所看到的区域还是太大了,溢出了不少。因为我们正在使用的是平行光,它使用的是正交相机OrthographicCamera
所以我们可以通过正交相机的toprightbottomleft四个属性来控制摄像机视锥体的哪一边可以看多远距离。

directionalLight.shadow.camera.top = 2
directionalLight.shadow.camera.right = 2
directionalLight.shadow.camera.left = -2
directionalLight.shadow.camera.bottom = -2

可以观察到现在的阴影跟前面没有调整相机前的阴影相比较起来,细节程度有所提高
在这里插入图片描述
灯光相机的可视范围越小,阴影越精确,当然如果设置得实在太小,阴影将会被裁剪掉

// 当相机far值过小,阴影被裁剪掉
directionalLight.shadow.camera.far = 3.8

在这里插入图片描述

模糊

我们可以通过radius属性控制阴影模糊程度,它不会改变灯光相机与物体的距离。

directionalLight.shadow.radius = 10

在这里插入图片描述

阴影贴图算法

有不同类型的算法可以应用于阴影贴图
THREE.BasicShadowMap-性能非常好但是质量很差
THREE.PCFShadowMap-性能较差但边缘更平滑(默认)
THREE.PCFSoftShadowMap-性能较差但边缘更柔和
THREE.VSMShadowMap-性能差,约束多,但能够产生意想不到的效果。

PCF柔软阴影贴图

// PCF柔软阴影贴图
renderer.shadowMap.type = THREE.PCFSoftShadowMap

radius不会在该类型中生效
在这里插入图片描述

聚光灯阴影

添加聚光灯

// 聚光灯
const spotLight = new THREE.SpotLight(0xffffff,0.4,10,Math.PI*0.3)
spotLight.castShadow = true
spotLight.position.set(0,2,2)
scene.add(spotLight)
// 如果要使聚光灯看向某处记得把target添加场景中
scene.add(spotLight.target)

添加相机助手

const spotLightCameraHelper = new THREE.CameraHelper(spotLight.shadow.camera)
scene.add(spotLightCameraHelper)

可以观察到混合阴影
在这里插入图片描述

优化聚光灯阴影贴图

和前面优化平行光阴影贴图一样。

spotLight.shadow.mapSize.width = 1024
spotLight.shadow.mapSize.height = 1024
spotLight.shadow.camera.near = 1
spotLight.shadow.camera.far = 6

但因为它是聚光灯,使用的是透视相机PerspectiveCamera,所以可以通过fov属性改变摄像机视锥体垂直视野角度

spotLight.shadow.camera.fov = 30

在这里插入图片描述

点光源阴影

添加点光源

//点光源
const pointLight = new THREE.PointLight(0xffffff,0.3)
pointLight.castShadow = true
pointLight.position.set(-1,1,0)
scene.add(pointLight)

添加相机助手

const pointLightCameraHelper = new THREE.CameraHelper(pointLight.shadow.camera)
scene.add(pointLightCameraHelper)

在这里插入图片描述

优化点光源阴影

pointLight.shadow.mapSize.width = 1024
pointLight.shadow.mapSize.height = 1024
pointLight.shadow.camera.near = 0.1
pointLight.shadow.camera.far = 5

在这里插入图片描述

点光源摄像机使用的也是透视相机,但最好不要去改变它的视锥体垂直视野角度fov属性

烘焙阴影

烘培阴影是Three.js阴影的一个很好的替代品。我们可以将阴影集成到纹理中,并将其应用到材质上。
先关闭渲染器的阴影贴图渲染,之后就看不到场景中的阴影了

renderer.shadowMap.enabled = false

然后我们设置平面的材质纹理为烘培阴影贴图
在这里插入图片描述
加载纹理贴图

// Textures
const textureLoader = new THREE.TextureLoader()
const bakedShadow = textureLoader.load('/textures/bakedShadow.jpg')

平面使用基础网格材质(MeshBasicMaterial)并应用烘培阴影纹理贴图

const plane = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(5, 5),
    new THREE.MeshBasicMaterial({map:bakedShadow})
)

在这里插入图片描述
这种方案适用于静态物体,因为当物体位置有所变化后,阴影并不会跟着移动。

备选方案

我们还可以使用更简单的烘焙阴影贴图并移动它,使其一直保持在球体下方。

// 加载简单阴影
const simpleShadow = textureLoader.load('/textures/simpleShadow.jpg')

在这里插入图片描述
我们要创建一个略高于地板的平面,把它的材质的alphaMap属性设置为简单阴影纹理贴图,

const sphereShadow = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(1.5,1.5),
    new THREE.MeshBasicMaterial({
        color:0x000000,
        transparent:true,
        alphaMap:simpleShadow
    })
)
sphereShadow.rotation.x = - Math.PI * 0.5
sphereShadow.position.y = plane.position.y + 0.01
scene.add(sphereShadow)

在这里插入图片描述
接着我们为球体添加动画,使其绕着地板平面做圆周运动,并且有在地板触底弹跳的效果

/**
 * Animate
 */
const clock = new THREE.Clock()
const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()
    //update sphere animate
    // 圆周运动
    sphere.position.x = Math.sin(elapsedTime)
    sphere.position.z = Math.cos(elapsedTime)
    // 触底弹跳
    sphere.position.y = Math.abs(Math.sin(elapsedTime * 3))

    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

接着再设置阴影跟随球体进行位置变换

  //更新球体阴影贴图位置
  //阴影贴图跟随球体
  sphereShadow.position.x = sphere.position.x
  sphereShadow.position.z = sphere.position.z
  //阴影根据球体高度变化,贴图的透明度也有所改变
  //球体距离平面越高,阴影越透明
  sphereShadow.material.opacity = (1 - Math.abs(sphere.position.y)) * 0.3

源代码

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as dat from 'dat.gui'

/**
 * Base
 */
// Debug
const gui = new dat.GUI()

// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

// Textures
const textureLoader = new THREE.TextureLoader()
const bakedShadow = textureLoader.load('/textures/bakedShadow.jpg')
const simpleShadow = textureLoader.load('/textures/simpleShadow.jpg')
/**
 * Lights
 */
// Ambient light
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3)
gui
  .add(ambientLight, 'intensity')
  .min(0)
  .max(1)
  .step(0.001)
scene.add(ambientLight)

// Directional light
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.3)
directionalLight.position.set(2, 2, -1)
gui
  .add(directionalLight, 'intensity')
  .min(0)
  .max(1)
  .step(0.001)
gui
  .add(directionalLight.position, 'x')
  .min(-5)
  .max(5)
  .step(0.001)
gui
  .add(directionalLight.position, 'y')
  .min(-5)
  .max(5)
  .step(0.001)
gui
  .add(directionalLight.position, 'z')
  .min(-5)
  .max(5)
  .step(0.001)
scene.add(directionalLight)
directionalLight.castShadow = true
directionalLight.shadow.mapSize.width = 1024
directionalLight.shadow.mapSize.height = 1024
directionalLight.shadow.camera.near = 2
directionalLight.shadow.camera.far = 6
directionalLight.shadow.camera.top = 2
directionalLight.shadow.camera.right = 2
directionalLight.shadow.camera.left = -2
directionalLight.shadow.camera.bottom = -2
directionalLight.shadow.radius = 10

// console.log(directionalLight.shadow);

const directionalLightCameraHelper = new THREE.CameraHelper(
  directionalLight.shadow.camera
)
directionalLightCameraHelper.visible = false
scene.add(directionalLightCameraHelper)

// 聚光灯
const spotLight = new THREE.SpotLight(0xffffff, 0.3, 10, Math.PI * 0.3)
spotLight.castShadow = true
spotLight.position.set(0, 2, 2)
scene.add(spotLight)
scene.add(spotLight.target)
spotLight.shadow.mapSize.width = 1024
spotLight.shadow.mapSize.height = 1024
spotLight.shadow.camera.fov = 30
spotLight.shadow.camera.near = 1
spotLight.shadow.camera.far = 6

const spotLightCameraHelper = new THREE.CameraHelper(spotLight.shadow.camera)
spotLightCameraHelper.visible = false
scene.add(spotLightCameraHelper)

//点光源
const pointLight = new THREE.PointLight(0xffffff, 0.3)
pointLight.castShadow = true
pointLight.position.set(-1, 1, 0)
pointLight.shadow.mapSize.width = 1024
pointLight.shadow.mapSize.height = 1024
pointLight.shadow.camera.near = 0.1
pointLight.shadow.camera.far = 5

scene.add(pointLight)

const pointLightCameraHelper = new THREE.CameraHelper(pointLight.shadow.camera)
pointLightCameraHelper.visible = false
scene.add(pointLightCameraHelper)

/**
 * Materials
 */
const material = new THREE.MeshStandardMaterial()
material.roughness = 0.7
gui
  .add(material, 'metalness')
  .min(0)
  .max(1)
  .step(0.001)
gui
  .add(material, 'roughness')
  .min(0)
  .max(1)
  .step(0.001)

/**
 * Objects
 */
const sphere = new THREE.Mesh(
  new THREE.SphereBufferGeometry(0.5, 32, 32),
  material
)
sphere.castShadow = true

const plane = new THREE.Mesh(new THREE.PlaneBufferGeometry(5, 5), material)
plane.rotation.x = -Math.PI * 0.5
plane.position.y = -0.5

plane.receiveShadow = true

scene.add(sphere, plane)

const sphereShadow = new THREE.Mesh(
  new THREE.PlaneBufferGeometry(1.5, 1.5),
  new THREE.MeshBasicMaterial({
    color: 0x000000,
    transparent: true,
    alphaMap: simpleShadow,
  })
)
sphereShadow.rotation.x = -Math.PI * 0.5
sphereShadow.position.y = plane.position.y + 0.01
scene.add(sphereShadow)
/**
 * Sizes
 */
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
}

window.addEventListener('resize', () => {
  // Update sizes
  sizes.width = window.innerWidth
  sizes.height = window.innerHeight

  // Update camera
  camera.aspect = sizes.width / sizes.height
  camera.updateProjectionMatrix()

  // Update renderer
  renderer.setSize(sizes.width, sizes.height)
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(
  75,
  sizes.width / sizes.height,
  0.1,
  100
)
camera.position.x = 1
camera.position.y = 1
camera.position.z = 2
scene.add(camera)

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

renderer.shadowMap.enabled = false
renderer.shadowMap.type = THREE.PCFSoftShadowMap
/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () => {
  const elapsedTime = clock.getElapsedTime()
  //更新球体位置
  //设置圆周运动轨迹
  sphere.position.x = Math.sin(elapsedTime) * 1.5
  sphere.position.z = Math.cos(elapsedTime) * 1.5
  //设置触底弹跳效果
  sphere.position.y = Math.abs(Math.sin(elapsedTime * 3))

  //更新球低阴影贴图位置
  //阴影贴图跟随球体
  sphereShadow.position.x = sphere.position.x
  sphereShadow.position.z = sphere.position.z
  //阴影根据球体高度变化,贴图的透明度也有所改变
  //球体距离平面越高,阴影越透明
  sphereShadow.material.opacity = (1 - Math.abs(sphere.position.y)) * 0.3
  // Update controls
  controls.update()

  // Render
  renderer.render(scene, camera)

  // Call tick again on the next frame
  window.requestAnimationFrame(tick)
}

tick()

标签:Shadows,const,THREE,three,camera,new,shadow,js,阴影
来源: https://blog.csdn.net/weixin_43990650/article/details/121681722

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有