threeJs学习笔记(三)

个人项目上线

使用vercel部署:https://vercel.com/

  1. 本地全局安装npm install vercel -g
  2. 打包项目npm run build
  3. 配置部署命令:
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "deploy": "vercel --prod"
  },
  1. 执行vercel login使用github登录vercel
  2. 执行npm run deploy即可:
    在这里插入图片描述

灯光

AmbientLight

环境光源会无差别地作用在物体的各个方向,不会造成阴影等效果

添加光照前:
在这里插入图片描述
添加光照后:

const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
scene.add(ambientLight);

在这里插入图片描述

DirectionalLight

平行光是从单一方向照射而来的光,默认平行光来源为正上方:(0,1,0)的位置

const directionalLight = new THREE.DirectionalLight("#ff0", 0.5);
scene.add(directionalLight);

在这里插入图片描述
平行光默认照向的位置是原点

HemisphereLight半球光

半球光自带两个方向,分为skyColorgroudColor,光的默认位置也是(0,1,0)

const hemisphereLight = new THREE.HemisphereLight("#ff0", "#00f", 1);
scene.add(new THREE.HemisphereLightHelper(hemisphereLight, 5));
scene.add(hemisphereLight);

在这里插入图片描述
注意看,半球光在光交界处的颜色是渐变的,即两种光深浅的交融
在这里插入图片描述

PointLight

点光源就是从某个点发出的光,默认从(0,0,0)的位置发出:

const pointLight = new THREE.PointLight("#f0f", 50);
scene.add(pointLight);

const pointLightHelper = new THREE.PointLightHelper(pointLight, 1);
scene.add(pointLightHelper);

在这里插入图片描述
通过distance属性可以配置光照射的距离,默认是0,代表无限远

如果将光移动到某个位置,distance设为达不到物体的距离,那么光就不会作用到distance范围外的物体,比如将光移动到下面这个位置并且设置光的距离:
在这里插入图片描述

decay对应的是光的衰减值,通常情况下使用的默认的就行,默认值是2,2已经是threejs算出来的最符合物理规律的值了

RectAreaLight

平面光光源,类似生活中摄影棚里的灯:
在这里插入图片描述

const rectLight = new THREE.RectAreaLight(0xffffff, 2, 3, 1);
const rectLightFolder = gui.addFolder("rectLight");
rectLightFolder.add(rectLight.position, "x").min(-10).max(10).onChange(() => {
    rectLight.lookAt(new THREE.Vector3(0,0,0))
});
rectLightFolder.add(rectLight.position, "y").min(-10).max(10).onChange(() => {
    rectLight.lookAt(new THREE.Vector3(0,0,0))
});
rectLightFolder.add(rectLight.position, "z").min(-10).max(10).onChange(() => {
    rectLight.lookAt(new THREE.Vector3(0,0,0))
});
rectLightFolder.add(rectLight, "width").min(-10).max(10).onChange(() => {
    rectLight.lookAt(new THREE.Vector3(0,0,0))
});
rectLightFolder.add(rectLight, "height").min(-10).max(10).onChange(() => {
    rectLight.lookAt(new THREE.Vector3(0,0,0))
});
scene.add(rectLight);

const rectLightHelper = new RectAreaLightHelper(rectLight);
scene.add(rectLightHelper);

在这里插入图片描述

代码中设置了在每次修改光源位置、宽高时都让光源重新照向(lookAt)原点,如果不这么设置,光源将保持初次声明的方向不变

注意,这个光源只能作用于MeshStandardMaterialMeshPhysicalMaterial 两种材质。

SpotLight

聚光灯,照射方式类似手电筒,
在这里插入图片描述

const spotLight = new THREE.SpotLight("#F0F");
const spotLightFolder = gui.addFolder("spotLight");
spotLightFolder.add(spotLight.position, "x").min(-10).max(10);
spotLightFolder.add(spotLight.position, "y").min(-10).max(10);
spotLightFolder.add(spotLight.position, "z").min(-10).max(10);
spotLightFolder.add(spotLight, "intensity").min(-10).max(10);
spotLightFolder.add(spotLight, "distance").min(0).max(10);
spotLight.angle = Math.PI / 4;
scene.add(spotLight);
const spotLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLightHelper);

