专栏名称: 前端早读课
我们关注前端,产品体验设计,更关注前端同行的成长。 每天清晨五点早读,四万+同行相伴成长。
51好读  ›  专栏  ›  前端早读课

【第3477期】基于three.js的虚拟人阴影渲染优化方案

前端早读课  · 公众号  · 前端  · 2025-03-24 08:00

主要观点总结

本文探讨了three.js中的阴影渲染机制,并分享了一些针对性能和效果优化的实用技巧。文章首先介绍了three.js中阴影的重要性及其类型,然后详细阐述了如何优化阴影渲染以提高用户体验和保持流畅性,最后讨论了地面阴影的实现方法。文章还提供了关于全局阴影和地面阴影优化方式的结论,以及后续可能的改进方向。

关键观点总结

关键观点1: three.js中的阴影渲染机制

文章介绍了three.js中阴影的重要性及其类型,包括硬阴影和软阴影,以及常见的阴影类型如BasicShadowMap、PCFShadowMap、PCFSoftShadowMap和VSMShadowMap等。

关键观点2: 阴影优化的实用技巧

文章讨论了如何优化阴影渲染以提高性能和效果,包括合理选择阴影的渲染方式、优化阴影相机的视野范围以及优化阴影贴图的分辨率等。

关键观点3: 地面阴影的实现方法

文章详细阐述了地面阴影的实现方式,通过创建一个正交相机获取底部视角的深度信息,结合自定义shader生成地面阴影。


正文

前言

将探讨 three.js 中的阴影渲染机制,并分享一些针对性能和效果优化的实用技巧,帮助开发者在不同场景下做出最佳的权衡选择。今日前端早读课文章由 @Su Ning 分享,公号:vivo 互联网技术授权。

正文从这开始~~

在 3D 网页应用中,高质量的阴影渲染对于营造场景的真实感至关重要。作为广泛采用的 WebGL 框架之一,three.js 为开发者提供了多种阴影渲染选项,使得创建生动逼真的光影效果成为可能。然而,实现这些视觉上的增强往往伴随着性能开销,尤其在处理复杂场景或运行于低端设备时更为明显。因此,在确保画面质量的同时优化阴影渲染,以提升用户体验和保持流畅性,便成了一个核心挑战。本文将解析 three.js 中的阴影渲染机制,并提供一系列实用的优化策略,助力开发者在不同应用场景下达成最佳平衡。

数字人中使用的阴影

在开发拟我形象的过程中,恰当运用阴影可以显著增加模型的立体感与真实度。同时,在地面上添加阴影不仅能够为观察者提供空间定位的参考点,还能大大增强场景的空间层次感和沉浸体验。

全局阴影

地面阴影

接下来,我们将探讨全局阴影的优化方法以及地面阴影的具体实施方案。

全局阴影的优化

全局阴影的实现主要依赖于 three.js 提供的 shadowMap。只需简单几步 —— 在 WebGLRenderer 中启用 shadowMap 功能、定义产生阴影的光源以及设定哪些物体负责投射或接收阴影 —— 即可轻松完成设置。

若仅使用 three.js 默认配置下的阴影设置,虽然操作简便但效果通常不尽如人意。特别是在针对移动平台进行开发时,考虑到性能限制,我们有必要对 three.js 的阴影特性做进一步研究:

three.js 的阴影

在 three.js 中,阴影的类型主要有两种,分别是硬阴影(hard shadows)和软阴影(soft shadows)。硬阴影的边缘清晰,常用于模拟光源较小或光源位置靠近物体的场景;软阴影的边缘较模糊,更加接近现实中的阴影效果。这两种阴影效果是通过不同的阴影贴图(shadow map)类型实现的。以下是常见的阴影类型:

BasicShadowMap(硬阴影)

特性:这是最基本的阴影类型,计算速度快,性能开销小,但效果相对简单。生成的阴影没有柔和的边缘,呈现出硬边界。

用途:用于性能要求较高但不太关注阴影效果的场景。

BasicShadowMap

PCFShadowMap (Percentage-Closer Filtering)(软阴影)

特性:默认的阴影类型,边缘相对柔和。使用了一种简单的滤波技术来使阴影边缘变得平滑。

用途:大多数情况下推荐使用,效果较好,性能开销也可以接受。

PCFShadowMap

PCFSoftShadowMap(软阴影)

特性:在 PCFShadowMap 的基础上,进一步对阴影的柔和度进行了优化,提供更柔和的阴影边缘效果,但性能开销会更大。

用途:用于需要较高质量阴影效果的场景。

PCFSoftShadowMap

VSMShadowMap (Variance Shadow Map)(软阴影)

