背景
破防了 😭
!忽然发现 SegmentFault
平台的粉丝数量曾经冲破 1000
了,它是我的三个博客平台掘金、博客园、SegmentFault中首个粉丝冲破 1000
的,于是设计开发这个页面,特此留念一下。非常感谢大家的关注 🙏
,后续我会更加专一前端常识的整顿分享,写出更多高质量的文章。(心愿其余平台也早日破千 😂
)
本文应用 React + Three.js
技术栈,实现粉丝冲破 1000
的 3D
留念页面,蕴含的次要知识点包含:Three.js
提供的光源、DirectionLight
平行光、HemisphereLight
半球光源、AmbientLight
环境光、奖牌素材生成、贴图常识、MeshPhysicalMaterial
物理材质、TWEEN
镜头补间动画、CSS
礼花动画等。
成果
实现效果图如文章 👆
Banner图
所示,页面由蕴含我的个人信息的奖牌 🥇
、1000+ Followers
模型形成,通过以下链接能够实时预览哦 🤣
。
👀
在线预览:https://dragonir.github.io/3d…
实现
引入资源
首先引入开发性能所需的库,其中 FBXLoader
用于加在 1000+
字体模型、OrbitControls
镜头轨道控制、TWEEN
用于生成补间动画、Stats
用于开发时性能查看。
import * as THREE from "three";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js";
import Stats from "three/examples/jsm/libs/stats.module";
场景初始化
这部分内容次要用于初始化场景和参数,具体解说可点击文章开端链接浏览我之前的文章,本文不再赘述。
container = document.getElementById('container');
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.needsUpdate = true;
container.appendChild(renderer.domElement);
// 场景
scene = new THREE.Scene();
// 给场景设置难看的背景
scene.background = new THREE.TextureLoader().load(backgroundTexture);
// 摄像机
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 0);
camera.lookAt(new THREE.Vector3(0, 0, 0));
// 控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.enableDamping = true;
controls.enableZoom = false;
controls.enablePan = false;
controls.rotateSpeed = .2;
📌
为了达到更好的视觉效果,为OrbitControls
设置了缩放禁用、平移禁用和减小默认旋转速度
光照成果
为了模仿实在的物理场景,本示例中应用了 3种
光源。
// 直射光
const cubeGeometry = new THREE.BoxGeometry(0.001, 0.001, 0.001);
const cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 0, 0);
light = new THREE.DirectionalLight(0xffffff, 1);
light.intensity = 1;
light.position.set(18, 20, 60);
light.castShadow = true;
light.target = cube;
light.shadow.mapSize.width = 512 * 12;
light.shadow.mapSize.height = 512 * 12;
light.shadow.camera.top = 80;
light.shadow.camera.bottom = -80;
light.shadow.camera.left = -80;
light.shadow.camera.right = 80;
scene.add(light);
// 半球光
const ambientLight = new THREE.AmbientLight(0xffffff);
ambientLight.intensity = .8;
scene.add(ambientLight);
// 环境光
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0xfffc00);
hemisphereLight.intensity = .3;
scene.add(hemisphereLight);
💡
Three.js 提供的光源
Three.js
库提供了一些列光源,而且没种光源都有特定的行为和用处。这些光源包含:
光源名称 | 形容 |
---|---|
AmbientLight 环境光 |
这是一种根底光源,它的色彩会增加到整个场景和所有对象的以后色彩上 |
PointLight 点光源 |
空间中的一点,朝所有的方向发射光线 |
SpotLight 聚光灯光源 |
这种光源有聚光的成果,相似台灯、天花板上的吊灯,或者手电筒 |
DirectionLight 平行光 |
也称为有限光。从这种光源收回的光线能够看着平行的。例如,太阳光 |
HemishpereLight 半球光 |
这是一种非凡光源,能够用来创立更加天然的室外光线,模仿放光面和光线强劲的天空 |
AreaLight 面光源 |
应用这种光源能够指定散发光线的立体,而不是空间中的一个点 |
LensFlare 镜头眩光 |
这不是一种光源,然而通过 LensFlare 能够为场景中的光源增加眩光成果 |
💡
THREE.DirectionLight 平行光
THREE.DirectionLight
能够看作是间隔很远的光,它收回的所有光线都是互相平行的。平行光的一个范例就是太阳光。被平行光照亮的整个区域承受到的光强是一样的。
构造函数:
new THREE.DirectionLight(color);
属性阐明:
position
:光源在场景中的地位。target
:指标。它的指向很重要。应用target
属性,你能够将光源指向场景中的特定对象或地位。此属性须要一个THREE.Object3D
对象。intensity
:光源照耀的强度,默认值:1
。castShadow
:投影,如果设置为true
,这个光源就会生成暗影。onlyShadow
:仅暗影,如果此属性设置为true
,则该光源只生成暗影,而不会在场景中增加任何光照。shadow.camera.near
:投影近点,示意间隔光源的哪一个地位开始生成暗影。shadow.camera.far
:投影远点,示意到间隔光源的哪一个地位能够生成暗影。shadow.camera.left
:投影左边界。shadow.camera.right
:投影右边界。shadow.camera.top
:投影上边界。shadow.camera.bottom
:投影下边界。shadow.map.width
和shadow.map.height
:暗影映射宽度和暗影映射高度。决定了有多少像素用来生成暗影。当暗影具备锯齿状边缘或看起来不润滑时,能够减少这个值。在场景渲染之后无奈更改。两者的默认值均为:512
。
💡
THREE.HemisphereLight 半球光光源
应用半球光光源,能够创立出更加贴近天然的光照成果。
构造函数:
new THREE.HeimsphereLight(groundColor, color, intensity);
属性阐明:
groundColor
:从高空收回的光线色彩。Color
:从天空收回的光线色彩。intensity
:光线照耀的强度。
💡
THREE.AmbientLight 环境光
在创立 THREE.AmbientLight
时,色彩会利用到全局。该光源并没有特地的起源方向,并且不会产生暗影。
构造函数:
new THREE.AmbientLight(color);
应用倡议:
- 通常不能将
THREE.AmbientLight
作为场景中惟一的光源,因为它会将场景中的所有物体渲染为雷同的色彩。 - 应用其余光源,如
THREE.SpotLight
或THREE.DirectionLight
的同时应用它,目标是弱化暗影或给场景增加一些额定色彩。 - 因为
THREE.AmbientLight
光源不须要指定地位并且会利用到全局,所以只须要指定个色彩,而后将它增加到场景中即可。
增加网格和高空
增加网格是为了不便开发,能够调整模型的适合的绝对地位,本例中保留网格的目标是为了页面更有 3D景深成果
。通明材质的高空是为了显示模型的暗影。
// 网格
const grid = new THREE.GridHelper(200, 200, 0xffffff, 0xffffff);
grid.position.set(0, -30, -50);
grid.material.transparent = true;
grid.material.opacity = 0.1;
scene.add(grid);
// 创立高空,通明材质显示暗影
var planeGeometry = new THREE.PlaneGeometry(200, 200);
var planeMaterial = new THREE.ShadowMaterial({ opacity: .5 });
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.set(0, -30, -50);
plane.receiveShadow = true;
scene.add(plane);
创立奖牌
因为工夫关系,本示例奖牌模型间接应用 Three.js
自带的根底立方体模型 THREE.BoxGeometry
来实现,你也能够应用其余立方体如球体、圆珠等,甚至能够应用 Blender
等业余建模软件创立本人喜爱的奖牌形态。(ps
:集体感觉立方体也挺难看的 😂
)
💡
奖牌UI素材生成
🥇
奖牌上上面和侧面贴图制作:
为了生成的奖牌有黄金质感,本例中应用 👇
该材质贴图,来生成亮瞎眼的24K纯金成果 🤑
。
🥇
奖牌侧面和反面贴图制作:
奖牌的侧面和反面应用的贴图是 SegmentFault
集体核心页的截图,为了更具备金属成果,我用 👆
下面金属材质贴图给它增加了一个带有圆角的边框。
Photoshop 生成圆角金属边框具体方法:截图下面增加金属图层 ->
应用框选工具框选须要删除的内容 ->
点击抉择 ->
点击批改 ->
点击平滑 ->
输出适合的圆角大小 ->
删除选区 ->
合并图层 ->
实现并导出图片。
最终的正反面的材质贴图如 👇
下图所示,为了显示更清晰,我在 Photoshop
中同时批改了图片的对比度
和 饱和度
,并加了 SegmentFault
的 Logo
在下面。
🥇
奖牌侧面和反面的法相贴图制作:
为了生成凹凸质感,就须要为模型增加法相贴图。应用 👆
下面曾经生成的侧面和反面的材质贴图,就能够应用在线工具主动生成法相贴图。生成时能够依据须要,通过调整 Strength
、Level
、Blur
等参数进行款式微调,并且可能实时预览。调整好后点击 Download
下载即可。
🚪
法相贴图在线制作工具传送门:NormalMap-Online
通过屡次调节优化,最终应用的法相贴图如 👇
下图所示。
应用下面生成的素材,当初进行奖牌模型的构建。侧面和反面应用个人信息材质,其余面应用金属材质。而后遍历对所有面调整金属度和粗糙度款式。
let segmentMap = new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(segmentTexture), normalMap: new THREE.TextureLoader().load(normalMapTexture) });
let metalMap = new THREE.MeshPhysicalMaterial({map: new THREE.TextureLoader().load(metalTexture)});
// 创立纹理数组
const boxMaps = [metalMap, metalMap, metalMap, metalMap, segmentMap, segmentMap];
// 💡 立方体长宽高比例须要和贴图的大小比例统一,厚度能够轻易定
box = new THREE.Mesh(new THREE.BoxGeometry(297, 456, 12), boxMaps);
box.material.map(item => {
// 材质款式调整
item.metalness = .5;
item.roughness = .4;
item.refractionRatio = 1;
return item;
});
box.scale.set(0.085, 0.085, 0.085);
box.position.set(-22, 2, 0);
box.castShadow = true;
meshes.push(box);
scene.add(box);
👆
下面 4
张效果图顺次对应的是:
图1
:创立没有贴图的BoxGeometry
,只是一个红色的立方体。图2
:立方体增加材质贴图
,此时没有凹凸成果。图3
:立方体增加法相贴图
,此时产生凹凸成果。图4
:调节立方体材质的金属度
、毛糙水平
和反射率
,更具备真实感。
💡
Three.js 中的贴图
贴图类型
map
:材质贴图normalMap
:法线贴图bumpMap
:凹凸贴图envMap
:环境贴图specularMap
:高光贴图lightMap
:光照贴图
贴图原理
通过纹理贴图加载器 TextureLoader()
去新创建一个贴图对象进去,而后再去调用外面的 load()
办法去加载一张图片,这样就会返回一个纹理对象,纹理对象能够作为模型材质色彩贴图 map
属性的值,材质的色彩贴图属性 map
设置后,模型会从纹理贴图上采集像素值。
💡
MeshPhysicalMaterial 物理材质
MeshPhysicalMaterial
类是 PBR
物理材质,能够更好的模仿光照计算,相比拟高光网格材质MeshPhongMaterial
渲染成果更真切。
如果你想展现一个产品,为了更真切的渲染成果最好抉择该材质,如果游戏为了更好的显示成果能够抉择
PBR
材质MeshPhysicalMaterial
,而不是高光材质MeshPhongMaterial
。
非凡属性
.metalness
金属度属性:示意材质像金属的水平。非金属材料,如木材或石材,应用0.0
,金属应用1.0
,两头没有(通常). 默认0.5
.0.0
到1.0
之间的值可用于生锈的金属外观。如果还提供了粗糙度贴图.metalnessMap
,则两个值都相乘。.roughness
粗糙度属性:示意材质的毛糙水平.0.0
示意平滑的镜面反射,1.0
示意齐全漫反射. 默认0.5
. 如果还提供粗糙度贴图.roughnessMap
,则两个值相乘..metalnessMap
金属度贴图:纹理的蓝色通道用于扭转资料的金属度..roughnessMap
粗糙度贴图:纹理的绿色通道用于扭转资料的粗糙度。
📌
留神应用物理材质的时候,个别须要设置环境贴图.envMap
。
加载1000+文字模型
1000+
字样的模型应用 THREE.LoadingManager
和 FBXLoader
加载。具体应用办法也不再本文中赘述,可参考文章开端链接查看我的其余文章,外面有详细描述。😁
const manager = new THREE.LoadingManager();
manager.onProgress = async(url, loaded, total) => {
if (Math.floor(loaded / total * 100) === 100) {
// 设置加载进度
_this.setState({ loadingProcess: Math.floor(loaded / total * 100) });
// 加载镜头挪动补间动画
Animations.animateCamera(camera, controls, { x: 0, y: 4, z: 60 }, { x: 0, y: 0, z: 0 }, 3600, () => {});
} else {
_this.setState({ loadingProcess: Math.floor(loaded / total * 100) });
}
};
const fbxLoader = new FBXLoader(manager);
fbxLoader.load(textModel, mesh => {
mesh.traverse(child => {
if (child.isMesh) {
// 生成暗影
child.castShadow = true;
// 款式调整
child.material.metalness = 1;
child.material.roughness = .2;
meshes.push(mesh);
}
});
mesh.position.set(16, -4, 0);
mesh.rotation.x = Math.PI / 2
mesh.scale.set(.08, .08, .08);
scene.add(mesh);
});
补间动画
相机挪动实现漫游等动画,页面关上时,模型加载结束从大变小的动画就是通过 TWEEN
实现的。
animateCamera: (camera, controls, newP, newT, time = 2000, callBack) => {
var tween = new TWEEN.Tween({
x1: camera.position.x, // 相机x
y1: camera.position.y, // 相机y
z1: camera.position.z, // 相机z
x2: controls.target.x, // 控制点的中心点x
y2: controls.target.y, // 控制点的中心点y
z2: controls.target.z, // 控制点的中心点z
});
tween.to({
x1: newP.x,
y1: newP.y,
z1: newP.z,
x2: newT.x,
y2: newT.y,
z2: newT.z,
}, time);
tween.onUpdate(function (object) {
camera.position.x = object.x1;
camera.position.y = object.y1;
camera.position.z = object.z1;
controls.target.x = object.x2;
controls.target.y = object.y2;
controls.target.z = object.z2;
controls.update();
});
tween.onComplete(function () {
controls.enabled = true;
callBack();
});
tween.easing(TWEEN.Easing.Cubic.InOut);
tween.start();
}
动画更新
最初不要忘了要在 requestAnimationFrame
中更新场景、轨道控制器、TWEEN
、以及模型的自转 🌍
等。
// 监听页面缩放,更新相机和渲染
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
stats && stats.update();
controls && controls.update();
TWEEN && TWEEN.update();
// 奖牌模型自转
box && (box.rotation.y += .04);
}
礼花动画
最初,通过 box-shadow
和简略的 CSS
动画,给页面增加 🎉
绽开成果,营造 🎅
欢庆气氛!
<div className="firework_1"></div>
<div className="firework_2"></div>
<!-- ... -->
<div className="firework_10"></div>
款式动画:
[class^=firework_] {
position: absolute;
width: 0.1rem;
height: 0.1rem;
border-radius: 50%;
transform: scale(8)
}
.firework_1 {
animation: firework_lg 2s both infinite;
animation-delay: 0.3s;
top: 5%;
left: 5%;
}
@keyframes firework_lg {
0%, 100% {
opacity: 0;
}
10%, 70% {
opacity: 1;
}
100% {
box-shadow: -0.9rem 0rem 0 #fff, 0.9rem 0rem 0 #fff, 0rem -0.9rem 0 #fff, 0rem 0.9rem 0 #fff, 0.63rem -0.63rem 0 #fff, 0.63rem 0.63rem 0 #fff, -0.63rem -0.63rem 0 #fff, -0.63rem 0.63rem 0 #fff;
}
}
实现成果:
🔗
残缺代码 https://github.com/dragonir/3…
总结
本文中次要波及到的知识点包含:
Three.js
提供的光源THREE.DirectionLight
平行光THREE.HemisphereLight
半球光光源THREE.AmbientLight
环境光- 奖牌
UI
素材生成 Three.js
中的贴图MeshPhysicalMaterial
物理材质TWEEN
镜头补间动画CSS
礼花动画
想理解场景初始化、光照、暗影及其他 Three.js
的相干常识,可浏览我的其余文章。如果感觉文章对你有帮忙,不要忘了 一键三连 👍
。
附录
- [1]. Three.js 实现虎年春节3D创意页面
- [2]. Three.js 实现脸书元宇宙3D动静Logo
- [3]. Three.js 实现3D全景侦探小游戏
- [4]. 应用Three.js实现炫酷的酸性格调3D页面
- [5]. 环境贴图起源:dribbble
- [6]. 字体模型起源:sketchfab
发表回复