

Threejs实现穿越云层动效
source link: https://segmentfault.com/a/1190000040526458
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Threejs实现穿越云层动效
上文说到,我对《你的性格主导色》活动中最感兴趣的部分就是通过 Three.js
实现穿越云层动效了,据作者说每朵云出现的位置都是随机的,效果很好,下图是我实现的版本。
在线 Demo
首先说下实现穿越云层动效的基本思路:
- 沿着 Z 轴均匀的放一堆 64*64 的平面图形,这些平面的 X 坐标和 Y 坐标是随机的(很像下图的桶装薯片)
- 把上面的所有图形合并成一个大的图形
- 把大的图形和贴片材质(云)生成网格,网格放进场景中
- 动效就是将相机从远处沿着 Z 轴缓慢移动,就会有了穿越云层的效果
首先官方文档提供了一个创建一个场景的快速开始,阅读后可以对下面的内容更好的理解。
下面介绍下Three.js
中的基本概念。仅限我这新手的理解。有讲的好的文档或者分享,欢迎帮忙指个路。
场景就是一块空间,用来装下我们想要渲染的内容。最简单的用处就是,场景可以添加一个网格,然后渲染出来。
// 初始化场景 var scene = new THREE.Scene(); // 其他代码... // 把物体添加进场景 scene.add(mesh); // 渲染场景 renderer.render(scene, camera);
这里说下场景中的坐标规则:原点是 canvas 的平面中心,Z 轴垂直于 X、Y 轴,正向是冲着我们的,我这里把 Z 轴的线做了些旋转,不然我们看不到,如下图:
const scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000); camera.position.set(0, 0, 100); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 线段1,红色的,从原点到X轴40 const points = []; points.push(new THREE.Vector3(0, 0, 0)); points.push(new THREE.Vector3(40, 0, 0)); const geometry1 = new THREE.BufferGeometry().setFromPoints(points); var material1 = new THREE.LineBasicMaterial({ color: 'red' }); var line1 = new THREE.Line(geometry1, material1); // 线段2,蓝色的,从原点到Y轴40 points.length = 0; points.push(new THREE.Vector3(0, 0, 0)); points.push(new THREE.Vector3(0, 40, 0)); const geometry2 = new THREE.BufferGeometry().setFromPoints(points); var material2 = new THREE.LineBasicMaterial({ color: 'blue' }); var line2 = new THREE.Line(geometry2, material2); // 线段3,绿色的,从原点到Z轴40 points.length = 0; points.push(new THREE.Vector3(0, 0, 0)); points.push(new THREE.Vector3(0, 0, 40)); const geometry3 = new THREE.BufferGeometry().setFromPoints(points); var material3 = new THREE.LineBasicMaterial({ color: 'green' }); var line3 = new THREE.Line(geometry3, material3); // 做了个旋转,不然看不到Z轴上的线 line3.rotateX(Math.PI / 8); line3.rotateY(-Math.PI / 8); scene.add(line1, line2, line3); renderer.render(scene, camera);
场景内的物体要想被我们看见,也就是渲染出来,需要相机去“看”,通过上面的坐标系图,我们知道同一个物体,相机观察的角度不同,肯定也会呈现出不一样的画面。最常用的就是这里用的透视相机,可以穿透物体,用在这里正好穿透云层,效果拔群。
// 初始化相机 camera = new THREE.PerspectiveCamera(70, pageWidth / pageHeight, 1, 1000); // 最后,场景和相机一起渲染出来,我们就能够看到场景中的物体了 renderer.render(scene, camera);
材质很好理解,在最初的例子中,使用MeshBasicMaterial
给立方体添加了颜色。材质的使用方式是,将材质和图形共同生成一个网格,我们这里使用的是比较复杂的贴图材质。
// 贴图材质 const material = new THREE.ShaderMaterial({ // 这里的值是给着色器传递的 uniforms: { map: { type: 't', value: texture }, fogColor: { type: 'c', value: fog.color }, fogNear: { type: 'f', value: fog.near }, fogFar: { type: 'f', value: fog.far } }, vertexShader: vShader, fragmentShader: fShader, transparent: true });
图形和网格
Three.js
默认提供了很多的几何体图形,也就是各种Geometry
,他们的基类是BufferGeometry
。
图形可以进行合并,像这里就是 clone 了很多个一样的平面图形,通过修改各自的位置,生成合并后形成一大片云的效果。
最初我认为图形和网格是一个概念,后来知道了,材质和图形可以生成网格,网格可以放进场景中。
// 把上面合并出来的形状和材质,生成一个网格 mesh = new THREE.Mesh(mergedGeometry, material);
将场景和相机渲染到目标元素上,会生成一个canvas
,如果是一个静态的场景,那么渲染完毕就可以了。但是如果是一个会动的场景,这里需要用到一个原生函数requestAnimationFrame
。
function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); }
上面的代码是一个渲染循环,在一般屏幕上的频率是 60HZ,在高刷屏幕上会增长刷新频率,也就是会给用户良好的刷新体验,不需要我们自己使用setInterval
去控制。并且当用户切换到其它的标签页时,它会暂停刷新,不会浪费用户宝贵的处理器资源,也不会损耗电池的使用寿命。
过程其实很有意思,也很曲折。
扒下来了《你的性格主导色》活动的前端代码,但是云层动效相关有很多代码压缩过了,看不懂。
怎么办?然后我就去 three.js
找官方的例子去,找了半天只找到一个下图这样的:
后来经过各种搜索,终于在three.js
的讨论区发现了这种穿越云层的特效,是three.js
的作者很久之前写的例子。
把云层动效源码拿到手以后,我对比后感觉 imyzf
同学应该也是从这个例子中借鉴了一下。
我发现源码中的three.js
的版本有一些落后,源码中的版本是 55,最新的是 131 版本,版本差距有点大,已经没有了上面的一些类和 API,下面介绍下不同的部分:
THREE.Geometry
首先就是这个类在最新版没有了,这个类是用来将很多个平面图形,合并为一个图形。观察下面的代码,55 的版本是先生成一个Geometry
,然后生成一个平面网格,调整网格的坐标后,把网格和Geometry
合并(这里有点不懂了,图形怎么和网格合并,而且是同一个网格,我猜是在合并的时候新生成了一个网格)。
// 初始化一个基础的图形 geometry = new THREE.Geometry(); // 初始化一个64*64的平面 var plane = new THREE.Mesh(new THREE.PlaneGeometry(64, 64)); for (var i = 0; i < 8000; i++) { // 调整平面图案的位置和旋转角度等 plane.position.x = Math.random() * 1000 - 500; plane.position.y = -Math.random() * Math.random() * 200 - 15; plane.position.z = i; plane.rotation.z = Math.random() * Math.PI; plane.scale.x = plane.scale.y = Math.random() * Math.random() * 1.5 + 0.5; // 平面合并到基础图形 THREE.GeometryUtils.merge(geometry, plane); }
查询最新文档后,发现所有图形的基类BufferGeometry
提供 clone 方法,平面图形自然也可以被 clone 出来。
// 一个平面形状 const geometry = new THREE.PlaneGeometry(64, 64); const geometries = []; for (var i = 0; i < CloudCount; i++) { const instanceGeometry = geometry.clone(); // 把这个克隆出来的云,通过随机参数,做一些位移,达到一堆云彩的效果,每次渲染出来的云堆都不一样 // X轴偏移后,通过调整相机位置达到平衡 // Y轴想把云彩放在场景的偏下位置,所以都是负值 // Z轴位移就是:当前第几个云*每个云所占的Z轴长度 instanceGeometry.translate(Math.random() * RandomPositionX, -Math.random() * RandomPositionY, i * perCloudZ); geometries.push(instanceGeometry); } // 把这些形状合并 const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
GeometryUtils.merge
旧代码码中有一个这样的 API,这是一个很重要的 API,目的就是合并图形和网格,生成一片云,最新版的three.js
已经没有了。
// 合并所有的平面图形到一个基础图形 THREE.GeometryUtils.merge(geometry, plane);
通过查询最新版的文档,发现了可以将一组图形进行合并,个人觉得比上面的好一些,语义上好很多。上面的代码是重复的把同一个平面合并到一个基础图形上面,下面是把这一组平面合成为一个新的平面。
// 把这些形状合并 const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
着色器代码逻辑我是完全的没有修改,GLSL(OpenGL 着色语言 OpenGL Shading Language),原来的着色器代码是写在<script>
元素标签里的,这和我们的工程化项目不符合。
// 原来的 <script id="vs" type="x-shader/x-vertex"> varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } </script> <script id="fs" type="x-shader/x-fragment"> uniform sampler2D map; uniform vec3 fogColor; uniform float fogNear; uniform float fogFar; varying vec2 vUv; void main() { float depth = gl_FragCoord.z / gl_FragCoord.w; float fogFactor = smoothstep( fogNear, fogFar, depth ); gl_FragColor = texture2D(map, vUv ); gl_FragColor.w *= pow( gl_FragCoord.z, 20.0 ); gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor ); } </script>
后来找了几个地方才知道可以时间使用字符串代替:
const vShader = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } `;
顶点着色器和片元着色器代码,我目前是真的不懂,先抄为敬。
最后放上源码,感兴趣的同学可以看一下,欢迎 Star 和提出建议。
Recommend
-
61
蓝色的天空,和铺满天空中的那些云层,一眼看上去就充满了夏天的气息。这样的背景,大家有想尝试进行作画的时候吗?这次就由插画家すけべ椅子老师为大家带来,夏季天空画法的讲座。文章的后半段还会配上Palmie
-
43
前言 之前逛论坛时看到一篇利用 three.js 实现粒子模型切换动画的分享,具体的效果如下: 也可以去预览。 但是作者并没有把源码分享出来,正好最近在学习 threejs,正好抽时间写了一个类似的 demo,希望能帮助一些喜欢 threejs 的初学者。效
-
118
弹簧动效是IOS系统原生自带的一个效果,如在iPhone上面的照片点开大图的展示效果就是一个弹簧动画,如下图所示: 它有一个弹闪的过程,一大一...
-
80
-
57
本文包含动图较多,总共大约有10M,移动端请谨慎 本文示例代码下载 Apple Watch 第三代发布的时候,我借健身...
-
37
原文链接 github.com/XboxYan/not… 按钮(button)可能是网页中最常见的组件之一了,大部分都平淡无奇,如果你碰到的是一个这样的按钮,会不会忍不住多点几次呢? 通常这类效果第一反应可能就是借助canvas了,比如下面这个案例 效果
-
17
用React Hooks与Web Animation API实现动效组件阿里巴巴集团 前端工程师一个体验良好的动效完全可以吸引用户的更多停留,以一种通用的方式从侧面提升业务的转化效果,某些特定场景...
-
15
这一次是一个非常非常干的干货,手把手教你如何配合开发将小动效在页面中展示出来。关于如何做动效,网上有相关的课程和文章,我在这里就不说了,因为我可能做的...
-
9
如何在 ThreeJS 中实现辉光效果发布于 2 分钟前全局辉光(Bloom),又称泛光。它其实是一种作用于特定区域的外发光效果。在游戏中,我们经常可以见到外发光...
-
8
AIGC的云层,对话微软大中华区Azure事业部总经理陶然_VR陀螺 ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK