threejs学习笔记(二)

Texture

即纹理,可以实现给材质添加上图片等功能

声明时要通过创建一个TextureLoader来加载,如:

 const texture = new THREE.TextureLoader().load("map-texture.jpg");
 const material = new THREE.MeshBasicMaterial({
   map: texture,
 });

背后的原理:

  const image = new Image();
  const texture = new THREE.Texture(image);
  image.onload = () => {
    texture.needsUpdate = true;
  };
  image.src = "map-texture.jpg";

这就是材质加载出来的样子:

在这里插入图片描述
如果使用textureLoader来加载,则不需要显式地调用texture.needsUpdate = true来通知纹理更新,纹理会在加载完成后自动地渲染出效果

LoadingManager

可以用来监听纹理、字体等物体的加载节点,内部实现了onStartonLoadonErroronProgress的回调函数
在这里插入图片描述

uvmapping(待补充)

uv坐标是一组用来定位纹理位置的坐标,掌握这个属性很重要

mipmapping

在这里插入图片描述
看左上角的图,一个4x4的正方形可以由4个2x2的正方形构成,一个2x2的正方形可以由4个1x1的正方形构成

mipmapping是一种GPU的计算策略,内部会将一张纹理图不断地拆解成若干个小单元,最后叠加起来构成纹理,同时这样也使得渲染性能得以提升

mipmapping会对纹理图进行持续的计算直到裁剪到1*1的像素为止,所以,使用纹理图时,要确保纹理图的宽高为2的幂次方,如256、512、1024等,否则在threejs内部还要额外进行处理,浪费性能

