基于物理的渲染和照明
参考文章:https://discoverthreejs.com/zh/book/first-steps/physically-based-rendering/
一、基于物理的渲染和照明
1、基于物理的渲染
基于物理的渲染 (PBR):这种渲染技术使用真实世界的物理学来计算表面对光的反应方式,从而避免在场景中设置材质和照明时进行猜测,three.js 中有个常用的 PBR 材料,即MeshStandardMaterial。
2、照明和材料
光照和材质在计算机图形渲染系统中有着内在的联系,我们需要同时使用。
要使用诸如MeshStandardMaterial的 PBR 材质,我们必须在场景中添加灯光。
如果没有光,我们就看不到。到目前为止,我们一直在使用的材料 MeshBasicMaterial 不是基于物理的,也不需要灯光,是一种特殊材料,其他材料均需要灯光。
这里可以使用 DirectionalLight 灯光,这种光类型模仿来自遥远光源(如太阳)的光线。
二、启用物理上正确的光照
物理上正确的照明:意味着使用真实世界的物理方程计算 光如何随着与光源的距离(衰减)而衰减。就是我们物理教科书中的那些方程。
另外基于物理的渲染涉及以物理上正确的方式计算光与表面的反应。不过,我们不必了解它们,可以直接使用它们!
要打开物理上正确的照明,只需启用渲染器的 .physicallyCorrectLights设置:
function createRenderer() { const renderer = new WebGLRenderer(); // turn on the physically correct lighting model renderer.physicallyCorrectLights = true; return renderer; }
三、Three.js 中的光照类型
1、概念
- 直接照明:是指光源直接照射到物体表面的光照效果。这种照明方式能够确定从光源到达物体表面的光线颜色和数量,但忽略了通过其他方式(如反射或折射)到达物体表面的光线。直接照明能够决定被物体表面吸收和反射的光量,通常用于模拟太阳光或点光源的效果。
- 间接照明:间接照明是指光线经过其他物体反射或散射后照射到物体表面的光照效果。这种照明方式决定了从除光源以外的其他地方到达物体表面的光线颜色和数量。间接照明通常通过反射从一个表面照亮另一个表面,例如,墙壁上的灯光反射到地板上。由于发光体和观察者之间存在多种路径,间接照明比直接照明更难计算且成本更高。
2、Three.js中的光照类型及其应用场景
- 环境光(AmbientLight):均匀照亮整个场景,没有方向性,不会产生阴影,它主要用于提供一个基础的光照,使得场景不会完全黑暗。
- 平行光(DirectionalLight):从无限远处发射平行光线,所有光线方向一致,模拟太阳光的效果,它可以产生阴影。
- 点光源(PointLight):从空间中某一点向所有方向发射光线,类似于灯泡,它可以产生阴影,并且可以通过调整位置来控制照明范围和强度。
- 聚光灯(SpotLight):从一点发射锥形光线,形成类似舞台聚光灯的效果。它可以产生阴影,并且可以通过调整角度和衰减来控制光束的范围和强度。
- 平面光(RectAreaLight):从一个矩形平面区域均匀发射光线,类似灯箱或窗户光。不会产生阴影。
注意点:即使我们使用 PBR材料,在 three.js 的世界中默认情况下,对象不会阻挡光线。也就是说,光路径中的每个物体都会收到照明,即使路上有一堵墙,落在物体上的光会照亮它,但也会直接穿过并照亮后面的物体。
四、平行光DirectionalLight
DirectionalLight设计的目的是模仿遥远的光源,例如太阳。
来自DirectionalLight的光线不会随着距离而消失。
场景中的所有对象都将被同样明亮地照亮,无论它们放在哪里,即使是在灯光后面。
DirectionalLight的光线是平行的,从一个位置照向一个目标。
默认情况下,目标放置在我们场景的中心点(0,0,0)。也可以通过设置.target
属性指定目标。
参数:
- color:光源颜色。默认为白色(0xffffff)。
- intensity:光照强度。默认值为 1。
const light = new THREE.DirectionalLight('white', 8); // 设置光源方向,即从(10, 10, 10)照向目标【默认情况下是(0, 0, 0)】 light.position.set(10, 10, 10); // 指定光照目标,不设置则默认是(0, 0, 0) light.target = mesh; // 指定目标为网格mesh // 将平行光添加到场景中 scene.add(light);
五、基于物理的MeshStandardMaterial
之前我们使用的 MeshBasicMaterial 是three.js中提供的最基本的材料。
它根本不会对灯光做出反应,并且网格的整个表面都用单一颜色着色。
不执行基于视角或距离的着色,因此对象看起来甚至不是三维的。我们所能看到的只是一个二维轮廓。
所以我们使用 MeshBasicMaterial ,添加灯光不会有任何立竿见影的效果,因为这种材质会忽略场景中的任何灯光。
现在我们介绍另一种材料 MeshStandardMaterial。
MeshBasicMaterial 这是一种高质量、通用、物理精确的材料,可以使用真实世界的物理方程对光做出反应。
顾名思义,MeshStandardMaterial应该是几乎所有情况下的首选“标准”材料。
通过添加精心制作的纹理,我们可以使用MeshStandardMaterial重建几乎任何常见的表面。
const material = new MeshStandardMaterial();
六、基于MeshStandardMaterial和平行光DirectionalLight
示例
<!DOCTYPE html> <html> <head> <title>基于MeshStandardMaterial和平行光DirectionalLight</title> <style> #scene-container { width: 100vw; height: 100vh; } canvas { display: block; } </style> </head> <body> <div id="scene-container"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script> const container = document.querySelector('#scene-container'); const scene = new THREE.Scene(); // 创建场景 scene.background = new THREE.Color('skyblue'); const fov = 35; const aspect = container.clientWidth / container.clientHeight; const near = 0.1; const far = 100; const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); camera.position.set(0, 0, 10); const geometry = new THREE.BoxGeometry(2, 2, 2); // 使用PBR材质:MeshStandardMaterial,并设置材质的颜色为紫色 const material = new THREE.MeshStandardMaterial({ color: 'purple' }); const cube = new THREE.Mesh(geometry, material); // cube.rotation.set(-0.5, -0.1, 0.8); // 设置网格旋转的角度,绕X、Y、Z轴旋转的角度 // 灯光 const light = new THREE.DirectionalLight('white', 8); // 模仿遥远的光源,例如太阳。设置为白色 强度为8 light.position.set(10, 10, 10); // 灯光从(10,10,10)照向(0,0,0) scene.add(cube, light); // 网格、灯光添加到场景中 const renderer = new THREE.WebGLRenderer(); renderer.physicallyCorrectLights = true; // 启用物理上正确的光照 renderer.setSize(container.clientWidth, container.clientHeight); renderer.setPixelRatio(window.devicePixelRatio); container.append(renderer.domElement); function animate() { requestAnimationFrame(animate); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } animate(); </script> </body> </html>