另外,通过penumbra属性,可以配置出灯光在边缘区域的衰减程度:
在这里插入图片描述
另外,聚光灯默认始终看向原点,如果想要改变其照射的方向,使用lookAt是无效的,必须通过修改spotLight.target.position才能实现:

scene.add(spotLight.target);
spotLight.target.position.set(-1, 1, 0);

在这里插入图片描述

性能问题

灯光是很耗性能的,我们应该使用尽可能少的光源实现,光的性能消耗从高到低排行如下:

T0:SpotLight、RectAreaLight(最耗性能)
T1:DirectionalLight 、PointLight
T2:AmbientLight 、HemisphereLight(最不耗性能)

所以,当来到性能优化层面时,灯光的实现可以考虑在一开始进行3D建模时就给纹理附加上灯光,而不是使用threejs里的光源实现,不过这样的问题是做不到实时的灯光移动,对于需要动态变化的场景可能就略有欠缺

比如threejs-journey.com上提供的示例:
在这里插入图片描述
这个场景内的几何体,都是使用原本就有灯光效果的纹理实现的
在这里插入图片描述

阴影

阴影在threejs中是被计算成一张阴影贴图来展示的,比如可以看官方给的expample示例:

在这里插入图片描述
画面中存在一个从上往下照射的directionalLight,那么经过threejs计算后,得到左上角第一张的阴影贴图;

画面中存在一个照向左下角的spotLight,这个spotLight只照得到环状结几何体,所以左上角第二张阴影贴图只有这个环状结

这就是threejs内部处理阴影的原理

阴影的一般实现方式

  1. 几何体网格设置castShadow = true
  2. 某个需要展示投影的平面Mesh设置receiveShadow = true
  3. 灯光设置castShadow = true
  4. 渲染器开启阴影renderer.shadowMap.enabled = true
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.castShadow = true;
// ...
const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 32, 32), material);
sphere.castShadow = true;
// ...
const plane = new THREE.Mesh(new THREE.PlaneGeometry(5, 5), material);
plane.receiveShadow = true;
// ...

在这里插入图片描述
默认情况下,阴影贴图的分辨率为512 * 512,可以通过打印灯光的shadow属性看到(阴影的相关属性存储在castShadow的光源对象里)
在这里插入图片描述
可以通过修改mapSize的分辨率来获取更清晰的阴影,比如将mapSize设为2048 * 2048

directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;

// 也可以使用x,y设置
// directionalLight.shadow.mapSize.x = 2048;
// directionalLight.shadow.mapSize.y = 2048;

阴影很显然地变得更高清了
在这里插入图片描述
分辨率越高,意味着GPU的计算性能就会占用得越多,并且基于GPU得渲染原理,分辨率的值应该尽量传2的指数幂

观察阴影

观察阴影的媒介其实是摄像机,所以在平行光的位置会会存在一台摄像机,通过directionalLight.shadow.camera可以访问到,添加一个CameraHelper可以看到这个摄像机的位置:

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

在这里插入图片描述

所以,既然是正交摄像机,那么就可以设置它的near、far、left等,目前默认情况下,摄像机的near = 0.5far = 500left = -5top = 5right = 5bottom = -5,将这些值改为下面的配置:

directionalLight.shadow.camera.near = 1;
directionalLight.shadow.camera.far = 6;

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

效果:
在这里插入图片描述
如果把摄像机的far设得更小,那就会发现阴影可能渲染得不完全:

directionalLight.shadow.camera.far = 3.8;

在这里插入图片描述
所以,如果未来遇到类似的这种阴影被切割或看不到阴影的问题,优先考虑是不是观察阴影的摄像机参数没有设对

不同的光源,使用的摄像机有可能也是不同的,可能是正交摄像机,也可能是透视摄像机

模糊

设置模糊属性要通过directionalLight.shadow.radius来设置,值越大代表阴影越模糊,如:

directionalLight.shadow.radius = 100

在这里插入图片描述

阴影贴图算法