特性:使用了方差阴影贴图算法,能够生成高质量且无锯齿的柔和阴影。相比 PCF 技术,它可以产生更加平滑的效果,并且可以避免常见的阴影采样问题。但该技术可能会产生 “光晕” 现象。

用途:适用于高质量阴影场景,特别是需要柔和渐变的阴影效果。

VSMShadowMap

从上面的预览图可以看出,对于 BasicShadowMap 和 PCFShadowMap,阴影的边缘有比较多的锯齿,而对于 PCFSoftShadowMap,除了有更多的性能开销之外,人物在动的时候边缘也会有明显的闪烁的情况出现,而且边缘模糊半径过大导致阴影的效果并不明显。使用 VSMShadowMap 虽然可以得到相对好的效果,但是会出现严重的伪影问题,虽然可以通过调整 shadow 的偏置值(bias)来解决,但是过大的 bias 值会使得阴影的深度测试结果偏移过多,导致阴影被错误地渲染得过远,从而产生不自然的视觉效果。

作为一个手机上的 H5 页面,除了要保障基础的视觉效果,还需要优化性能以使其运行在更多的设备上,为了实现一开始向大家展示的效果同时不增加性能的开销,我们有了下面的优化思路。

【第3322期】基于 Three.js 的 3D 模型加载优化

优化思路

要想有一个比较好的阴影效果,首先不能是硬阴影,所以排除了 BasicShadowMap;

由于 PCFSoftShadowMap 对于性能的开销较大的同时效果提升的也不是很明显,所以也排除掉;最后由于伪影难以控制,所以我们选择了基于 PCFShadowMap 进行优化。

为了得到更好的阴影边缘,可以通过提升 shadowMap 的分辨率来优化,但是分辨率的提升势必会导致性能开销变大,如何在不提升贴图分辨率的情况下提升阴影边缘的质量呢?

我们都知道在不同尺寸的屏幕相同分辨率的情况下,越小的屏幕显示效果越细腻,DirectionalLight 在生成阴影时,会使用一个正交相机(OrthographicCamera)来确定渲染阴影的区域。这个相机的四个边界(left、right、top、bottom)定义了阴影贴图的范围。通过缩小这些边界,可以将阴影贴图的像素更集中于需要渲染阴影的区域,从而提升阴影的清晰度。实际上在虚拟人的场景中,用户的主要注意力都集中在头部区域,所以只要将阴影相机聚焦在头部的区域即可,而不需要获取全局的阴影。

 const bias =1.6// 设置一个y轴的偏置值,使得阴影相机可以正对人脸
const mainLight =newTHREE.DirectionalLight(0xf2f7ff)
 mainLight.intensity =1.8
 mainLight.position.set(0.3,0.81+ bias,2.71)

const target =newTHREE.Object3D()
 target.position.set(0, bias,0)// 设置灯光的照射目标
 group.add(target)
 mainLight.target = target
 mainLight.castShadow =true

 mainLight.shadow.radius =2// 设置阴影边缘的模糊半径,这个值并不是越大越好,需要根据实际场景进行微调
const{ camera }= mainLight.shadow
 camera.far =5

// 阴影相机的默认边界为上下左右分别为5,将其缩小至各0.5
 camera.top =-0.5
 camera.bottom =0.5
 camera.left =-0.5
 camera.right = 0.5

地面阴影的实现

在最开始的动图 (图 2) 中,除了脸部的阴影,还有一个地面的阴影,很显然地面阴影不可能专门打一束光照在脚上获得,这样会使得整体的光影显得很奇怪,那么地面阴影是怎么实现的呢。

实际上这里参考了 model-viewer ( https://github.com/google/model-viewer) 的实现,地面上的阴影实际上是一个方形加上阴影贴图:

  • 创建一个正交相机,将相机的位置设置在脚下,朝向上方并有一点点倾角,获取到从地面向上看的图像;
  • 创建一个材质,并且自定义着色器渲染物体的深度信息,渲染第一步创建的相机的场景的时候将材质赋值给 scene.overrideMaterial 属性,这样场景中所有的物体都会使用这个材质进行渲染;
  • 再创建一个正交相机,用于模糊第一个相机获取到的图像;
  • 将模糊后的图像作为贴图,应用到地板平面上;

此方案在每帧画面渲染之前都要再额外先把地面阴影的场景渲染出来,所以会增加额外的性能开销,由于地面阴影的边缘经过模糊平滑的处理,所以分辨率并不需要太高,贴图尺寸设置为 64*64 即可,有效的控制地面阴影带来的性能损失。

 // 设置阴影渲染目标,作为阴影贴图
const size =64






请到「今天看啥」查看全文