【Threejs基础教程-光影篇】5.2 Threejs 阴影系统

了解光影系统

threejs是实时光影

实时光影是指:光影会随着灯光和物体的改变而改变,且变化速率非常高,参与渲染过程,

web端目前没有优质的实时光影

实时光影一般在B端不会有太好的效果,只有在C端环境下,且需要更强力的硬件支持,才能达到更好的实时光影渲染效果,像实时光追系统,就是C端专属的功能,且对显卡消耗也是巨大,也许未来某一天,随着WebGPU发展的更多,对显卡的使用更加完美后,实时光影或许也会登陆B端,这里我们可以暂且期待一下

实时光影会大幅增加渲染压力

物体越多,光影渲染增加的渲染压力就越高,对大多数情况下,渲染压力会增加一倍,如果你的设备,本身能承担的面数,大概是1000万,那么你用了实时光影后,大概这个极限会缩减到500万甚至更低,所以谨慎使用实时光影

元素越多,点线面越多,都会对最终实时光影的渲染计算量有影响

没有独显的电脑不建议添加实时光影

没有独显的电脑,本身渲染压力已经非常大了,再添加光影,cpu只会压力更大

笔记本一定要看清楚,你的浏览器是否使用独显渲染,别让CPU干渲染的活

阴影配置

什么样的灯光可以产生阴影

在目前threejs的系统下,一共有三个灯光可以产生阴影,分别是

PointLight 点光源
DirectionalLight 平行光
SpotLight 聚光灯

什么样的物体可以产生阴影和接受阴影

不是所有的物体都可以产生阴影和接受阴影

灯光只能产生阴影,不能接收阴影

可以产生并接受阴影的物体有:Mesh,Line等

不能直接产生阴影且不能接收阴影的有:Object3D,Group,

完全不产生阴影且不能接收阴影的有:Sprite(精灵),Points(粒子系统)

注意开启阴影渲染

阴影渲染是有开关的,在renderer下
在这里插入图片描述
当允许渲染阴影的时候,Threejs才会开启阴影渲染系统,否则你怎么让物体产生接收阴影,你都看不到任何的光影效果

	//开启阴影渲染
	renderer.shadowMap.enabled = true;

阴影可以做一些设置,比如说,允许自动更新光影,以及定义阴影类型,一般我们要使得实时光影效果最好,建议使用
THREE.PCFSoftShadowMap,如果你觉得性能太差,使用PCFShadowMap或者更低的BasicShadowMap

关于VSM阴影,这里挖个坑,后续有机会详细讲解,如果你在用PCFSoft的时候,调整不出来正确的光影效果,可以尝试使用VSM阴影来解决

灵活运用阴影

上面提到了,阴影系统可以设置产生阴影和接收阴影,所以你也可以指定某个物体仅接收阴影,但是不产生阴影,也可以设置某个物体只产生阴影不接收阴影,来实现某些效果
也可以通过设置渲染器是否自动更新阴影,来让阴影的渲染变成单帧渲染

平行光阴影

在这里插入图片描述
这里官方描述的并不是很清楚,我们写一段代码来研究这个阴影