结合mipmapping使用的是minFilter属性和magFilter属性,一般设为NearestFilter性能会比较好,锐利度也够高(why?

当我们把minFilter设置成NearestFilter后,可以将texture.generateMipmaps设为false进一步提升性能

使用纹理图时,纹理图大小不要太大,以降低请求的时间

Material(材质)

要在场景中渲染几何体,需要同时给这个几何体添加相应的材质才可实现

创建material时,通过配置map属性来添加纹理,比如添加一张门的纹理:
在这里插入图片描述
几何体正常情况下会渲染成这样:
在这里插入图片描述
会发现纹理图不是想象中的大小,门的两侧有同色的拉伸效果,如果想要取消掉拉伸效果,可以提供一个alpha材质,比如:
在这里插入图片描述
alphaMap要求提供一张黑白图,图中白色部分会被渲染,黑色部分会被省略,配置后的效果:
在这里插入图片描述
注意,同时还需要配置transparent:true才会生效

MeshNormalMaterial

法线材质使用纹理时可以使用带方向的纹理图
在这里插入图片描述
几何体的每个面都有n个顶点,可以通过widthSigmentsheightSigments来设置,每个面的顶点由相交线相交得来,有多少个顶点就有多少个方向,

这种材质可以实现打光,折射和反射;拿灯光举例,之所以能看到几何体的某个面,是因为那个面的法线恰好指向我们自己或光源的方向,而其他非正面的方向就有不同程度的阴影或看不到(背面) ,这就是gpu计算出3D物体的光形态的原理

比如配置一个默认的MeshNormalMaterial,效果如下:
在这里插入图片描述
假如加上一个flatShading配置,如:

const material = new THREE.MeshNormalMaterial();
material.flatShading = true;

可以看到表面变得不光滑了:
在这里插入图片描述
可以再进一步开启wireFrame观察构成几何体的平面,可以看到它们的表面就是由若干个三角形构成的
在这里插入图片描述
在这里插入图片描述
所以flatShading的意义在于仅对这些平面进行着色,忽略平滑的计算效果

MeshMatcapMaterial

这个材质会自动根据提供的纹理图选择合适的颜色进行渲染,选择的依据是基于对灯光的模拟,比如下图:
在这里插入图片描述
添加到几何体上后:

const material = new THREE.MeshMatcapMaterial({
  matcap: matcapTexture
});

在这里插入图片描述
这个纹理的好处在于可以直接使用图片来模拟灯光、阴影的效果,比如再来一个例子:
在这里插入图片描述
在这里插入图片描述
一个找matcap纹理的仓库:github.com/nidorx/matcaps,商用时需要确保自己有授权

也可以自己用3d绘制软件画一个球,做好灯光投射等配置后,输出矩形的matcap图来使用

MeshDepthMaterial

深度材质,特点是离摄像机越近则越白,越远则越黑
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
需要注意的是,灯光不会作用于该材质上,所以使用了该材质后,ambientLightpointLight都不会有效果

MeshLambertMaterial

对场景中使用LambertMaterial,需要同时加上光源:

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

const light = new THREE.PointLight(0xffffff, 0.5);
light.position.x = 2;
light.position.y = 3;
light.position.z = 4;
scene.add(light);
const material = new THREE.MeshLambertMaterial();

在这里插入图片描述
因为有点光源的存在,当把摄像机拉到背面时,可以看到阴影
在这里插入图片描述

这是一个会受光影响的材质,表现力很强,性能好,但是当你放大到足够大时,几何体表面会有一些模糊的线条:
在这里插入图片描述

如果不添加光源,比如ambientLight,看到的物体就是一团黑

MeshPhongMaterial

相比于Lamber,他不会有模糊的问题,自带“光反射”,但是性能不好,如果项目中出现了很多物体,导致整体性能不够好时,要考虑换掉这种材质

所有可以光反射的材质都可以设置shininess(光反射强度),(lambert不行,因为它不会对光做出反射)

使用Phong时,shininess用来控制反射强度,值越大,光反射越集中:

shininess = 0
在这里插入图片描述
shininess = 200
在这里插入图片描述
specular用来控制光反射的颜色,比如设为红色时:
在这里插入图片描述

MeshToonMaterial

拥有类似卡通效果的材质,与lambert材质有类似的效果,但是由于颜色会发生急剧的变化,所以会有类似卡通的效果出现

在这里插入图片描述
从点光源的方向照射过来,到阴影的位置急剧变化,所以出现了类似断层的现象,看起来就和卡通的效果相似

一般可以配置gradientMap属性来控制它颜色的效果,比如加载一张只有深灰、浅灰、白色的渐变图作为texture
在这里插入图片描述
代码:

const material = new THREE.MeshToonMaterial();
material.gradientMap = gradientTexture;

效果:
在这里插入图片描述
只是简单的设置gradientMap后会让卡通效果消失,这是因为gpu会自动使用mipmapping进行计算,mipmapping会把渐变色计算的非常精细,所以过渡效果就会很平滑,当像素被拆分得足够精细时,颜色急剧变化就不存在了

可以配置minFilter、magFilterNearestFilter来恢复急剧的变化:

const material = new THREE.MeshToonMaterial();
gradientTexture.minFilter = THREE.NearestFilter;
gradientTexture.magFilter = THREE.NearestFilter;
material.gradientMap = gradientTexture;

在这里插入图片描述
这里是使用了只有3种颜色的渐变图片,当换成有5个渐变色时:
在这里插入图片描述
效果:
在这里插入图片描述
同时,因为使用了NearestFilter,所以可以把generateMipmaps设为false来进一步减少性能开销

MeshStandardMaterial

这个材质与LambertPhong类似,但是它呈现的效果就更贴近现实,因为它的算法比较"真实",并且参数的设置上更利于我们理解

默认情况下的效果:
在这里插入图片描述
可以看到默认情况下metalnessroughness分别是0和1,当将他随意改变时,会发现比较神奇的效果:
在这里插入图片描述
官方文档对metalness的解释,与金属有多相似可以理解成反光的程度
在这里插入图片描述
roughness的解释:
在这里插入图片描述

去到官方文档,可以看到相关的描述:1. 基于PBR渲染,显得更真实;2. 占用、计算成本更高;3. 使用时尽可能制定envMap
在这里插入图片描述
在使用该材质的情况下同时加上之前的纹理图,会出现更逼真的效果,比如:

const material = new THREE.MeshStandardMaterial();
gui.add(material, "metalness").min(0).max(1).step(0.0001);
gui.add(material, "roughness").min(0).max(1).step(0.0001);
material.map = doorColorTexture;

在这里插入图片描述

使用aoMap(AmbientOcclusion)

同时,为了更好地模拟阴影效果,可以引入aoMap来实现,在官方文档中地描述:(全名:AmbientOcclusion
在这里插入图片描述
比如使用下面这张aoMao纹理图:在这里插入图片描述

const material = new THREE.MeshStandardMaterial();
gui.add(material, "metalness").min(0).max(1).step(0.0001);
gui.add(material, "roughness").min(0).max(1).step(0.0001);
material.map = doorColorTexture;
material.aoMap = doorAmbientOcclusionTexture;
material.aoMapIntensity = 1;

文档中要求第二组uv坐标,可以这么实现:


const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1, 100, 100), material);
plane.geometry.setAttribute(
  "uv2",
  new THREE.BufferAttribute(plane.geometry.attributes.uv.array, 2)
);

配置完成后,可以得到下面的效果,
在这里插入图片描述
调整aoMapIntensity,可以看到更深的阴影:
在这里插入图片描述

displacementMap

位移贴图,官方文档的解释:
在这里插入图片描述
当使用下面这张图当作displacementMap时:
在这里插入图片描述
图片的黑色部分代表其高度不受影响,从黑色到白色,高度的变化程度不断增高
得到的效果:
在这里插入图片描述
位移贴图可以用来呈现”高度“的效果,而”高度“的效果由几何体的顶点数量决定,拿中间的planeGeometry举例,打开wireFrame,当配置的widthSegmentsheightSegments参数为10时:

