老板花一万大洋让我写的艺术工作室官网?! HeroSection 再度来袭!(Three.js)
引言.我不是鸽子大王!!
哈喽大家好!距离我上次发文已经过去半个月了,差点又变回了那只熟悉的“老鸽子”。不行,我不能堕落!我还没有将 Web3D
推广到普罗大众,还没有让更多人感受到三维图形的魅力 (其实是还没有捞着米)。怀着这样的心情,我决定重新振作,继续为大家带来更多关于 Three.js
和 Shader
的进阶内容。
0.前置条件
欢迎阅读本篇文章!在深入探讨 Three.js
和 Shader (GLSL)
的进阶内容之前,确保您已经具备以下基础知识:
- Three.js 基础:您需要熟悉
Three.js
的基本概念和使用方法,包括场景(Scene
)、相机(Camera
)、渲染器(Renderer
)、几何体(Geometry
)、材质(Material
)和网格(Mesh
)等核心组件。如果您还不熟悉这些内容,建议先学习Three.js
的入门教程。 - Shader 语法:本文涉及
GLSL
(OpenGL Shading Language)的编写,因此您需要了解GLSL
的基本语法,包括顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)的编写,以及如何在Three.js
中使用自定义着色器。
1. Hero Section 概览
Hero Section 是网页设计中的一个术语,通常指页面顶部的一个大型横幅区域。但对于开发人员而言,这个概念可以更直观地理解为用户在访问网站的瞬间所感受到的视觉冲击,或者促使用户停留在该网站的关键原因因素。
话说这天老何接到了一个私活
起始钱不钱的无所谓!主要是想挑战一下自己(不是)。最后的成品如图所示 (互动方式为鼠标滑动
+ 鼠标点击
GIF 压缩太多了内容了,实际要好看很多)。
PC端在线预览地址: fluid-light.vercel.app
Debug调试界面: fluid-light.vercel.app/#debug
2.基础场景搭建
首先我来为你解读一下这个场景里面有什么,他非常简单。只有一个可以接受光照影响的平面几何体以及数个点光源构成,仅此而已。
让我去掉后处理以及一些页面文本元素展示给你看
构建这样的一个基础场景不难。
2.1 构建平面几何体
让我们先来解决平面几何体
值得注意的是,为了让显示效果更好,我使用了正交相机并让平面覆盖整个视窗大小
this.geometry = new THREE.PlaneGeometry(2 * 屏幕宽高比, 2);
然后构建相应的物理材质,可以去 polyhaven 下载一些自己喜欢的texture
并下载下来。
根据右边的分类选择纹理大类细分,随后选择想要下载的纹理点击下载。
因为我们本质是需要 Displacement Texture
置换贴图 & Normal Texture
法线贴图
所以不需要太在意这个纹理是作用在什么物件上面的
随后将纹理导入后赋予材质相应的属性,并对部分参数进行调整。通常直接赋予displacementMap
后 Threejs
中显示平面的凹凸会特别明显。所以记得通过
displacementScale
来调整相应的大小。
this.material = new THREE.MeshPhysicalMaterial({
color: '#121423',
metalness: 0.59,
roughness: 0.41,
displacementMap: 下载的纹理贴图,
displacementScale: 0.1,
normalMap: 下载的法线贴图,
normalScale: new THREE.Vector2(0.68, 0.75),
side: THREE.FrontSide
});
最后将物体加入场景即可
this.mesh = new THREE.Mesh(this.geometry, this.material);
scene.add(this.mesh);
(tips:MeshStandardMaterial 和 MeshPhysicalMaterial 适合需要真实感光照和复杂物理特性的场景,但性能消耗较高。如果您的电脑出现卡顿可以选择消耗较少性能的物理材质)
2.2 灯光加入战场
在本案例中,高级感的来源之一就是灯光的变换。如果您足够细心,可能会注意到一些更微妙的细节:场景中的灯光并不是简单地从 A Color
切换到 B Color
,而是同时存在多个光源,并且它们的强度也在动态变化。这种设计使得场景的光影效果更加丰富和立体。
如果场景中只有一个光源,效果可能会显得单调。而本案例中,灯光的变化呈现出一种层次感:中间是白色,周围还有一层类似年轮的光圈,最后逐渐扩散为纯色背景。这种效果的关键在于同一时间场景中存在多个点光源。虽然多个光源会显著增加性能消耗,但为了实现唯美的视觉效果,这是值得的。
让我们逐步分析灯光是如何实现的。
1. 封装创建点光源的函数
为了简化代码并提高复用性,我们可以先封装一个创建点光源的函数。这个函数会返回一个包含光源对象和目标颜色的对象。
createPointLight(intensity) {
const light = new THREE.PointLight(
0xff_ff_ff,
intensity,
100,
Math.random() * 10
);
light.position.copy(this.lightPosition); //所有的光源都同步在一个位置
return {
object: light,
targetColor: new THREE.Color()
};
}
2. 生成多个点光源
接下来,我们可以调用这个函数生成多个点光源,并将它们添加到场景中。
this.colors = [
new THREE.Color('orange'),
new THREE.Color('red'),
new THREE.Color('red'),
new THREE.Color('orange'),
new THREE.Color('lightblue'),
new THREE.Color('green'),
new THREE.Color('blue'),
new THREE.Color('blue')
];
this.lights = [
this.createPointLight(2),
this.createPointLight(3),
this.createPointLight(2.5),
this.createPointLight(10),
this.createPointLight(2),
this.createPointLight(3),
];
// 初始化灯光颜色
const numberLights = this.lights.length;
for (let index = 0; index < numberLights; index++) {
const colorIndex = Math.min(index, this.colors.length - 1);
this.lights[index].object.color.copy(this.colors[colorIndex]);
}
for (const light of this.lights) this.scene.add(light.object);
3. 动态调整光源强度
在场景中,所有光源同时存在,但它们的强度会有所不同。每次由光照强度为 10 的光源担任场景的主色。当用户点击场景时,灯光会像上楼梯或者传送带一样逐步切换,即由新的点光源担任场景主色。
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
8 8"b, "Ya
8 8 "b, "Ya
8 aa=D光源=a8, "b, "Ya
8 8"b, "Ya "8""""""8
8 8 "b, "Ya 8 8
8 a=C光源=8, "b, "Ya8 8
8 8"b, "Ya "8""""""" 8
8 8 "b, "Ya 8 8
8 a=B光源=8, "b, "Ya8 8
8 8"b, "Ya "8""""""" 8
8 8 "b, "Ya 8 8
8=A光源=, "b, "Ya8 8
8"b, "Ya "8""""""" 8
8 "b, "Ya 8 8
8, "b, "Ya8 8
"Ya "8""""""" 8
"Ya 8 8
"Ya8 8
"""""""""""""""""""""""""""""""""""""
让我们看看代码是如何实现的吧
window.addEventListener('click', () => {
// 打乱颜色数组(看个人喜好)
this.colors = [...this.colors.sort(() => Math.random() - 0.5)];
// 标记开始颜色过渡
this.colorTransition = true;
// 为每个灯光设置目标颜色
const numberLights = this.lights.length;
for (let index = 0; index < numberLights; index++) {
const colorIndex = Math.min(index, this.colors.length - 1);
this.lights[index].targetColor = this.colors[colorIndex].clone();
}
});
然后再Render函数中以easeing
方式更新颜色
update() {
// 只在需要时更新颜色
if (this.colorTransition) {
const numberLights = this.lights.length;
const baseSmooth = 0.25;
const smoothIncrement = 0.05;
let allTransitioned = true; // 检查所有颜色是否已完成过渡
for (let index = 0; index < numberLights; index++) {
const smoothTime = baseSmooth + index * smoothIncrement;
// 使用目标颜色进行平滑过渡
const currentColor = this.lights[index].object.color;
const targetColor = this.lights[index].targetColor;
this.dampC(currentColor, targetColor, smoothTime, delta);
// 检查是否还在过渡
if (!this.isColorClose(currentColor, targetColor)) {
allTransitioned = false;
}
}
// 如果所有颜色都已完成过渡,停止更新
if (allTransitioned) {
this.colorTransition = false;
}
}
}
3.后处理完善场景
在完成了场景的基本构建之后,我们已经实现了大约 80% 的内容。即使现在加上 UI,效果也不会太差。不过,为了让场景更具视觉冲击力和艺术感,我们可以通过后处理(Post Processing)技术来进一步提升质感。
使用 UnrealBloomPass
和 FilmPass
在本文中,我们将使用 UnrealBloomPass
(辉光效果)和 FilmPass
(电影滤镜)来增强场景的视觉效果。以下是具体的实现步骤:
- 引入后处理库:首先,我们需要引入
Three.js
的后处理库EffectComposer
以及相关的Pass
类。 - 创建
EffectComposer
:EffectComposer
是后处理的核心类,用于管理和执行各种后处理效果。 - 添加
RenderPass
:RenderPass
用于将场景渲染到后处理管道中。 - 添加
UnrealBloomPass
:UnrealBloomPass
用于实现辉光效果,可以使场景中的亮部区域产生光晕。 - 添加
FilmPass
:FilmPass
用于模拟电影胶片的效果,增加颗粒感和复古风格。
这里的具体参数需要看个人品味进行调试。同款参数可以从这里看我的源码。具体路径位于src\js\world\effect.js
this.composer = new EffectComposer(this.renderer);
this.composer.addPass(this.renderPass);
this.composer.addPass(this.bloomPass);
this.composer.addPass(this.filmPass);
此时页面的质感是不是一下就上来了呢?
最后我们需要添加最关键的一部,就是画面扭曲。
这里我们需要用到 Threejs
的 ShaderPass
,让我们来创建一个初始的ShaderPass
,仅将 EffectComposer 的读取缓冲区的图像内容复制到其写入缓冲区,而不应用任何效果。
具体内容你可以从 Threejs 后处理中了解到更多
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
const BaseShader = {
name: 'BaseShader',
uniforms: {
'tDiffuse': { value: null },
'opacity': { value: 1.0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform float opacity;
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main() {
vec4 texel = texture2D( tDiffuse, vUv );
gl_FragColor = opacity * texel;
}`
};
const BasePass = new ShaderPass( BaseShader );
此时画面不会有任何变化
让我们对uv
进行简单操纵,让其读取tDiffuse
时可以发生扭曲
vec2 uv = vUv;
uv.y += sin(uv.x * frequency + offset) * amplitude;
gl_FragColor = texture2D(tDiffuse, uv);
最后得到效果
4.最后一些话
技术的未来与前端迁移
随着 AI 技术的快速发展,各类技术的门槛正在大幅降低,以往被视为高门槛的 3D
技术也不例外。与此同时,过去困扰开发者的数字资产构建成本问题,也正在被最新的 3D generation
技术所攻克。这意味着,在不久的将来,前端开发将迎来一次技术迁移,开发者需要掌握更新颖的交互方式和更出色的视觉效果。
为什么选择 Three.js
?
Three.js
作为最流行的 WebGL
库之一,不仅简化了三维图形的开发流程,还提供了丰富的功能和强大的扩展性。无论是创建复杂的 3D 场景,还是实现炫酷的视觉效果,Three.js
都能帮助开发者快速实现目标。
本专栏的愿景
本专栏的愿景是通过分享 Three.js
的中高级应用和实战技巧,帮助开发者更好地将 3D
技术应用到实际项目中,打造令人印象深刻的 Hero Section
。我们希望通过本专栏的内容,能够激发开发者的创造力,推动 Web3D
技术的普及和应用。
加入社区,共同成长
如果您对 Threejs
这个 3D
图像框架很感兴趣,或者您也深信未来国内会涌现越来越多 3D
设计风格的网站,欢迎加入 ice 图形学社区。这里是国内 Web 图形学最全的知识库,致力于打造一个全新的图形学生态体系!您可以在认证达人里找到我这个 Threejs
爱好者和其他大佬。
此外,如果您很喜欢 Threejs
又在烦恼其原生开发的繁琐,那么我诚邀您尝试 Tresjs 和 TvTjs, 他们都是基于 Vue
的 Threejs
框架。 TvTjs 也为您提供了大量的可使用案例,并且拥有较为活跃的开发社区,在这里你能碰到志同道合的朋友一起做开源!
5.下期预告
未来科技?机器人概念官网来袭 !!!
6. 往期回顾
2025 年了,我不允许有前端不会用 Trae 让页面 Hero Section 变得高级!!!(Threejs)
2024年了,前端人是时候给予页面一点 Hero Section 魔法了!!! (Three.js)
来源:juejin.cn/post/7478403990141796352