稀土掘金技术社区

老板花一万大洋让我写的艺术工作室官网?! HeroSection 再度来袭!(Three.js)

引言

哈喽大家好!距离我上次发文已经过去半个月了,差点又变回了那只熟悉的“老鸽子”。不行,我不能堕落!我还没有将 Web3D 推广到普罗大众,还没有让更多人感受到三维图形的魅力 (其实是还没有捞着米)。怀着这样的心情,我决定重新振作,继续为大家带来更多关于 Three.js 和 Shader 的进阶内容。

前置条件

欢迎阅读本篇文章!在深入探讨 Three.js 和 Shader (GLSL) 的进阶内容之前,确保您已经具备以下基础知识:

  1. 「Three.js 基础」:您需要熟悉 Three.js 的基本概念和使用方法,包括场景(Scene)、相机(Camera)、渲染器(Renderer)、几何体(Geometry)、材质(Material)和网格(Mesh)等核心组件。如果您还不熟悉这些内容,建议先学习 Three.js 的入门教程。
  2. 「Shader 语法」:本文涉及 GLSL(OpenGL Shading Language)的编写,因此您需要了解 GLSL 的基本语法,包括顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)的编写,以及如何在 Three.js 中使用自定义着色器。

Hero Section 概览

❝

Hero Section 是网页设计中的一个术语,通常指页面顶部的一个大型横幅区域。但对于开发人员而言,这个概念可以更直观地理解为「用户在访问网站的瞬间所感受到的视觉冲击,或者促使用户停留在该网站的关键原因因素」。

❞

话说这天老何接到了一个私活

Image

起始钱不钱的无所谓!主要是想挑战一下自己(不是)。最后的成品如图所示 (互动方式为鼠标滑动 + 鼠标点击 GIF 压缩太多了内容了,实际要好看很多)。

Image

PC端在线预览地址: https://fluid-light.vercel.app

Debug调试界面: https://fluid-light.vercel.app/#debug

源码地址: https://github.com/hexianWeb/fluid-light

基础场景搭建

首先我来为你解读一下这个场景里面有什么,他非常简单。只有一个「可以接受光照影响的平面几何体」以及「数个点光源」构成,仅此而已。

让我去掉后处理以及一些页面文本元素展示给你看

Image

构建这样的一个基础场景不难。

构建平面几何体

让我们先来解决平面几何体

值得注意的是,为了让显示效果更好,我使用了正交相机并让平面覆盖整个视窗大小

    this.geometry = new THREE.PlaneGeometry(2 * 屏幕宽高比, 2);

然后构建相应的物理材质,可以去 polyhaven 下载一些自己喜欢的texture 并下载下来。

Image

根据右边的分类选择纹理大类细分,随后选择想要下载的纹理点击下载。

因为我们本质是需要 Displacement Texture置换贴图 & Normal Texture 法线贴图

所以不需要太在意这个纹理是作用在什么物件上面的

Image

随后将纹理导入后赋予材质相应的属性,并对部分参数进行调整。通常直接赋予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」 适合需要真实感光照和复杂物理特性的场景,但性能消耗较高。如果您的电脑出现卡顿可以选择消耗较少性能的物理材质)

灯光加入战场

在本案例中,高级感的来源之一就是灯光的变换。如果您足够细心,可能会注意到一些更微妙的细节:场景中的灯光并不是简单地从 A Color 切换到 B Color,而是同时存在多个光源,并且它们的强度也在动态变化。这种设计使得场景的光影效果更加丰富和立体。

Image

如果场景中只有一个光源,效果可能会显得单调。而本案例中,灯光的变化呈现出一种层次感:「中间是白色,周围还有一层类似年轮的光圈,最后逐渐扩散为纯色背景」。这种效果的关键在于「同一时间场景中存在多个点光源」。虽然多个光源会显著增加性能消耗,但为了实现唯美的视觉效果,这是值得的。

让我们逐步分析灯光是如何实现的。

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,    "Ya8                           8  "b,    "Ya8                    aa=D光源=a8,   "b,  "Ya8                    8"b,    "Ya   "8""""""88                    8  "b,    "Ya  8      88             a=C光源=8,   "b,    "Ya8      88             8"b,    "Ya   "8"""""""      88             8  "b,    "Ya  8             88      a=B光源=8,   "b,    "Ya8             88      8"b,    "Ya   "8"""""""             88      8  "b,    "Ya  8                    88=A光源=,   "b,    "Ya8                    88"b,    "Ya   "8"""""""                    88  "b,    "Ya  8                           88,   "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; } } }

Image

后处理完善场景

在完成了场景的基本构建之后,我们已经实现了大约 80% 的内容。即使现在加上 UI,效果也不会太差。不过,为了让场景更具视觉冲击力和艺术感,我们可以通过后处理(Post Processing)技术来进一步提升质感。

Image

使用 UnrealBloomPass 和 FilmPass

在本文中,我们将使用 UnrealBloomPass(辉光效果)和 FilmPass(电影滤镜)来增强场景的视觉效果。以下是具体的实现步骤:

  1. 「引入后处理库」:首先,我们需要引入 Three.js 的后处理库 EffectComposer 以及相关的 Pass 类。
  2. 「创建 EffectComposer」:EffectComposer 是后处理的核心类,用于管理和执行各种后处理效果。
  3. 「添加 RenderPass」:RenderPass 用于将场景渲染到后处理管道中。
  4. 「添加 UnrealBloomPass」:UnrealBloomPass 用于实现辉光效果,可以使场景中的亮部区域产生光晕。
  5. 「添加 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);

此时页面的质感是不是一下就上来了呢?

Image

最后我们需要添加最关键的一部,就是画面扭曲。

这里我们需要用到 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);

最后得到效果

Image

最后一些话

随着 AI 技术的快速发展,各类技术的门槛正在大幅降低,以往被视为高门槛的 3D 技术也不例外。与此同时,过去困扰开发者的数字资产构建成本问题,也正在被最新的 3D generation 技术所攻克。这意味着,在不久的将来,前端开发将迎来一次技术迁移,开发者需要掌握更新颖的交互方式和更出色的视觉效果。

「为什么选择 Three.js?」

Three.js 作为最流行的 WebGL 库之一,不仅简化了三维图形的开发流程,还提供了丰富的功能和强大的扩展性。无论是创建复杂的 3D 场景,还是实现炫酷的视觉效果,Three.js 都能帮助开发者快速实现目标。

「本专栏的愿景」

本专栏的愿景是通过分享 Three.js 的中高级应用和实战技巧,帮助开发者更好地将 3D 技术应用到实际项目中,打造令人印象深刻的 Hero Section。我们希望通过本专栏的内容,能够激发开发者的创造力,推动 Web3D 技术的普及和应用。

下期预告

「未来科技?机器人概念官网来袭 !!!」

Image