const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1, 10, 10), material);

它的顶点相对比较少:
在这里插入图片描述
此时如果配置上高度图,可以看到实际上就是顶点的位置发生了变化:
在这里插入图片描述
以此得到了类似高度的效果;而进一步将widthSegmentsheightSegments设置成更高的值时,顶点也随之增多:那么顶点突起的部分则更加精细:

const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1, 100, 100), material);

在这里插入图片描述
关闭wireFrame,对比10和100的情况,会发现顶点越多,出来的效果就越好,这是合情合理的:

10的情况:
在这里插入图片描述
100的情况:
在这里插入图片描述
但是显然,我们不需要那么“高”的突起,所以可以通过displacementScale(位移比例)来设置突起的程度,比如将displacementScale设为0.1时,效果就已经很好了:

material.displacementMap = doorHeightTexture;
material.displacementScale = 0.1;

在这里插入图片描述

在这里插入图片描述

metalnessMap与roughNessMap

两张图,分别作为metalnessMaproughNessMap
在这里插入图片描述
在这里插入图片描述
代码配置上之后:

material.metalnessMap = doorMetalnessTexture;
material.roughnessMap = doorRoughnessTexture;

在这里插入图片描述
乍看没什么变化,但是移动一下视角会发现,表面的反射和光泽更加逼真了:
在这里插入图片描述

normalMap

同样的,加上一个normalMap纹理图:
在这里插入图片描述

material.normalMap = doorNormalTexture;

在这里插入图片描述
乍看没什么变化,但是转换一下视角会发现,细节一下就出来了:
在这里插入图片描述
放大则看的更明显:
在这里插入图片描述
同样的,也可以配置normalScale来调整法线纹理的比例:
在这里插入图片描述

最后,要实现一扇完美的门,再加上之前的alphaMa即可:

const material = new THREE.MeshStandardMaterial();
material.side = THREE.DoubleSide;
// material.metalness = 0;
// material.roughness = 1;
gui.add(material, "metalness").min(0).max(1).step(0.0001);
gui.add(material, "roughness").min(0).max(1).step(0.0001);
material.map = doorColorTexture;
material.aoMap = doorAmbientOcclusionTexture;
gui.add(material, "aoMapIntensity").min(0).max(100).step(1);
gui.add(material, "displacementScale").min(0).max(1).step(0.001);
material.displacementMap = doorHeightTexture;
material.displacementScale = 0.1;
// material.wireframe = true;
material.metalnessMap = doorMetalnessTexture;
material.roughnessMap = doorRoughnessTexture;
material.normalMap = doorNormalTexture;
gui
  .add(material.normalScale, "x")
  .min(0)
  .max(100)
  .step(0.1)
  .name("normalScaleX");
gui
  .add(material.normalScale, "y")
  .min(0)
  .max(100)
  .step(0.1)
  .name("normalScaleY");
material.normalScale.set(10, 10);
material.transparent = true; // 要设置透明效果才行
material.alphaMap = doorAlphaTexture; // 配置alpha通道

在这里插入图片描述

使用envMap

可以在这个网站找高清环境贴图:https://hdri-haven.com/
然后去这个网站将图片转为cubeMap6张图:https://matheowis.github.io/HDRI-to-CubeMap/

使用envMap时,要使用具有6个方位图片的环境贴图纹理,需要使用CubeTextureLoader来加载纹理,比如加载下面这六张图:
在这里插入图片描述

const cubeTextureLoader = new THREE.CubeTextureLoader();
const environmentMapTexture = cubeTextureLoader.load([
  "/textures/environmentMaps/2/px.jpg",
  "/textures/environmentMaps/2/nx.jpg",
  "/textures/environmentMaps/2/py.jpg",
  "/textures/environmentMaps/2/ny.jpg",
  "/textures/environmentMaps/2/pz.jpg",
  "/textures/environmentMaps/2/nz.jpg",
]);

加载环境贴图时,url的路径是有顺序要求的:在这里插入图片描述
加载环境纹理后,使用下面的配置:

const material = new THREE.MeshStandardMaterial();
material.metalness = 1;
material.roughness = 0;
gui.add(material, "metalness").min(0).max(1).step(0.0001);
gui.add(material, "roughness").min(0).max(1).step(0.0001);
material.envMap = environmentMapTexture;

得到的效果:
在这里插入图片描述

Text

实际使用时可以直接复制threejs/examples里的typeface字体和LICENSE到自己的项目目录下使用

或者你有自定义的一套字体,将其在这个网站转换为typeface格式字体:
https://gero3.github.io/facetype.js/

不过需要确保你有使用对应字体的授权

加载字体时,需要使用fontLoader加载字体,使用TextGeometry创建字体(拉伸)集合体

