我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛
声明:本文涉及图文和模型素材仅用于个人学习、研究和欣赏,请勿二次修改、非法传播、转载、出版、商用、及进行其他获利行为。
背景
深居内陆的人们,大概每个人都有过大海之梦吧。夏日傍晚在沙滩漫步奔跑;或是在海上冲浪游泳;或是在海岛游玩探险;亦或静待日出日落……本文使用 React + Three.js
技术栈,实现 3D
海洋和岛屿,主要包含知识点包括:Tone Mapping
、Water
类、Sky
类、Shader
着色、ShaderMaterial
着色器材质、Raycaster
检测遮挡以及 Three.js
的其他基础知识,让我们在这个夏天通过此页面共赴大海之约。
效果
💻
本页面适配PC
端及移动端,大屏访问效果更佳。👁🗨
在线预览地址1:3d-eosin.vercel.app/#/ocean👁🗨
在线预览地址2:dragonir.github.io/3d/#/ocean
码上掘金
地址:https://code.juejin.cn/pen/7102188496340647950
实现
👨🎨 素材准备
开发之前,需要准备页面所需的素材,本文用到的海岛素材是在 sketchfab.com 找的免费模型。下载好素材之后,在 Blender
中打开,按自己的想法调整模型的颜色、材质、大小比例、角度、位置等信息,删减不需要的模块、缩减面数以压缩模型体积,最后删除相机、光照、UV
、动画等多余信息,只导出模型网格备用。
📦 资源引入
首先,引入开发所需的必备资源,OrbitControls
用于镜头轨道控制;GLTFLoader
用于加载 gltf
格式模型;Water
是 Three.js
内置的一个类,可以生成类似水的效果;Sky
可以生成天空效果;TWEEN
用来生成补间动画;Animations
是对 TWEEN
控制镜头补间动画方法的封装;waterTexture
、flamingoModel
、islandModel
三者分别是水的法向贴图、飞鸟模型、海岛模型;vertexShader
和 fragmentShader
是用于生成彩虹的 Shader
着色器。
1 | js复制代码import * as THREE from "three"; |
📃 页面结构
页面主要由3部分构成:canvas.webgl
用于渲染 WEBGL
场景;div.loading
用于模型加载完成前显示加载进度;div.point
用于添加交互点,省略部分是其他几个交互点信息。
1 | js复制代码render () { |
🌏 场景初始化
在这部分,先定义好需要的状态值,loadingProcess
用于显示页面加载进度。
1 | js复制代码state = { |
定义一些全局变量和参数,初始化场景、相机、镜头轨道控制器、灯光、页面缩放监听等。
1 | js复制代码const clock = new THREE.Clock(); |
💡
Tone Mapping
可以注意到,本文使用了 renderer.toneMapping = THREE.ACESFilmicToneMapping
来设置页面渲染效果。目前 Three.js
中有以下几种 Tone Mapping
值,它们定义了 WebGLRenderer
的 toneMapping
属性,用于在近似标准计算机显示器或移动设备的低动态范围 LDR
屏幕上展示高动态范围 HDR
外观。大家可以修改不同的值看看渲染效果有何不同。
THREE.NoToneMapping
THREE.LinearToneMapping
THREE.ReinhardToneMapping
THREE.CineonToneMapping
THREE.ACESFilmicToneMapping
🌊 海
使用 Three.js
自带的 Water
类创建海洋,首先创建一个平面网格 waterGeometry
,让后将它传递给 Water
,并配置相关属性,最后将海洋添加到场景中。
1 | js复制代码const waterGeometry = new THREE.PlaneGeometry(10000, 10000); |
💡
Water 类
参数说明:
textureWidth
:画布宽度textureHeight
:画布高度waterNormals
:法向量贴图sunDirection
:阳光方向sunColor
:阳光颜色waterColor
:水颜色distortionScale
:物体倒影分散度fog
:雾alpha
:透明度
🌞 空
接着,使用 Three.js
自带的天空类 Sky
创建天空,通过修改着色器参数设置天空样式,然后创建太阳并添加到场景中。
1 | js复制代码const sky = new Sky(); |
💡
Sky 类
天空材质着色器参数说明:
turbidity
浑浊度rayleigh
视觉效果就是傍晚晚霞的红光的深度luminance
视觉效果整体提亮或变暗mieCoefficient
散射系数mieDirectionalG
定向散射值
🌈 虹
首先,创建具有彩虹渐变效果的着色器 Shader
, 然后使用着色器材质 ShaderMaterial
, 创建圆环 THREE.TorusGeometry
并添加到场景中。
顶点着色器 vertex.glsl:
1 | glsl复制代码varying vec2 vUV; |
片段着色器 fragment.glsl:
1 | glsl复制代码varying vec2 vUV; |
彩虹渐变着色器效果:
1 | js复制代码const material = new THREE.ShaderMaterial({ |
💡
Shader 着色器
WebGL
中记述了坐标变换的机制就叫做着色器 Shader
,着色器又有处理几何图形顶点的 顶点着色器
和处理像素的 片段着色器
两种类型
准备顶点着色器和片元着色器
着色器的添加有多种方法,最简单的方法就是把着色器记录在 HTML
中。该方法利用HTML
的 script
标签来实现,如:
顶点着色器:
1 | html复制代码<script id="vshader" type="x-shader/x-vertex"></script> |
片段着色器:
1 | html复制代码<script id="fshader" type="x-shader/x-fragment"></script> |
🎏
也可以像本文中一样,直接使用单独创建glsl
格式文件引入。
着色器的三个变量与运行方式
Uniforms
:是所有顶点都具有相同的值的变量。 比如灯光,雾,和阴影贴图就是被储存在uniforms
中的数据。uniforms
可以通过顶点着色器和片元着色器来访问。Attributes
:是与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes
中的数据。attributes
只可以在顶点着色器中访问。Varyings
:是从顶点着色器传递到片元着色器的变量。对于每一个片元,每一个varying
的值将是相邻顶点值的平滑插值。
顶点着色器
首先运行,它接收 attributes
, 计算每个单独顶点的位置,并将其他数据varyings
传递给片段着色器。片段着色器
后运行,它设置渲染到屏幕的每个单独的片段的颜色。
💡
ShaderMaterial 着色器材质
Three.js
所谓的材质对象 Material
本质上就是着色器代码和需要传递的 uniform
数据光源、颜色、矩阵。Three.js
提供可直接渲染着色器语法的材质 ShaderMaterial
和 RawShaderMaterial
。
RawShaderMaterial
: 和原生WebGL
中一样,顶点着色器、片元着色器代码基本没有任何区别,不过顶点数据和uniform
数据可以通过Three.js
的API
快速传递,要比使用WebGL
原生的API
与着色器变量绑定要方便得多。ShaderMaterial
:ShaderMaterial
比RawShaderMaterial
更方便些,着色器中的很多变量不用声明,Three.js
系统会自动设置,比如顶点坐标变量、投影矩阵、视图矩阵等。
构造函数:
1 | js复制代码ShaderMaterial(parameters : Object) |
parameters
:可选,用于定义材质外观的对象,具有一个或多个属性。
常用属性:
attributes[Object]
:接受如下形式的对象,{ attribute1: { value: []} }
指定要传递给顶点着色器代码的attributes
;键为attribute
修饰变量的名称,值也是对象格式,如{ value: [] }
,value
是固定名称,因为attribute
相对于所有顶点,所以应该回传一个数组格式。只有bufferGeometry
类型的能使用该属性。.uniforms[Object]
:如下形式的对象:{ uniform1: { value: 1.0 }, uniform2: { value: 2.0 }}
指定要传递给shader
代码的uniforms
;键为uniform
的名称,值是如下形式:{ value: 1.0 }
这里value
是uniform
的值。名称必须匹配着色器代码中uniform
的name
,和GLSL
代码中的定义一样。 注意,uniforms
逐帧被刷新,所以更新uniform
值将立即更新GLSL
代码中的相应值。.fragmentShader[String]
:片元着色器的GLSL
代码,它也可以作为一个字符串直接传递或者通过AJAX
加载。.vertexShader[String]
:顶点着色器的GLSL
代码,它也可以作为一个字符串直接传递或者通过AJAX
加载。
🌴 岛
接着,使用 GLTFLoader
加载岛屿模型并添加到场景中。加载之前可以使用 LoadingManager
来管理加载进度。
1 | js复制代码const manager = new THREE.LoadingManager(); |
🦅 鸟
使用 GLTFLoader
加载岛屿模型添加到场景中,获取模型自带的动画帧并进行播放,记得要在 requestAnimationFrame
中更新动画。可以使用 clone
方法在场景中添加多只飞鸟。鸟模型来源于 Three.js
官网。
1 | js复制代码loader.load(flamingoModel, gltf => { |
🖐 交互点
添加交互点,鼠标 hover
悬浮时显示提示语,点击交互点可以切换镜头角度,视角聚焦到交互点对应的位置 📍
上。
1 | js复制代码const points = [ |
🎥 动画
在 requestAnimationFrame
中更新水、镜头轨道控制器、相机、TWEEN
、交互点等动画。
1 | js复制代码const animate = () => { |
💡
Raycaster 检测遮挡
仔细观察,在上述 👆
更新交互点动画的方法中,通过 raycaster
射线来检查交互点是否被物体遮挡,如果被遮挡就隐藏交互点,否则显示交互点,大家可以通过旋转场景观察到这一效果。
💥 镜头光晕
给点光源增加镜头光晕 Lensflare
效果,看起来更加真实,营造满满氛围感!
1 | js复制代码import { Lensflare, LensflareElement } from 'three/examples/jsm/objects/Lensflare.js'; |
总结
本文包含的新知识点主要包括:
Tone Mapping
Water
类Sky
类Shader
着色器ShaderMaterial
着色器材质Raycaster
检测遮挡Lensflare
镜头光晕
想了解其他前端知识或其他未在本文中详细描述的
Web 3D
开发技术相关知识,可阅读我往期的文章。转载请注明原文地址和作者。如果觉得文章对你有帮助,不要忘了一键三连哦 👍。
参考
- [1]. threejs.org
附录
- 朕的3D专栏
- [1]. 🦊 Three.js 实现3D开放世界小游戏:阿狸的多元宇宙
- [2]. 🔥 Three.js 火焰效果实现艾尔登法环动态logo
- [3]. 🐼 Three.js 实现2022冬奥主题3D趣味页面,含冰墩墩
...
- [1]. 📷 前端实现很哇塞的浏览器端扫码功能
- [2]. 🌏 前端瓦片地图加载之塞尔达传说旷野之息
- [3]. 😱 仅用CSS几步实现赛博朋克2077风格视觉效果
...
本文转载自: 掘金