添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

把球体材质改为标准网格材质MeshStandardMaterial,再添加平行光

const directionalLight = new THREE.DirectionalLight('#ffffff',1)
directionalLight.position.set(0.25,3,-2.25)
scene.add(directionalLight)
gui.add(directionalLight, 'intensity').min(0).max(10).step(0.001).name('光照强度')
gui.add(directionalLight.position, 'x').min(- 5).max(5).step(0.001).name('光X')
gui.add(directionalLight.position, 'y').min(- 5).max(5).step(0.001).name('光Y')
gui.add(directionalLight.position, 'z').min(- 5).max(5).step(0.001).name('光Z')

在这里插入图片描述
默认情况下,three.js的光强数值不真实。为了使得光强更趋于真实值,应该把渲染器的physicallyCorrectLights属性设为true

renderer.physicallyCorrectLights = true

可以看到光源暗下来了,回到平行光设置将其光强提升到3
在这里插入图片描述

下面把测试球体移除然后加载模型。

  1. 导入并实例化GLTFLoader
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
......
 1. Loader
const gltfLoader = new GLTFLoader()
  1. 加载飞行员头盔模型
* Model gltfLoader.load('/models/FlightHelmet/glTF/FlightHelmet.gltf',(gltf)=>{ scene.add(gltf.scene)
  1. 缩放大小并微调位置
gltfLoader.load('/models/FlightHelmet/glTF/FlightHelmet.gltf', gltf => {
  gltf.scene.scale.set(10, 10, 10)
  gltf.scene.position.set(0, -4, 0)
  gltf.scene.rotation.y = Math.PI * 0.5
  scene.add(gltf.scene)
  gui.add(gltf.scene.rotation, 'y').min(- Math.PI).max(Math.PI).step(0.001).name('头盔旋转')

关于环境贴图可以看另一篇笔记three.js学习笔记(三)——material材质。我们将使用环境贴图作为背景,并照亮整个模型。

  1. 实例化CubeTextureLoader
const cubeTextureLoader = new THREE.CubeTextureLoader()
  1. 加载环境贴图
1. 环境贴图 const environmentMap = cubeTextureLoader.load([ '/textures/environmentMaps/0/px.jpg', '/textures/environmentMaps/0/nx.jpg', '/textures/environmentMaps/0/py.jpg', '/textures/environmentMaps/0/ny.jpg', '/textures/environmentMaps/0/pz.jpg', '/textures/environmentMaps/0/nz.jpg'
  1. 把贴图应用到场景的background属性
scene.background =  environmentMap

将环境贴图应用到模型上

真实渲染的一个基本特征在于使用环境贴图来照亮模型。
前面已经教过如何使用envMap属性将环境贴图应用到标准网格材质
MeshStandardMaterial中,现在的问题在于我们的模型是由许多个网格Mesh组成的,因此我们要使用 traverse(…)方法来遍历场景中的所有三维物体Object3D类对象,包括继承自Object3D的Group和Mesh。

  1. 在创建环境贴图的前面创建updateAllMaterials函数
1. 更新所有材质 const updateAllMaterials = ()=>{ scene.traverse((child)=>{ console.log(child);
  1. 当模型加载完成并添加到场景后调用函数
gltfLoader.load(
    '/models/FlightHelmet/glTF/FlightHelmet.gltf',
    (gltf) =>
        // ...
        updateAllMaterials()
  1. 把envMap应用到材质为标准网格材质的Mesh上
1. 更新所有材质 const updateAllMaterials = ()=>{ scene.traverse((child)=>{ if(child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial){ child.material.envMap = environmentMap
  1. 上图看不出有什么变化,因此可以往GUI面板中添加envMapIntensity来调整环境贴图强度。
// 创建空调试对象
const debugObject = {}
debugObject.envMapIntensity = 5
// 当调整面板数值时调用更新材质函数
gui.add(debugObject,'envMapIntensity').min(0).max(10).step(0.01).onChange(updateAllMaterials)

注意在更新材质函数中应用envMapIntensity

const updateAllMaterials = ()=>{
    scene.traverse((child)=>{
        if(child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial){
            child.material.envMap = environmentMap
            child.material.envMapIntensity = debugObject.envMapIntensity
  1. 有一种更简单的方法将环境贴图应用到所有对象上,我们可以像更改场景的background属性一样去更改场景的environment属性。这样做的话就不必再在updateAllMaterials函数中去设置环境贴图了。
scene.environment = environmentMap

        但是我们仍然无法直接从场景里更改每个材质的环境贴图强度,因此还是需要updateAllMaterials函数。
在这里插入图片描述

尽管目前看起来效果还行,但在颜色方面还是有点欠缺需要下点工夫。这是因为WebGLRenderer 属性的问题。

Output encoding

outputEncoding属性控制输出渲染编码。默认情况下,outputEncoding的值为THREE.LinearEncoding,看起来还行但是不真实,建议将值改为THREE.sRGBEncoding

renderer.outputEncoding = THREE.sRGBEncoding

这下我们可以看到更亮的材质,同时这也影响到环境贴图。
在这里插入图片描述
除此之外还有另一个属性值为THREE.GammaEncoding,这种编码的优点在于它允许我们使用一种表现像亮度brightness的叫gammaFactor的值。GammaEncoding是一种存储颜色的方法,根据人眼的敏感度优化明暗值的存储方式。当使用sRGBEncoding时,其实就像使用默认gammaFactor值为2.2的GammaEncoding。
下面链接可以提供更多信息关于GammaEncoding和sRGBEncoding

Textures encoding

我们可以发现设置完渲染器的输出编码outputEncodingTHREE.sRGBEncoding后,我们的环境贴图颜色也改变了,虽然看起来效果不错,但我们还是要选择保留其原先正确的颜色。问题就在于我们设置完渲染器的输出编码之后,环境贴图的纹理还是默认的THREE.LinearEncoding
其实规则很直接,所有我们能够直接看到的纹理贴图,比如map,就应该使用THREE.sRGBEncoding作为编码;而其他的纹理贴图比如法向纹理贴图normalMap就该使用THREE.LinearEncoding
我们可以直接看到环境贴图,所以应该将其编码设为THREE.sRGBEncoding

environmentMap.encoding = THREE.sRGBEncoding

在这里插入图片描述
你可能会问那模型上的各种纹理贴图都要一个个亲自去设置吗?大可不必,因为GLTFLoader会将加载的所有纹理自动进行正确的编码。

Tone mapping

色调映射Tone mapping旨在将超高的动态范围HDR转换到我们日常显示的屏幕上的低动态范围LDR的过程。
说明一下HDR和LDR(摘自知乎
LDR和HDR):

  • 因为不同的厂家生产的屏幕亮度(物理)实际上是不统一的,那么我们在说LDR时,它是一个0到1范围的值,对应到不同的屏幕上就是匹配当前屏幕的最低亮度(0)和最高亮度(1)
  • 自然界中的亮度差异是非常大的。例如,蜡烛的光强度大约为15,而太阳光的强度大约为10w。这中间的差异是非常大的,有着超级高的动态范围。
  • 我们日常使用的屏幕,其最高亮度是经过一系列经验积累的,所以使用、用起来不会对眼睛有伤害;但自然界中的,比如我们直视太阳时,实际上是会对眼睛产生伤害的。

那为了改变色调映射tone mapping,则要更新WebGLRenderer上的toneMapping属性,有以下这些值

  • THREE.NoToneMapping (默认)
  • THREE.LinearToneMapping
  • THREE.ReinhardToneMapping
  • THREE.CineonToneMapping
  • THREE.ACESFilmicToneMapping

尽管我们的贴图不是HDR,但使用tone mapping可以塑造更真实的效果。

renderer.toneMapping = THREE.ACESFilmicToneMapping

在这里插入图片描述
为了更直观看到不同色调映射的区别,可以将其添加到GUI面板中。切换完选项后还需要更新材质,在updateAllMaterials函数中添加child.material.needsUpdate = true然后在gui面板中切换选项后调用函数。

.add(renderer, 'toneMapping', { No: THREE.NoToneMapping, Linear: THREE.LinearToneMapping, Reinhard: THREE.ReinhardToneMapping, Cineon: THREE.CineonToneMapping, ACESFilmic: THREE.ACESFilmicToneMapping, .onFinishChange(() => { // 如果我们在外面打印值比如console.log(THREE.ReinhardToneMapping)你会发现输出结果是数字类型的3 // 然而当我们把对象放到dat.GUI面板里面后其会将值给转化为字符串,导致控制台报警告 // 因此我们需要在这里将其重新转化为数字类型 renderer.toneMapping = Number(renderer.toneMapping) updateAllMaterials()

在这里插入图片描述
我们还可以更改色调映射曝光度toneMappingExposure

renderer.toneMappingExposure = 3
// 添加到gui面板
  .add(renderer, 'toneMappingExposure')
  .min(0)
  .max(10)
  .step(0.001)

在这里插入图片描述
下面使用的是色调映射是THREE.ReinhardToneMapping

在一些像素比为1的显示屏上看模型时会发现明显的锯齿。
在这里插入图片描述
在这里插入图片描述
为此我们只需要在实例化渲染器renderer时开启抗锯齿antialias属性即可(开启抗锯齿只能在实例化的时候开启)

const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true

使用抗锯齿会损耗性能这点是众所周知的,其实像素比大于1的屏幕实际上不怎么需要抗锯齿。一个正确的方法是只在像素比低于2的屏幕上激活它。

设置渲染器开启阴影贴图,并将类型设为THREE.PCFSoftShadowMap

renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap

设置平行光投射阴影

directionalLight.castShadow = true

优化阴影贴图,可以参考另一篇笔记three.js学习笔记(五)——Shadows阴影

directionalLight.shadow.camera.far = 15
directionalLight.shadow.mapSize.set(1024, 1024)

然后添加相机助手查看,可以的话就移除掉助手。

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

在这里插入图片描述
然后,激活模型中所有网格的阴影

const updateAllMaterials = () =>
    scene.traverse((child) =>
        if(child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial)
            // ...
            child.castShadow = true
            child.receiveShadow = true

在这里插入图片描述
最后你只需要将场景中的光源调整到接近环境贴图中光源的位置就行。

我们可以导入上次自己做的汉堡包模型。因为它经过Draco压缩,所以需要导入DRACOLoader。具体步骤参照three.js学习笔记(十一)——导入模型

gltfLoader.load('/models/hamburger.glb', gltf => {
  gltf.scene.scale.set(0.3, 0.3, 0.3)
  gltf.scene.position.set(0, -1, 0)
  gltf.scene.rotation.y = Math.PI * 0.5
  scene.add(gltf.scene)
  updateAllMaterials()

在这里插入图片描述
可以看到汉堡包表面有些奇怪的条纹,这种情况被称为“阴影失真shadow acne
在计算曲面是否处于阴影中时,由于精度原因,阴影失真可能会发生在平滑和平坦表面上。
而现在在汉堡包上发生的是汉堡包在它自己的表面上投射了阴影。因此我们必须调整灯光阴影shadow的“偏移bias”和“法线偏移normalBias”属性来修复此阴影失真。

  • bias通常用于平面,因此不适用于我们的汉堡包。但如果你有在一块平坦的表面上出现阴影失真,可以试着增加偏差直到失真消失。
  • normalBias通常用于圆形表面,因此我们增加法向偏差直到阴影失真消失。
directionalLight.shadow.normalBias = 0.05
				
首先来看下THREE.WebGLRenderer可以设置的参数如下: var renderer = new THREE.WebGLRenderer({ //创建渲染器对象 // canvas: document.getElementById('can3d'), //渲染器绘制其输出的画布, alpha: false, // 画布是否包含alpha(透明度)缓冲区。默认值为false。 premultipliedAlpha: true, //渲染器是否会假设颜色具有 预乘alpha。默
callback - 以一个object3D对象作为第一个参数的函数。通过此函数我们可以遍历加载的对象 从而分析出加载模型的类型目的 在以后的每个分部分上可以添加阴影等其他效果1.创建函数 2.调用函数 通过判断类型可以给 mesh加入envmap材质 加入后效果变化不是很明显调节envMapIntensity可以看到效果属性控制输出渲染编码。默认情况下,o 该特效只有在摄像机对象启动HDR模式时才能正常工作,设置较高的光源强度值会在产生更明显的效果,此外,由于bloom特效可以使光源拥有更好的亮度范围,所以该特效配合bloom特效一起使用会得到更好的效果 technique:技术,该项用于指定色彩映射的计算方式,即如何将高动态光照渲染产生的高范围的光照度映射至显示设备能显示的低范围内,有7中选择。分别是:Simplereinh
好的,我会为您解答关于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阴影与实现物体动画的方法,希望能够对您有所帮助。
Yale Qi: 加载glsl的时候,vite,以字符串形式加载.glsl文件即可,即在文件后面加上“raw”参数即可: import basicVertextShader from './shader/basic/vertex.glsl?raw' import basicFragmentShader from './shader/basic/fragment.glsl?raw'