import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
import { FontLoader } from "three/addons/loaders/FontLoader.js";
import * as THREE from "three";

const fontLoader = new FontLoader();
fontLoader.load("/fonts/helvetiker_regular.typeface.json", (font) => {
  const textGeometry = new TextGeometry("Hello Three.js", {
    font,
    size: 12,
    depth: 1,
    curveSegments: 6,
    bevelSegments: 2,
    bevelEnabled: true,
    bevelThickness: 0.03,
    bevelSize: 0.02,
  });
  const textMaterial = new THREE.MeshBasicMaterial({
    wireframe: true,
  });
  const mesh = new THREE.Mesh(textGeometry, textMaterial);
  scene.add(mesh);
});

const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 300;
scene.add(camera);

写入上面的配置后,可以得到下面的结果:
在这里插入图片描述

  1. curveSegmentsbevelSegments会控制几何体顶点的数量,为了更好的性能,可以开着wireFrame来不断将它们调低,找到能满足自己需求的最小值
  2. 将视角放大,会看到在原点处,H字母的底部线是斜的,这是因为开启了bevelEnabled的原因
    在这里插入图片描述

那如果我们想把字体“居中”,该怎么做呢?

正常来说,每个几何体都有一个固定的box来将其包裹:
在这里插入图片描述
默认情况下,threejs的几何体是用球状框包裹的,此时无法通过textGeometry.boundingBox获取到它的边框值;
如果我们想获取到某个几何体的边框,需要先调用textGeometry.computedBoundingBox()方法,将几何体外边框转成矩形边框,然后才可以获取到该矩形边框

console.log('before:', JSON.parse(JSON.stringify(textGeometry.boundingBox)));
textGeometry.computeBoundingBox();
console.log('after:', JSON.parse(JSON.stringify(textGeometry.boundingBox)));

在这里插入图片描述

这一步骤是全程不可见的,没有什么办法来肉眼看到它的边框是怎样的

所以,居中实际上就是通过将几何体所有顶点沿着x轴、y轴、z轴移动其外层盒子框体最大xyz值的一半来实现居中,主要通过translate方法来实现:

textGeometry.computeBoundingBox();
console.log("before:", JSON.parse(JSON.stringify(textGeometry.boundingBox)));
textGeometry.translate(
  -textGeometry.boundingBox.max.x * 0.5,
  -textGeometry.boundingBox.max.y * 0.5,
  -textGeometry.boundingBox.max.z * 0.5
);
textGeometry.computeBoundingBox()
console.log("after:", JSON.parse(JSON.stringify(textGeometry.boundingBox)));

在这里插入图片描述
在这里插入图片描述
但是移动后box的最大最小值貌似不太对,按理说最小值的x应该和最大值的x之和为0,但是加上会发现它们有0.02的差距

这个差距对应的就是上面配置的bevelSize: 0.02,所以实际上精确的计算方式应该是:

textGeometry.translate(
  -(textGeometry.boundingBox.max.x - 0.02) * 0.5,
  -(textGeometry.boundingBox.max.y - 0.02) * 0.5,
  -textGeometry.boundingBox.max.z * 0.5
);

当然,除了上面这种居中的方法,我们还可以使用textGeometry.center()方法来一步到位,不需要搞得这么麻烦:

textGeometry.computeBoundingBox();
console.log("before:", JSON.parse(JSON.stringify(textGeometry.boundingBox)));
textGeometry.center();
textGeometry.computeBoundingBox();
console.log("after:", JSON.parse(JSON.stringify(textGeometry.boundingBox)));

一个移动的问题

注意,如果是自己计算,实际上有两种办法可以“居中”,但他们略有不同

一种是将几何体通过translate方法修改所有顶点的位置
一种是对mesh.position进行修改

  textGeometry.translate(
    -(textGeometry.boundingBox.max.x - 0.02) * 0.5,
    -(textGeometry.boundingBox.max.y - 0.02) * 0.5,
    -textGeometry.boundingBox.max.z * 0.5
  );

// 或直接移动mesh
  mesh.position.sub(
    new THREE.Vector3(
      (textGeometry.boundingBox.max.x - 0.02) * 0.5,
      (textGeometry.boundingBox.max.y - 0.02) * 0.5,
      textGeometry.boundingBox.max.z * 0.5
    )
  );

虽然居中的效果相同,但是当要进行旋转、缩放时,结果可能就会偏离预期了

如果是移动几何体顶点的方式或.center()的方式,缩放、旋转都是以原点(0,0,0)进行:

在这里插入图片描述

但如果是通过移动mesh来实现的话,其几何体中心并不是(0,0,0),缩放、旋转的中心是以原本的中心位置为基点进行的:

在这里插入图片描述

好的,我会为您解答关于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、付费专栏及课程。

余额充值