通过renderer.shadowMap.type设置,可选的值有4个,性能表现依次下降:
在这里插入图片描述
比如降低原本阴影的分辨率:

directionalLight.shadow.mapSize.width = 256;
directionalLight.shadow.mapSize.height = 256;
// directionalLight.shadow.radius = 100;

使用BasicShadowMap
在这里插入图片描述
使用默认值PCFShadowMap
在这里插入图片描述
放大:
在这里插入图片描述

使用PCFSoftShadowMap
在这里插入图片描述
放大:
在这里插入图片描述
使用VSMShadowMap
在这里插入图片描述

阴影的其他实现方式

场景中如果实时计算的阴影太多,就会造成性能问题,所以还可以使用静态阴影贴图来实现

比如下面这张图是在3D软件中对球几何体模拟光照导出的阴影贴图
在这里插入图片描述
当位置正确时,阴影的效果是很好的
在这里插入图片描述
既然是静态阴影,那么当物体发生移动时,阴影的效果当然就不尽人意了
在这里插入图片描述
这种阴影属于BakedShadow,可以理解为定制化的阴影,即3D建模完成后,阴影就已经定型了,不应该去动态的修改它,也不好修改

但如果是一个简单的alphaMap阴影,那他修改起来就很方便,比如对于下面这张图:
在这里插入图片描述
想要在几何体下方创建一个plane用来放置阴影图,首先创建一个plane

const sphereShadow = new THREE.Mesh(
  new THREE.PlaneGeometry(1.5, 1.5),
  new THREE.MeshBasicMaterial({
    color: "red",
  })
);
scene.add(sphere, sphereShadow, plane);

默认情况下这个创建出来的plane是面对我们的:
在这里插入图片描述
通过将其绕X轴逆时针旋转Π / 2可以得到朝上的平面

sphereShadow.rotateX(-Math.PI * 0.5);

在这里插入图片描述
将其放到物体下方,也就是底部平面正上方一点点,如:

sphereShadow.position.y = plane.position.y + 0.01;

在这里插入图片描述

这么做是避免出现渲染的冲突,即不应该把两个平面放在同一层,假如放在同一层,会出现下面的glitch效果:
在这里插入图片描述
最后使用阴影图作为alpha贴图即可,对于alphaMap而言,贴图里白色的部分会渲染,黑色的部分会被忽略,注意要设置transparent :true

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("/textures/simpleShadow.jpg");
const sphereShadow = new THREE.Mesh(
  new THREE.PlaneGeometry(1.5, 1.5),
  new THREE.MeshBasicMaterial({
    color: "red",
    transparent: true,
    alphaMap: texture,
  })
);

在这里插入图片描述
最后把material的红色改为黑色即可:
在这里插入图片描述
那么,假如遇到物体移动的话,我们也可以很轻松的结合这个方式设置动态的阴影了

举个例子,假如物体在空间中沿着Y轴绕圈,阴影要跟着它动,可以这么实现:

const clock = new THREE.Clock();