首先,我们要先让阴影系统生效

    import * as THREE from "../three/build/three.module.js";
    import {OrbitControls} from "../three/examples/jsm/controls/OrbitControls.js";

    window.addEventListener('load',e=>{
        init();
        addLight();//添加灯光
        addLand();//添加地面
        addMesh();//添加物体
        render();
    })

    let scene,renderer,camera;
    let orbit;
    let mesh;

    function init(){

        scene = new THREE.Scene();
        renderer = new THREE.WebGLRenderer({
            alpha:true,
            antialias:true
        });
        renderer.setSize(window.innerWidth,window.innerHeight);

        //渲染器开启阴影,并使用PCF算法过滤阴影,且使用软阴影
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;

        document.body.appendChild(renderer.domElement);

        camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);
        camera.position.set(10,10,10);

        orbit = new OrbitControls(camera,renderer.domElement);
        orbit.enableDamping = true;
    }

    function addLight() {

        //添加平行光
        let directionalLight = new THREE.DirectionalLight(0xffffff,1.0);
        //设置平行光产生阴影
        directionalLight.castShadow = true;
        //设置平行光位置
        directionalLight.position.set(0,20,20);

        //平行光辅助线
        let directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight,1,0xff0000);

        //平行光光影辅助线
        let cameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera);

        //将物体添加到场景中
        scene.add(directionalLight);
        scene.add(directionalLightHelper);
        scene.add(cameraHelper);
    }

    function addMesh() {
        let geometry = new THREE.TorusKnotGeometry(5,1,64,8);
        let material = new THREE.MeshBasicMaterial({color:0xffffff * Math.random()});
        mesh = new THREE.Mesh(geometry,material);
        //给物体添加生成阴影和接收阴影的设置
        mesh.castShadow = true;
        mesh.receiveShadow = true;
        mesh.position.y = 3;
        scene.add(mesh);
    }

    function addLand() {
        //添加一个地面
        let geometry = new THREE.PlaneGeometry(100,100).rotateX(-Math.PI/2);
        let material = new THREE.MeshStandardMaterial({
            color:0xffffff
        });
        let mesh = new THREE.Mesh(geometry,material);
        //设定地面仅接收阴影  因为我们使用的是个地面,所以没必要再让它生成阴影浪费算力,除非你的地下有东西
        mesh.receiveShadow = true;
        scene.add(mesh);
    }

    function render() {
        renderer.render(scene,camera);
        orbit.update();
        requestAnimationFrame(render);
        mesh.rotation.x += 0.01;
        mesh.rotation.y += 0.01;
    }

在这里插入图片描述
从效果中,我们可以清晰的看到,阴影是有范围的,这个范围就是cameraHelper的范围
这是因为,阴影的计算,是借助了相机的算法来计算的,平行光内部使用了正交相机OrthographicCamera来计算阴影

我们可以打印平行光,来看到阴影的基本数据,也可以通过添加CameraHelper来查看实际计算的阴影范围

在这里插入图片描述
现在我们上面的效果,遇到了明显的问题,就是阴影不完整,这个时候,我们可以通过调整相机的实际范围来增加阴影的生成区域

控制正交相机的6个要素,分别为 left,right,top,bottom,near, far

正交相机在之前的相机篇已经做了介绍,不懂的可以回顾一下【ThreeJS基础教程-初识Threejs】1.5 选择合适的相机与相机切换

首先我们得知道,默认的值是多少
在这里插入图片描述
在这里插入图片描述

从侧面拉远了之后,我们看到,物体并没有超出near和far的范畴,所以near和far此时不需要更改

在这里插入图片描述
从正面看,明显比物体小一圈,所以我们此时把left,right,top,bottom都翻一倍即可

        directionalLight.shadow.camera.left = -10;
        directionalLight.shadow.camera.right = 10;
        directionalLight.shadow.camera.top = 10;
        directionalLight.shadow.camera.bottom = -10;

在这里插入图片描述
这时,我们的阴影就正常了

点光源阴影

我们将上述代码,替换 addLight() 的部分

    function addLight() {
        let pointLight = new THREE.PointLight(0xffffff,1.0,40,0.1);
        pointLight.position.set(0,20,20);
        pointLight.castShadow = true;

        console.log(pointLight);

        let pointLightHelper = new THREE.PointLightHelper(pointLight,1,0xff0000);
        let cameraHelper = new THREE.CameraHelper(pointLight.shadow.camera);

        scene.add(pointLight);
        scene.add(pointLightHelper);
        scene.add(cameraHelper);
    }

在这里插入图片描述
在这里插入图片描述

官方对于点光源阴影的介绍实在太过简单。。。

点光源实际使用透视相机来计算阴影,但是我们实际上通过透视相机辅助线并不能看到透视相机的变化,这个原因就由你们自行研究了

可以说的是,点光源的阴影也是有范围的,且范围跟随distance属性的变化而变化,我们在代码中,把distance设置到了40,并没有完全覆盖物体后面的光影区域,所以不仅照不亮那一块,连阴影也不会产生

我们修改了distance后,达到了比较好的效果

在这里插入图片描述
可以看得出,点光源的阴影,是会随着距离点光源的距离,而越来越大,与现实中的灯泡是完全一致的

聚光灯阴影

//和上面一样,我们依然修改addLight()即可
    function addLight() {

        //聚光灯的属性这里不再赘述
        let spotLight = new THREE.SpotLight(0xffffff,10,50,0.5,0.2,0.2);
        spotLight.castShadow = true;
        spotLight.position.set(0,20,20);
        let spotLightHelper = new THREE.SpotLightHelper(spotLight,0xff0000);

        scene.add(spotLight);
        scene.add(spotLightHelper);
    }

在这里插入图片描述
聚光灯内部也是使用透视相机进行阴影计算的,和点光源的越远阴影越大一样

如果想对聚光灯调节照射范围,只需要修改distance,angle,等属性即可,无需像上面平行光一样需要手动修改相机

优化阴影

以下方案对上述所有光源均有效

阴影范围外一片漆黑,可以优化一下吗

我们以刚写完的聚光灯案例入手,其实我们只需要加一个亮度不高的环境光,效果就会好很多
这样,即使物体不处在聚光灯光源之下,也能看清楚物体

        scene.add(new THREE.AmbientLight(0xffffff,0.2));

在这里插入图片描述

一般情况下,场景中是需要一个全局光照的,这个全局光照,可以是一个纯环境光,也可以是一个半球光,根据自己的需求来设置即可,这样开启阴影后,就不会产生某个地方特别漆黑的情况,也能有比较贴近现实的感觉,环境光亮度根据自己的实际需求来调节即可

增加阴影的精度

阴影的本质,其实就是计算一块阴影,然后覆盖到物体的原有的贴图上

所以阴影也是有纹理的性质的,比如说分辨率
如果你的阴影效果比较差,可以使用下面的方式来提高阴影精度

	//建议两个参数值相等,且数值为2的幂次倍
	// 如: 256 * 256,512 * 512,1024 * 1024,2048 * 2048
	light.shadow.mapSize.set(512,512);

由于在demo中,修改此值没有太大区别,所以这里不做演示了

注意:提高了一倍的阴影精度,计算量大约会增加4倍,最高仅建议到4096

消除伪影

摩尔纹效果图来自百度
摩尔纹效果来源百度,如有侵权请联系笔者

有时候阴影会出现类似上图的摩尔纹效果,可以用调整bias来解决

笔者刚才在尝试的过程中,没有一次能复现摩尔纹效果,所以这里仅找一张百度的图片来代替演示效果,实际上

在这里插入图片描述

	light.shadow.bias -= 0.0001;

静态阴影渲染 / 渐进式阴影渲染

Threejs官方案例shadowmap_progressive

这里的光影,我们可以从效果中看到,阴影不是第一时间渲染完成的,而是在物体或灯光移动后的几秒钟后完成的,这种阴影渲染叫:静态光影渲染,或渐进式阴影渲染

这种渲染的好处是,我们不需要实时的去更新阴影,只需要在改变物体的一瞬间重新渲染光影即可

这种阴影渲染可以用在不经常移动的物体上

具体的实现方式,请自行查看threejs官方案例的源代码

烘培阴影

烘培阴影已经在上一篇做了简单介绍,这里就不再赘述了
【Threejs基础教程-光影篇】5.1 常用的灯光

使用阴影时需要注意的点

  1. 阴影计算非常消耗性能,要根据实际需求去决定如何使用阴影渲染
  2. 你的模型已经很大的情况下,不推荐使用实时阴影
  3. 减少产生阴影的灯光,产生阴影的光源越多,也会呈指数级的额外消耗性能

Threejs基础教程在此完结,后续请查阅本人的: Threejs进阶教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值