const tick = () => {
  const elapsedTime = clock.getElapsedTime();

  controls.update();
  // 球体绕圈旋转:
  sphere.position.x = Math.cos(elapsedTime) * 1.5;
  sphere.position.z = Math.sin(elapsedTime) * 1.5;
  // 更新阴影位置:
  sphereShadow.position.x = sphere.position.x;
  sphereShadow.position.z = sphere.position.z;
  renderer.render(scene, camera);

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

在这里插入图片描述
再比如,如果要让球体弹跳,同时阴影有着明暗程度的变化,用现在的阴影贴图也可以很轻易地实现:

const clock = new THREE.Clock();

const tick = () => {
  const elapsedTime = clock.getElapsedTime();

  // Update controls
  controls.update();
  // 球体绕圈旋转:
  sphere.position.x = Math.cos(elapsedTime) * 1.5;
  sphere.position.z = Math.sin(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 - sphere.position.y) * 0.3;
  // Render
  renderer.render(scene, camera);

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

在这里插入图片描述

阴影的实现方案要根据实际场景来决定,贴图的简单动画相比动态的实时计算性能更好,有时牺牲一些物理上的真实性对用户的感知来说其实并不会有太大的影响

搭鬼屋demo记录的一些API

git@github.com:JohnWicc/threejs-hunted-house.git

生成雾

有线性雾和指数雾FogExp2,区别在于雾的变化是线性变化程度还是指数变化程度,都是越远雾越蒙

const fog = new THREE.Fog("#262837", 1, 15);
scene.fog = fog;

setClearColor

可以理解为设置天空颜色,与scene.background在设置颜色时效果相同

// scene.background = new THREE.Color('#red') // 等价
renderer.setClearColor("red", 0.5);

在这里插入图片描述

clock类

threejs内置的时钟,通常用来实现动画的计算,最常用的API是getElapsedTime,可以获取此时此刻的秒数

在这里插入图片描述

const clock = new THREE.Clock();
const tick = () => {
  const elapsedTime = clock.getElapsedTime();

  // update Ghosts
  const ghost1Angle = elapsedTime * 0.5;
  ghost1.position.x = Math.cos(ghost1Angle) * 4;
  ghost1.position.z = Math.sin(ghost1Angle) * 4;
  ghost1.position.y = Math.sin(ghost1Angle * 3);
  // Update controls
  controls.update();

  // Render
  renderer.render(scene, camera);
  // Call tick again on the next frame
  window.requestAnimationFrame(tick);
};

tick();

好的,我会为您解答关于WebGL three.js的阴影与实现物体动画的问题。首先,让我们来了解一下WebGL three.js是什么。 WebGL three.js是一款基于WebGL的JavaScript 3D库,可以帮助我们快速搭建3D场景和应用。接下来我们来讲解阴影和实现物体动画的方法。 一、阴影 阴影是模拟物体之间的阴影效果,让3D场景更加真实。在three.js中,我们可以通过设置Mesh的castShadow和receiveShadow属性来实现阴影效果。 1. 首先,我们需要在场景中添加光源,例如SpotLight或DirectionalLight。 2. 然后,在需要投射阴影的物体上设置castShadow为true。 3. 最后,在需要接收阴影的物体上设置receiveShadow为true。 代码示例: ```javascript // 添加光源 const light = new THREE.SpotLight(0xffffff); light.position.set(0, 100, 0); light.castShadow = true; scene.add(light); // 添加需要投射阴影的物体 const cube = new THREE.Mesh(new THREE.BoxGeometry(10, 10, 10), new THREE.MeshLambertMaterial({ color: 0xff0000 })); cube.castShadow = true; scene.add(cube); // 添加需要接收阴影的物体 const plane = new THREE.Mesh(new THREE.PlaneGeometry(200, 200, 1, 1), new THREE.MeshLambertMaterial({ color: 0xffffff })); plane.receiveShadow = true; plane.rotation.x = -Math.PI / 2; scene.add(plane); ``` 二、物体动画 在three.js中,我们可以通过Tween.js库来实现物体的动画效果。Tween.js是一款JavaScript动画库,可以帮助我们实现非常丰富的动画效果。 1. 首先,我们需要在HTML文件中引入Tween.js库文件。 2. 然后,在需要动画的物体上设置初始状态。 3. 最后,通过Tween.js库来设置物体的目标状态和动画效果,例如缓动动画(ease)或弹跳动画(bounce)。 代码示例: ```javascript // 引入Tween.js库文件 <script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/18.6.4/tween.min.js"></script> // 添加需要动画的物体 const cube = new THREE.Mesh(new THREE.BoxGeometry(10, 10, 10), new THREE.MeshLambertMaterial({ color: 0xff0000 })); cube.position.set(0, 0, 0); scene.add(cube); // 设置初始状态 const start = { x: 0, y: 0, z: 0 }; // 设置目标状态 const end = { x: 50, y: 50, z: 50 }; // 设置动画效果 const tween = new TWEEN.Tween(start) .to(end, 1000) .easing(TWEEN.Easing.Quadratic.InOut) .onUpdate(() => { cube.position.set(start.x, start.y, start.z); }) .start(); ``` 以上是关于WebGL three.js阴影与实现物体动画的方法,希望能够对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值